// qodeva-app.jsx — self-contained QODEVA AI animated logo reveal.
// Exports window.QodevaApp. Includes a compact timeline engine + the scene.
// NOTE: `React` is injected by the runtime — do NOT re-declare it.

// ── helpers ───────────────────────────────────────────────────
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
const Easing = {
  linear: (t) => t,
  easeOutCubic: (t) => (--t) * t * t + 1,
  easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
  easeInOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
  easeOutBack: (t) => { const c1 = 1.70158, c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); },
};
function interpolate(input, output, ease = Easing.linear) {
  return (t) => {
    if (t <= input[0]) return output[0];
    if (t >= input[input.length - 1]) return output[output.length - 1];
    for (let i = 0; i < input.length - 1; i++) {
      if (t >= input[i] && t <= input[i + 1]) {
        const span = input[i + 1] - input[i];
        const local = span === 0 ? 0 : (t - input[i]) / span;
        const easeFn = Array.isArray(ease) ? (ease[i] || Easing.linear) : ease;
        return output[i] + (output[i + 1] - output[i]) * easeFn(local);
      }
    }
    return output[output.length - 1];
  };
}
function animate({ from = 0, to = 1, start = 0, end = 1, ease = Easing.easeInOutCubic }) {
  return (t) => {
    if (t <= start) return from;
    if (t >= end) return to;
    return from + (to - from) * ease((t - start) / (end - start));
  };
}

// ── brand palette ───────────────────────────────────────────────
const NAVY_TOP = '#22357f';
const BLUE = '#2f6bf0';
const BLUE_BRIGHT = '#4d8bff';
const SQ_DARK = '#2a3c7a';

// mark geometry (600 viewBox), centred 300,300
const CX = 300, CY = 300, R = 182;
const d2r = (d) => (d * Math.PI) / 180;
const pt = (deg) => ({ x: CX + R * Math.cos(d2r(deg)), y: CY + R * Math.sin(d2r(deg)) });

// ── the scene ───────────────────────────────────────────────────
function LogoRevealScene({ time: t }) {
  const pathRef = React.useRef(null);
  const tailRef = React.useRef(null);
  const [len, setLen] = React.useState(0);
  const [tlen, setTlen] = React.useState(0);
  React.useEffect(() => {
    if (pathRef.current) setLen(pathRef.current.getTotalLength());
    if (tailRef.current) setTlen(tailRef.current.getTotalLength());
  }, []);

  const arcP = interpolate([0.55, 2.55], [0, 1], Easing.easeInOutCubic)(t);
  const bigSq = animate({ from: 0, to: 1, start: 2.35, end: 3.0, ease: Easing.easeOutBack })(t);
  const smSq = animate({ from: 0, to: 1, start: 2.6, end: 3.25, ease: Easing.easeOutBack })(t);
  const tailP = interpolate([2.9, 3.65], [0, 1], Easing.easeOutCubic)(t);
  const glint = animate({ from: -0.35, to: 1.35, start: 3.55, end: 4.5, ease: Easing.easeInOutSine })(t);
  const glintOn = t > 3.55 && t < 4.5;
  const settle = interpolate([2.55, 2.95, 4.0], [1, 1.05, 1], [Easing.easeOutCubic, Easing.easeInOutCubic])(t);
  const aiP = animate({ from: 0, to: 1, start: 4.75, end: 5.4, ease: Easing.easeOutBack })(t);
  const tagP = animate({ from: 0, to: 1, start: 5.35, end: 6.25, ease: Easing.easeOutCubic })(t);
  const outF = animate({ from: 0, to: 1, start: 8.45, end: 9.0, ease: Easing.easeInOutCubic })(t);
  const bgIn = animate({ from: 0, to: 1, start: 0.0, end: 0.7, ease: Easing.easeOutCubic })(t);
  const seed = animate({ from: 0, to: 1, start: 0.05, end: 0.5, ease: Easing.easeOutBack })(t);
  const seedOut = animate({ from: 0, to: 1, start: 0.55, end: 0.95, ease: Easing.easeOutCubic })(t);
  const breathe = t > 4.0 ? Math.sin((t - 4.0) * 1.0) * 4.5 : 0;
  const shimmer = (Math.sin(t * 2.4) + 1) / 2;
  const globalA = 1 - outF;

  let head = null;
  if (len && arcP > 0.03 && arcP < 0.992 && pathRef.current) {
    const p = pathRef.current.getPointAtLength(arcP * len);
    head = { x: p.x, y: p.y };
  }

  const ur = pt(312), lr = pt(72);
  const arcPath = `M ${ur.x.toFixed(1)} ${ur.y.toFixed(1)} A ${R} ${R} 0 1 0 ${lr.x.toFixed(1)} ${lr.y.toFixed(1)}`;
  const tailA = { x: 404, y: 402 }, tailB = { x: 480, y: 476 };
  const letters = ['Q', 'O', 'D', 'E', 'V', 'A'];

  return (
    <div data-screen-label={`Logo reveal · ${Math.floor(t)}s`} style={{
      position: 'absolute', inset: 0, overflow: 'hidden',
      background: 'radial-gradient(120% 120% at 50% 38%, #0d1c49 0%, #081231 46%, #04081c 100%)',
    }}>
      {/* ambient glow */}
      <div style={{
        position: 'absolute', left: '50%', top: '42%', width: 1200, height: 1200,
        transform: 'translate(-50%,-50%)',
        background: `radial-gradient(circle, rgba(47,107,240,${0.16 * bgIn + 0.06 * shimmer * (arcP > 0.9 ? 1 : 0)}) 0%, rgba(47,107,240,0) 58%)`,
        pointerEvents: 'none',
      }} />

      {/* particles */}
      {Array.from({ length: 22 }).map((_, i) => {
        const sx = (i * 137.5) % 100;
        const sy = (i * 71.3) % 100;
        const drift = Math.sin(t * 0.35 + i) * 14;
        const tw = (Math.sin(t * 1.3 + i * 1.7) + 1) / 2;
        const size = 1.5 + (i % 3);
        return (
          <div key={i} style={{
            position: 'absolute', left: `${sx}%`, top: `${(sy + drift + 100) % 100}%`,
            width: size, height: size, borderRadius: '50%',
            background: i % 4 === 0 ? BLUE_BRIGHT : '#5e79c9',
            opacity: (0.1 + tw * 0.35) * bgIn, filter: 'blur(0.4px)',
          }} />
        );
      })}

      {/* composition */}
      <div style={{
        position: 'absolute', inset: 0,
        display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center', gap: 4,
        opacity: globalA, transform: `translateY(${breathe}px)`,
      }}>
        <div style={{ position: 'relative', width: 560, height: 560, marginTop: -28 }}>
          {seedOut < 1 ? (
            <div style={{
              position: 'absolute', left: '50%', top: '50%',
              width: 26, height: 26, marginLeft: -13, marginTop: -13,
              borderRadius: '50%', background: BLUE_BRIGHT,
              transform: `scale(${0.2 + seed * 1.0})`,
              opacity: (1 - seedOut) * seed,
              boxShadow: '0 0 40px 8px rgba(77,139,255,0.9)',
            }} />
          ) : null}

          <svg viewBox="0 0 600 600" width="560" height="560"
            style={{ position: 'absolute', inset: 0, overflow: 'visible', transform: `translateX(-20px) scale(${settle})`, transformOrigin: '50% 52%' }}>
            <defs>
              <linearGradient id="qgrad" x1="0.15" y1="0" x2="0.45" y2="1">
                <stop offset="0" stopColor={NAVY_TOP} />
                <stop offset="0.55" stopColor="#27448f" />
                <stop offset="1" stopColor={BLUE} />
              </linearGradient>
              <linearGradient id="tailgrad" x1="0" y1="0" x2="1" y2="1">
                <stop offset="0" stopColor="#2c5fd8" />
                <stop offset="1" stopColor={BLUE_BRIGHT} />
              </linearGradient>
              <filter id="soft" x="-40%" y="-40%" width="180%" height="180%">
                <feGaussianBlur stdDeviation="9" />
              </filter>
              <clipPath id="markclip">
                <path d={arcPath} fill="none" stroke="#000" strokeWidth="72" strokeLinecap="round" strokeLinejoin="round" />
                <rect x="358" y="274" width="57" height="57" rx="11" />
                <rect x="425" y="243" width="38" height="38" rx="7" />
                <line x1={tailA.x} y1={tailA.y} x2={tailB.x} y2={tailB.y} stroke="#000" strokeWidth="60" strokeLinecap="round" />
              </clipPath>
            </defs>

            <path d={arcPath} fill="none" stroke={BLUE} strokeWidth="72" strokeLinecap="round" strokeLinejoin="round"
              strokeDasharray={len || 1} strokeDashoffset={len ? len * (1 - arcP) : 1}
              opacity={0.35 * arcP} filter="url(#soft)" />

            <path ref={pathRef} d={arcPath} fill="none" stroke="url(#qgrad)" strokeWidth="70" strokeLinecap="round" strokeLinejoin="round"
              strokeDasharray={len || 1} strokeDashoffset={len ? len * (1 - arcP) : 1} />

            {head ? (
              <g>
                <circle cx={head.x} cy={head.y} r="30" fill={BLUE_BRIGHT} opacity="0.55" filter="url(#soft)" />
                <circle cx={head.x} cy={head.y} r="11" fill="#cfe0ff" />
              </g>
            ) : null}

            <line ref={tailRef} x1={tailA.x} y1={tailA.y} x2={tailB.x} y2={tailB.y}
              stroke="url(#tailgrad)" strokeWidth="58" strokeLinecap="round"
              strokeDasharray={tlen || 1} strokeDashoffset={tlen ? tlen * (1 - tailP) : 1} />

            <g transform={`translate(386 302) scale(${bigSq}) translate(-386 -302)`} opacity={bigSq > 0 ? 1 : 0}>
              <rect x="358" y="274" width="57" height="57" rx="11" fill={BLUE} />
            </g>
            <g transform={`translate(444 262) scale(${smSq}) translate(-444 -262)`} opacity={smSq > 0 ? 1 : 0}>
              <rect x="425" y="243" width="38" height="38" rx="7" fill={SQ_DARK} />
            </g>

            {glintOn ? (
              <g clipPath="url(#markclip)">
                <rect x={-120 + glint * 760} y="-60" width="120" height="720" transform="rotate(18 300 300)"
                  fill="#ffffff" opacity="0.45" filter="url(#soft)" />
              </g>
            ) : null}
          </svg>
        </div>

        {/* wordmark */}
        <div style={{
          display: 'flex', alignItems: 'baseline',
          fontFamily: 'Montserrat, system-ui, sans-serif',
          fontWeight: 600, fontSize: 118, letterSpacing: '0.04em', lineHeight: 1, marginTop: -8,
        }}>
          <span style={{ display: 'flex' }}>
            {letters.map((ch, i) => {
              const lp = animate({ from: 0, to: 1, start: 3.95 + i * 0.07, end: 4.42 + i * 0.07, ease: Easing.easeOutCubic })(t);
              return (
                <span key={i} style={{ color: '#eef3ff', opacity: lp, transform: `translateY(${(1 - lp) * 24}px)`, display: 'inline-block' }}>{ch}</span>
              );
            })}
          </span>
          <span style={{
            color: BLUE_BRIGHT, opacity: aiP, marginLeft: '0.16em',
            transform: `translateY(${(1 - aiP) * 10}px) scale(${0.7 + aiP * 0.3})`,
            transformOrigin: 'left bottom', display: 'inline-block',
            textShadow: `0 0 ${18 + shimmer * 16}px rgba(77,139,255,${0.45 + aiP * 0.25})`,
          }}>AI</span>
        </div>

        {/* tagline */}
        <div style={{
          fontFamily: 'Montserrat, system-ui, sans-serif',
          fontWeight: 500, fontSize: 22, letterSpacing: '0.26em',
          color: '#8aa0d8', textTransform: 'uppercase',
          marginTop: 22, paddingLeft: '0.26em',
          opacity: tagP, transform: `translateY(${(1 - tagP) * 10}px)`,
        }}>Let's Build the Future Together</div>
      </div>
    </div>
  );
}

// ── compact Stage (timeline + playback) ───────────────────────
// ZOOM enlarges the drawing inside the same box (1 = original / fit).
const ZOOM = 1;

function Stage({ width = 1920, height = 1080, duration = 8.4 }) {
  // Always start fresh at 0 so the reveal draws on every page load.
  const [time, setTime] = React.useState(0);
  const [scale, setScale] = React.useState(1);
  const wrapRef = React.useRef(null);
  const rafRef = React.useRef(null);
  const lastRef = React.useRef(null);

  React.useEffect(() => {
    const measure = () => {
      if (!wrapRef.current) return;
      const el = wrapRef.current;
      const s = Math.min(el.clientWidth / width, el.clientHeight / height);
      setScale(Math.max(0.05, s) * ZOOM);
    };
    measure();
    const ro = new ResizeObserver(measure);
    if (wrapRef.current) ro.observe(wrapRef.current);
    window.addEventListener('resize', measure);
    return () => { ro.disconnect(); window.removeEventListener('resize', measure); };
  }, [width, height]);

  React.useEffect(() => {
    // Draw once: advance to `duration`, then stop on the final frame.
    let done = false;
    const step = (ts) => {
      if (lastRef.current == null) lastRef.current = ts;
      const dt = (ts - lastRef.current) / 1000;
      lastRef.current = ts;
      setTime((t) => {
        const n = t + dt;
        if (n >= duration) { done = true; return duration; }
        return n;
      });
      if (!done) rafRef.current = requestAnimationFrame(step);
    };
    rafRef.current = requestAnimationFrame(step);
    return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); lastRef.current = null; };
  }, [duration]);

  return (
    <div ref={wrapRef} style={{
      position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
      alignItems: 'center', background: '#04081c',
    }}>
      <div style={{ flex: 1, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'hidden', minHeight: 0 }}>
        <div style={{
          width, height, position: 'relative', background: '#04081c',
          transform: `scale(${scale})`, transformOrigin: 'center', flexShrink: 0, overflow: 'hidden',
        }}>
          <LogoRevealScene time={time} />
        </div>
      </div>
    </div>
  );
}

function QodevaApp() {
  return <Stage width={1920} height={1080} duration={8.4} />;
}
window.QodevaApp = QodevaApp;
if (typeof module !== 'undefined') { module.exports = { QodevaApp }; }
