/* global React, MotionCanvas */
const { useState, useEffect, useCallback, useMemo, useRef } = React;

/* ------------------ Questions ------------------ */
const DIMENSIONS = [
  { id: 'focus',    name: 'Focus',      hint: 'Do you know where AI should go first?' },
  { id: 'data',     name: 'Data',       hint: 'Can you measure what AI changes?' },
  { id: 'team',     name: 'Team',       hint: 'Can your people execute it?' },
  { id: 'momentum', name: 'Momentum',   hint: 'Can you compound the wins?' },
];

// Each question belongs to a dimension. Scale questions: higher answer = higher dim score.
// Choice questions: each choice has a score 0–4. Slider: defined bands.
const QUESTIONS = [
  // ---------- FOCUS ----------
  {
    dim: 'focus', type: 'binary', weight: 1.2,
    prompt: 'Do you have a <em>written list</em> of your top 3 AI opportunities, ranked by expected ROI?',
    sub: 'Not "we\'ve talked about it." Written down, shared, and updated.',
    choices: [
      { label: 'Yes', score: 4 },
      { label: 'No',  score: 0 },
    ],
  },
  {
    dim: 'focus', type: 'choice',
    prompt: 'How do AI initiatives currently <em>get picked</em> at your company?',
    sub: 'The honest answer, not the aspirational one.',
    choices: [
      { label: 'Whoever\'s loudest in the meeting', score: 0 },
      { label: 'The CEO reads something and forwards it', score: 1 },
      { label: 'Whatever vendor demos most compellingly', score: 1 },
      { label: 'We have a loose prioritization, often bypassed', score: 2 },
      { label: 'Other', score: 2, other: true },
    ],
  },
  {
    dim: 'focus', type: 'scale',
    prompt: 'We can name a specific business metric that AI will move in the next <em>90 days</em>.',
    sub: 'Revenue per rep, close time, NPS, churn — something concrete.',
  },

  // ---------- DATA ----------
  {
    dim: 'data', type: 'scale', weight: 1.1,
    prompt: 'If we deployed an AI tool tomorrow, we could <em>measure</em> its impact within 30 days.',
    sub: 'Baseline data exists, is clean, and is queryable without a six-week data project.',
  },
  {
    dim: 'data', type: 'choice',
    prompt: 'Your <em>customer and operational data</em> lives in…',
    choices: [
      { label: 'Spreadsheets, email threads, and people\'s heads', score: 0 },
      { label: 'A few tools that don\'t talk to each other', score: 1 },
      { label: 'A CRM + an ERP, roughly in sync', score: 2 },
      { label: 'Systems connected, but the numbers sometimes disagree', score: 3 },
      { label: 'A single source of truth we trust for decisions', score: 4 },
    ],
  },
  {
    dim: 'data', type: 'scale',
    prompt: 'Our leadership team looks at the <em>same dashboard</em> every week and agrees on what it says.',
  },

  // ---------- TEAM ----------
  {
    dim: 'team', type: 'choice',
    prompt: 'How many people on your team have <em>shipped</em> something AI-powered to production?',
    choices: [
      { label: 'Zero — we\'re exploring', score: 0 },
      { label: 'One person, one side-project', score: 1 },
      { label: '2–3 people, mostly internal tools', score: 2 },
      { label: 'A small team, customer-facing', score: 3 },
      { label: 'AI-native across multiple functions', score: 4 },
    ],
  },
  {
    dim: 'team', type: 'scale',
    prompt: 'Our engineers use AI coding tools <em>daily</em> and can articulate where they help and where they don\'t.',
    sub: 'Past the demo phase. Actual muscle memory.',
  },
  {
    dim: 'team', type: 'scale',
    prompt: 'When we commit to an initiative, we <em>finish it</em>. Our backlog is not a graveyard.',
  },

  // ---------- MOMENTUM ----------
  {
    dim: 'momentum', type: 'slider', weight: 1.1,
    prompt: 'How many <em>weeks</em> pass between deciding to try something and actually shipping v1?',
    sub: 'Your typical internal initiative, from "yes" to "in production".',
    min: 0, max: 24, step: 1, default: 8, unit: 'weeks',
    bands: [
      { max: 2, score: 4 }, { max: 4, score: 3 }, { max: 8, score: 2 }, { max: 16, score: 1 }, { max: 999, score: 0 }
    ],
  },
  {
    dim: 'momentum', type: 'scale',
    prompt: 'We\'ve killed an initiative in the last 12 months because it <em>wasn\'t working</em>.',
    sub: 'Discipline to stop is rarer than discipline to start.',
  },
  {
    dim: 'momentum', type: 'choice',
    prompt: 'The last time you tried a new AI tool, you…',
    choices: [
      { label: 'Never actually rolled it out', score: 0 },
      { label: 'Rolled out but adoption stalled', score: 1 },
      { label: 'Got partial adoption, mixed results', score: 2 },
      { label: 'Shipped it and measured impact', score: 3 },
      { label: 'Shipped, measured, iterated to v2', score: 4 },
    ],
  },
];

const SCALE_LABELS = ['Strongly disagree', 'Disagree', 'Neutral', 'Agree', 'Strongly agree'];

/* ------------------ Scoring ------------------ */
function scoreAnswers(answers) {
  // Each dimension gets a weighted average of its question scores, 0–4.
  const byDim = {};
  DIMENSIONS.forEach(d => { byDim[d.id] = { sum: 0, w: 0 }; });
  QUESTIONS.forEach((q, i) => {
    const a = answers[i];
    if (a == null) return;
    const w = q.weight || 1;
    let s = 0;
    if (q.type === 'scale') {
      s = q.reverse ? (4 - a) : a; // a is 0–4
    } else if (q.type === 'choice' || q.type === 'binary') {
      s = q.choices[a].score;
    } else if (q.type === 'slider') {
      const band = q.bands.find(b => a <= b.max);
      s = band ? band.score : 0;
    }
    byDim[q.dim].sum += s * w;
    byDim[q.dim].w += w;
  });
  const dims = DIMENSIONS.map(d => {
    const { sum, w } = byDim[d.id];
    const score04 = w > 0 ? sum / w : 0;
    return { id: d.id, name: d.name, score: score04, pct: Math.round((score04/4)*100) };
  });
  const overall = Math.round(dims.reduce((a,d) => a + d.pct, 0) / dims.length);
  return { dims, overall };
}

function bandFor(score) {
  if (score >= 80) return { label: 'Accelerating', read: 'You have <em>direction</em>. Most of your peers are still picking tools. You\'re past that — ready to compound wins and widen the lead.' };
  if (score >= 60) return { label: 'Directional', read: 'You have a vector, but the magnitude is small. The next 90 days decide whether this becomes <em>compounding</em> or a series of one-off wins.' };
  if (score >= 40) return { label: 'Searching', read: 'Real activity, unclear trajectory. You\'re <em>spending</em> on AI. You\'re not yet <em>investing</em> in it. The difference is measurement and follow-through.' };
  if (score >= 20) return { label: 'Curious', read: 'Lots of energy, no direction. The risk isn\'t that AI fails — it\'s that the <em>exhaustion</em> from shallow attempts makes it harder to try again later.' };
  return { label: 'Uncommitted', read: 'You\'re ahead of the people pretending. Now the work is to pick one thing that <em>matters</em> and measure it.' };
}

function recommendationsFor(result) {
  const weak = [...result.dims].sort((a,b) => a.pct - b.pct);
  const primary = weak[0];
  const secondary = weak[1];
  const recs = {
    focus: [
      { title: 'Start with a Direction memo', body: 'One page. Top 3 AI opportunities, scored on revenue impact × feasibility. Nothing else ships until this exists.' },
      { title: 'Kill two initiatives', body: 'You can\'t add focus. You can only remove noise. Pick the two lowest-signal projects and stop them this week.' },
    ],
    data: [
      { title: 'Baseline before building', body: 'Pick one metric that AI should move. Instrument it. Two weeks of clean data beats six months of tool evaluation.' },
      { title: 'Unify the source of truth', body: 'Until your leadership team sees one dashboard and agrees, AI outputs will be contested. Fix this first.' },
    ],
    team: [
      { title: 'Name an AI accountable', body: 'One senior person, 40% of their time, empowered to kill bad projects. Without an owner, AI belongs to everyone and no one.' },
      { title: 'Dev tooling baseline', body: 'Every engineer on Cursor or Copilot within 30 days. Measure throughput before and after. No opinions, just numbers.' },
    ],
    momentum: [
      { title: 'Pick a 4-week target', body: 'One AI initiative, one metric, shipped in 4 weeks. The goal isn\'t impact — it\'s proving your org can close a loop.' },
      { title: 'Write the stop-rule', body: 'Before you start the next initiative, write down the condition that would cause you to kill it. Momentum requires the courage to stop.' },
    ],
  };
  return [
    recs[primary.id][0],
    recs[secondary.id][0],
    recs[primary.id][1],
  ];
}

/* ------------------ App ------------------ */
function DirectionScore() {
  const [phase, setPhase] = useState('intro'); // intro | quiz | lead | done
  const [idx, setIdx] = useState(0);
  const [answers, setAnswers] = useState(() => Array(QUESTIONS.length).fill(null));
  const [others, setOthers] = useState({});
  const [lead, setLead] = useState({ name: '', email: '', company: '', role: '', size: '' });

  // persist
  useEffect(() => {
    try {
      const raw = localStorage.getItem('ds_state');
      if (raw) {
        const s = JSON.parse(raw);
        if (s && Array.isArray(s.answers) && s.answers.length === QUESTIONS.length) {
          setAnswers(s.answers);
          setIdx(s.idx || 0);
          setPhase(s.phase || 'intro');
          if (s.others) setOthers(s.others);
          if (s.lead) setLead(s.lead);
        }
      }
    } catch {}
  }, []);
  useEffect(() => {
    try { localStorage.setItem('ds_state', JSON.stringify({ phase, idx, answers, others, lead })); } catch {}
  }, [phase, idx, answers, others, lead]);

  const setAnswer = useCallback((i, v) => {
    setAnswers(a => { const next = a.slice(); next[i] = v; return next; });
  }, []);

  const next = useCallback(() => {
    if (idx < QUESTIONS.length - 1) setIdx(i => i + 1);
    else setPhase('lead');
  }, [idx]);
  const prev = useCallback(() => {
    if (idx > 0) setIdx(i => i - 1);
    else setPhase('intro');
  }, [idx]);

  const restart = () => {
    setAnswers(Array(QUESTIONS.length).fill(null));
    setOthers({});
    setLead({ name: '', email: '', company: '', role: '', size: '' });
    setIdx(0);
    setPhase('intro');
  };

  // progress by dimension
  const progress = useMemo(() => {
    const map = {};
    DIMENSIONS.forEach(d => { map[d.id] = { total: 0, done: 0 }; });
    QUESTIONS.forEach((q, i) => {
      map[q.dim].total++;
      if (answers[i] != null) map[q.dim].done++;
    });
    return DIMENSIONS.map(d => ({ ...d, ...map[d.id] }));
  }, [answers]);

  const currentDim = phase === 'quiz' ? QUESTIONS[idx].dim : null;

  return (
    <div className="ds-shell">
      <TopBar onExit={phase === 'quiz' ? () => { if (confirm('Exit the assessment? Your progress is saved.')) setPhase('intro'); } : null}/>
      {phase === 'quiz' && <ProgressBar segments={progress} currentDim={currentDim}/>}
      <main className="ds-stage">
        {phase === 'intro' && <Intro onBegin={() => { setIdx(answers.findIndex(a => a == null) === -1 ? 0 : Math.max(0, answers.findIndex(a => a == null))); setPhase('quiz'); }} hasProgress={answers.some(a => a != null)} onRestart={restart}/>}
        {phase === 'quiz' && (
          <Question
            key={idx}
            q={QUESTIONS[idx]}
            idx={idx}
            total={QUESTIONS.length}
            answer={answers[idx]}
            otherText={others[idx] || ''}
            onOtherText={(v) => setOthers(o => ({ ...o, [idx]: v }))}
            onAnswer={(v) => setAnswer(idx, v)}
            onNext={next}
            onPrev={prev}
          />
        )}
        {phase === 'done' && <Result answers={answers} onRestart={restart}/>}
        {phase === 'lead' && (
          <LeadCapture
            lead={lead}
            onChange={(patch) => setLead(l => ({ ...l, ...patch }))}
            onBack={() => { setIdx(QUESTIONS.length - 1); setPhase('quiz'); }}
            onSubmit={() => setPhase('done')}
          />
        )}
      </main>
      <Footer/>
    </div>
  );
}

/* ------------------ TopBar ------------------ */
function TopBar({ onExit }) {
  return (
    <header className="ds-top">
      <a href="Vectory Site.html" className="ds-top__logo">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M4 20 L20 4 M14 4 L20 4 L20 10"/></svg>
        <b>Vectory</b>
      </a>
      <div className="ds-top__meta">
        <span className="dot"/>
        <span>Direction Score · v1.0</span>
        {onExit && <button className="ds-exit" onClick={onExit}>Exit</button>}
      </div>
    </header>
  );
}

/* ------------------ Progress ------------------ */
function ProgressBar({ segments, currentDim }) {
  return (
    <div className="ds-progress">
      {segments.map(s => {
        const active = s.id === currentDim;
        const pct = s.total ? s.done / s.total : 0;
        return (
          <div key={s.id} className={'ds-progress__seg' + (active ? ' ds-progress__seg--active' : '')}>
            <div className="ds-progress__bar">
              <div className={'ds-progress__fill' + (active ? ' ds-progress__fill--active' : '')} style={{ transform: `scaleX(${pct})` }}/>
            </div>
            <div className="ds-progress__label">
              <b>{s.name}</b>
              <span>{s.done}/{s.total}</span>
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* ------------------ Intro ------------------ */
function Intro({ onBegin, hasProgress, onRestart }) {
  return (
    <div className="ds-intro">
      <div>
        <div className="ds-intro__eyebrow">Direction Score · 4 minutes</div>
        <h1>How much <em>direction</em> does your AI effort actually have?</h1>
        <p className="ds-intro__lead">
          Twelve questions. No email wall. No lead magnet. You get a number, a read, and three specific moves — calibrated against the operators we\'ve worked with at the $20M–$250M band.
        </p>
        <div className="ds-intro__meta">
          <div className="row"><span className="k">Length</span><span className="v">12 questions · ~4 minutes</span></div>
          <div className="row"><span className="k">Output</span><span className="v">Score 0–100, dimensional read, top-3 moves</span></div>
          <div className="row"><span className="k">Privacy</span><span className="v">Runs in your browser. Nothing sent.</span></div>
          <div className="row"><span className="k">Honesty</span><span className="v">Scores are blunt. That\'s the point.</span></div>
        </div>
        <div style={{display:'flex', gap:14, flexWrap:'wrap', alignItems:'center'}}>
          <button className="vt-btn vt-btn--primary" onClick={onBegin}>
            {hasProgress ? 'Resume assessment' : 'Begin assessment'} <span className="ar">→</span>
          </button>
          {hasProgress && <button className="vt-btn vt-btn--ghost" onClick={onRestart}>Start over</button>}
        </div>
      </div>
      <div className="ds-intro__motion">
        <MotionCanvas type="harmonograph" speed={0.7} density={0.9} background="ink" interactive={false}/>
        <div className="corner corner--tl"/>
        <div className="corner corner--br"/>
        <div className="label">ƒ(x) · Direction</div>
      </div>
    </div>
  );
}

/* ------------------ Question ------------------ */
function Question({ q, idx, total, answer, otherText, onOtherText, onAnswer, onNext, onPrev }) {
  const autoAdvance = useRef(null);
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Enter' && answer != null) { e.preventDefault(); onNext(); return; }
      if (e.key === 'Backspace' && e.metaKey) { e.preventDefault(); onPrev(); return; }
      if (e.key === 'ArrowLeft') { e.preventDefault(); onPrev(); return; }
      if (e.key === 'ArrowRight' && answer != null) { e.preventDefault(); onNext(); return; }
      // number keys for scale/choice
      const n = parseInt(e.key, 10);
      if (!isNaN(n) && n >= 1) {
        if (q.type === 'scale' && n >= 1 && n <= 5) { onAnswer(n - 1); }
        else if (q.type === 'choice' && n >= 1 && n <= q.choices.length) { onAnswer(n - 1); }
        else if (q.type === 'binary' && (n === 1 || n === 2)) { onAnswer(n - 1); }
      }
      if (q.type === 'binary') {
        if (e.key.toLowerCase() === 'y') onAnswer(0);
        if (e.key.toLowerCase() === 'n') onAnswer(1);
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [q, answer, onAnswer, onNext, onPrev]);

  const handleAnswer = (v) => {
    onAnswer(v);
    // auto-advance after a short delay for scale/binary and non-"other" choice
    const isOther = q.type === 'choice' && q.choices[v]?.other;
    if (q.type !== 'slider' && !isOther) {
      clearTimeout(autoAdvance.current);
      autoAdvance.current = setTimeout(() => onNext(), 420);
    }
  };

  return (
    <div className="ds-q">
      <div className="ds-q__num">
        <b>Q{String(idx+1).padStart(2,'0')}</b>
        <span>/</span>
        <span>{String(total).padStart(2,'0')}</span>
        <span style={{marginLeft:'auto'}}>· {DIMENSIONS.find(d => d.id === q.dim).name}</span>
      </div>
      <h2 className="ds-q__prompt" dangerouslySetInnerHTML={{ __html: q.prompt }}/>
      {q.sub && <p className="ds-q__sub">{q.sub}</p>}
      {q.type === 'scale' && <ScaleInput value={answer} onChange={handleAnswer}/>}
      {q.type === 'choice' && <ChoiceInput choices={q.choices} value={answer} otherText={otherText} onOtherText={onOtherText} onChange={handleAnswer}/>}
      {q.type === 'binary' && <BinaryInput choices={q.choices} value={answer} onChange={handleAnswer}/>}
      {q.type === 'slider' && <SliderInput q={q} value={answer} onChange={onAnswer}/>}
      <div className="ds-nav">
        <button className="vt-btn vt-btn--ghost" onClick={onPrev}>
          <span className="ar" style={{transform:'rotate(180deg)'}}>→</span> Back
        </button>
        <div className="ds-nav__hint">
          {q.type === 'slider'
            ? <>Drag to answer · <kbd>Enter</kbd> to continue</>
            : q.type === 'binary'
              ? <><kbd>Y</kbd> / <kbd>N</kbd> to answer · <kbd>Enter</kbd> to continue</>
              : <><kbd>1</kbd>–<kbd>{q.type === 'scale' ? '5' : q.choices.length}</kbd> to answer · <kbd>Enter</kbd> to continue</>}
        </div>
        <button className="vt-btn vt-btn--primary" onClick={onNext} disabled={answer == null} style={{opacity: answer == null ? 0.35 : 1, pointerEvents: answer == null ? 'none' : 'auto'}}>
          {idx === total - 1 ? 'See score' : 'Next'} <span className="ar">→</span>
        </button>
      </div>
    </div>
  );
}

/* ------------------ Inputs ------------------ */
function ScaleInput({ value, onChange }) {
  return (
    <>
      <div className="ds-scale__legend">
        <span>← Strongly disagree</span>
        <span>Strongly agree →</span>
      </div>
      <div className="ds-scale">
        {SCALE_LABELS.map((label, i) => (
          <button key={i} className={'ds-scale__btn' + (value === i ? ' is-selected' : '')} onClick={() => onChange(i)}>
            <span className="n">0{i+1}</span>
            <span className="label">{label}</span>
          </button>
        ))}
      </div>
    </>
  );
}

function ChoiceInput({ choices, value, onChange, otherText, onOtherText }) {
  return (
    <div className="ds-choices">
      {choices.map((c, i) => (
        <button key={i} className={'ds-choice' + (value === i ? ' is-selected' : '')} onClick={() => onChange(i)}>
          <span className="key">{i+1}</span>
          {c.other ? (
            <span className="ds-choice__label-row">
              <span>{c.label}</span>
              <span className="ds-choice__sep">·</span>
              <input
                type="text"
                className="ds-choice__other-inline"
                placeholder="tell us how"
                value={value === i ? (otherText || '') : ''}
                onClick={(e) => { e.stopPropagation(); if (value !== i) onChange(i); }}
                onChange={(e) => { if (value !== i) onChange(i); onOtherText(e.target.value); }}
                onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); e.currentTarget.blur(); } }}
              />
            </span>
          ) : (
            <span>{c.label}</span>
          )}
          <span className="ar">→</span>
        </button>
      ))}
    </div>
  );
}

function BinaryInput({ choices, value, onChange }) {
  return (
    <div className="ds-binary">
      {choices.map((c, i) => (
        <button key={i} className={'ds-binary__btn' + (value === i ? ' is-selected' : '')} onClick={() => onChange(i)}>
          <span className="label">{c.label}</span>
        </button>
      ))}
    </div>
  );
}

function SliderInput({ q, value, onChange }) {
  const v = value == null ? q.default : value;
  const pct = ((v - q.min) / (q.max - q.min)) * 100;
  return (
    <div className="ds-slider">
      <div className="ds-slider__track">
        <div className="ds-slider__fill" style={{width: pct + '%'}}/>
        <div className="ds-slider__thumb" style={{left: pct + '%'}}>
          <div className="ds-slider__value">{v}<span className="unit">{q.unit}</span></div>
        </div>
        <input type="range" min={q.min} max={q.max} step={q.step} value={v} onChange={(e) => onChange(parseInt(e.target.value, 10))}/>
      </div>
      <div className="ds-slider__legend">
        <span>{q.min} {q.unit}</span>
        <span>{q.max}+ {q.unit}</span>
      </div>
    </div>
  );
}

/* ------------------ Result ------------------ */
function Result({ answers, onRestart }) {
  const result = useMemo(() => scoreAnswers(answers), [answers]);
  const band = bandFor(result.overall);
  const recs = recommendationsFor(result);
  return (
    <div className="ds-result">
      <div>
        <div className="ds-result__eyebrow">Your Direction Score</div>
        <h1>You are <em>{band.label.toLowerCase()}</em>.</h1>
        <div className="ds-result__score">
          <span className="big"><AnimatedNumber to={result.overall}/></span>
          <span className="of">/ 100</span>
          <span className="band">
            Band
            <b>{band.label}</b>
          </span>
        </div>
        <p className="ds-result__read" dangerouslySetInnerHTML={{ __html: band.read }}/>
        <div className="ds-result__recs">
          <h3>Top moves, ranked</h3>
          <ol>
            {recs.map((r, i) => (
              <li key={i}>
                <div>
                  <strong>{r.title}</strong>
                  <span>{r.body}</span>
                </div>
              </li>
            ))}
          </ol>
        </div>
        <div className="ds-result__actions">
          <a className="vt-btn vt-btn--primary" href="Vectory Site.html#about">Apply to work with Vectory <span className="ar">→</span></a>
          <button className="vt-btn vt-btn--ghost" onClick={onRestart}>Retake assessment</button>
        </div>
      </div>
      <aside className="ds-radar">
        <div className="ds-radar__frame">
          <RadarChart dims={result.dims}/>
          <div className="corner corner--tl"/>
          <div className="corner corner--br"/>
          <div className="tag">ƒ · Dimensional read</div>
        </div>
        <div className="ds-radar__breakdown">
          {result.dims.map(d => (
            <div key={d.id} className="cell">
              <div className="k">{d.name}</div>
              <div className="v">{d.pct}<span className="max">/100</span></div>
            </div>
          ))}
        </div>
      </aside>
    </div>
  );
}

function AnimatedNumber({ to }) {
  const [n, setN] = useState(to);
  useEffect(() => {
    setN(0);
    let raf = 0; const start = performance.now(); const D = 1200;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / D);
      const eased = 1 - Math.pow(1 - t, 3);
      setN(Math.round(to * eased));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    const fallback = setTimeout(() => setN(to), 1500);
    return () => { cancelAnimationFrame(raf); clearTimeout(fallback); };
  }, [to]);
  return <>{n}</>;
}

function RadarChart({ dims }) {
  // Animated vector polygon across 4 axes (Focus, Data, Team, Momentum).
  const [progress, setProgress] = useState(1);
  useEffect(() => {
    setProgress(0);
    let raf = 0; const start = performance.now(); const D = 1400;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / D);
      setProgress(1 - Math.pow(1 - t, 3));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    const fallback = setTimeout(() => setProgress(1), 1800);
    return () => { cancelAnimationFrame(raf); clearTimeout(fallback); };
  }, [dims]);

  const size = 400, cx = size/2, cy = size/2, R = size * 0.38;
  const N = dims.length;
  const angle = (i) => (-Math.PI/2) + (i / N) * Math.PI*2;
  const point = (i, r) => [cx + Math.cos(angle(i))*r, cy + Math.sin(angle(i))*r];

  // grid rings
  const rings = [0.25, 0.5, 0.75, 1.0];
  const gridPoints = (r) => Array.from({length:N}, (_,i) => point(i, R*r).join(',')).join(' ');

  const dataPoly = dims.map((d,i) => point(i, R * (d.score/4) * progress).join(',')).join(' ');

  return (
    <svg viewBox={`0 0 ${size} ${size}`}>
      <defs>
        <linearGradient id="radarFill" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#D9541A" stopOpacity="0.4"/>
          <stop offset="100%" stopColor="#D9541A" stopOpacity="0.1"/>
        </linearGradient>
      </defs>
      {/* grid rings */}
      {rings.map((r,i) => (
        <polygon key={i} points={gridPoints(r)} fill="none" stroke="rgba(250,250,247,0.12)" strokeWidth="1"/>
      ))}
      {/* axes */}
      {dims.map((_,i) => {
        const p = point(i, R);
        return <line key={i} x1={cx} y1={cy} x2={p[0]} y2={p[1]} stroke="rgba(250,250,247,0.14)" strokeWidth="1"/>;
      })}
      {/* data */}
      <polygon points={dataPoly} fill="url(#radarFill)" stroke="#D9541A" strokeWidth="1.5"/>
      {/* points */}
      {dims.map((d,i) => {
        const p = point(i, R * (d.score/4) * progress);
        return <circle key={i} cx={p[0]} cy={p[1]} r="4" fill="#D9541A"/>;
      })}
      {/* center dot */}
      <circle cx={cx} cy={cy} r="2" fill="rgba(250,250,247,0.4)"/>
      {/* labels */}
      {dims.map((d,i) => {
        const p = point(i, R + 36);
        const anchor = Math.abs(p[0] - cx) < 10 ? 'middle' : (p[0] > cx ? 'start' : 'end');
        return (
          <g key={i}>
            <text x={p[0]} y={p[1] - 4} textAnchor={anchor} fill="#FAFAF7" fontFamily="JetBrains Mono" fontSize="11" letterSpacing="1.4" style={{textTransform:'uppercase'}}>{d.name.toUpperCase()}</text>
            <text x={p[0]} y={p[1] + 14} textAnchor={anchor} fill="#D9541A" fontFamily="Instrument Serif" fontSize="22">{Math.round(d.pct*progress)}</text>
          </g>
        );
      })}
    </svg>
  );
}

/* ------------------ Footer ------------------ */
function Footer() {
  return (
    <footer className="ds-foot">
      <span>Vectory · Direction for AI</span>
      <span>© 2026</span>
    </footer>
  );
}

window.DirectionScore = DirectionScore;
