// gm-anim.jsx — Componentes animados: Remotion (React, frame-driven) e Hyperframes (HTML+GSAP).
// Preview roda ao vivo (loop + scrubber) e mostra o CÓDIGO real de cada framework.
// Exports: GMRemotion, GMHyperframes.

/* ── motor de preview (espelha interpolate/spring para renderizar igual) ──── */
function interp(frame, [i0, i1], [o0, o1], clamp = true) {
  let p = (i1 === i0) ? 1 : (frame - i0) / (i1 - i0);
  if (clamp) p = Math.max(0, Math.min(1, p));
  return o0 + (o1 - o0) * p;
}
function spring01(frame, fps, { delay = 0, damping = 100 } = {}) {
  const t = Math.max(0, (frame - delay) / fps);
  const w = 11, zeta = Math.min(1, damping / 100);
  let v;
  if (zeta >= 1) v = 1 - Math.exp(-w * t) * (1 + w * t);
  else { const wd = w * Math.sqrt(1 - zeta * zeta); v = 1 - Math.exp(-zeta * w * t) * Math.cos(wd * t); }
  return Math.min(v, 1.2);
}
const easeOut = (p) => 1 - Math.pow(1 - p, 3);

function Player({ durationInFrames, fps = 30, render, ratio = "16:9", dark = true }) {
  const [frame, setFrame] = React.useState(0);
  const [playing, setPlaying] = React.useState(true);
  const boxRef = React.useRef(null);
  const visible = React.useRef(true);
  React.useEffect(() => {
    const el = boxRef.current; if (!el || !("IntersectionObserver" in window)) return;
    const io = new IntersectionObserver(([e]) => { visible.current = e.isIntersecting; }, { threshold: 0.15 });
    io.observe(el); return () => io.disconnect();
  }, []);
  React.useEffect(() => {
    if (!playing) return;
    let on = true, raf, last = null, acc = 0;
    const step = 1000 / fps;
    const tick = (ts) => {
      if (!on) return;
      if (visible.current) {
        if (last == null) last = ts;
        acc += ts - last; last = ts;
        if (acc >= step) { const adv = Math.floor(acc / step); acc -= adv * step; setFrame((f) => { const nf = f + adv; return nf >= durationInFrames ? 0 : nf; }); }
      } else { last = ts; }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => { on = false; cancelAnimationFrame(raf); };
  }, [playing, durationInFrames, fps]);
  const f = frame;
  return (
    <div ref={boxRef}>
      <Frame ratio={ratio} dark={dark}>
        <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", padding: "7%" }}>{render(f, fps)}</div>
      </Frame>
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginTop: 12 }}>
        <button onClick={() => { setPlaying((p) => !p); }} style={{ cursor: "pointer", width: 34, height: 34, borderRadius: "50%", border: `1px solid ${T.line2}`, background: T.card, color: T.ink, fontSize: 13, flexShrink: 0 }}>{playing ? "❙❙" : "▶"}</button>
        <button onClick={() => { setFrame(0); }} style={{ cursor: "pointer", height: 34, borderRadius: 8, border: `1px solid ${T.line2}`, background: T.card, color: T.sub, fontFamily: F.mono, fontSize: 11, padding: "0 10px", flexShrink: 0 }}>↻</button>
        <input type="range" min={0} max={durationInFrames} value={frame} onChange={(e) => { setPlaying(false); setFrame(Number(e.target.value)); }} style={{ flex: 1, accentColor: T.coral }} />
        <span style={{ fontFamily: F.mono, fontSize: 11, color: T.sub, width: 66, textAlign: "right", flexShrink: 0 }}>{f}/{durationInFrames}f</span>
      </div>
    </div>
  );
}

/* ── renderers (visual idêntico ao que o código produz) ───────────────────── */
const DATA_BARS = [38, 52, 47, 71, 60];
const R = {
  countup: (f, fps) => {
    const s = spring01(f, fps, { damping: 100 });
    const val = Math.floor(interp(s, [0, 1], [0, 1200000]));
    return (
      <div style={{ textAlign: "center", fontFamily: F.display }}>
        <div style={{ fontSize: "clamp(40px,9vw,84px)", fontWeight: 800, color: T.coral, letterSpacing: -3, lineHeight: 0.9 }}>{val.toLocaleString("pt-BR")}</div>
        <div style={{ fontFamily: F.mono, fontSize: 12, letterSpacing: 2, color: T.dim, marginTop: 12 }}>EVENTOS / DIA</div>
      </div>
    );
  },
  bars: (f, fps) => {
    const max = Math.max(...DATA_BARS);
    return (
      <div style={{ display: "flex", gap: "3%", alignItems: "flex-end", height: "78%", width: "70%" }}>
        {DATA_BARS.map((v, i) => {
          const g = spring01(f, fps, { delay: i * 5, damping: 200 });
          return <div key={i} style={{ flex: 1, height: `${g * (v / max) * 100}%`, borderRadius: 6, background: i === 3 ? T.coral : "#6E6253" }}></div>;
        })}
      </div>
    );
  },
  title: (f, fps) => (
    <div style={{ fontFamily: F.display, fontWeight: 800, fontSize: "clamp(28px,6vw,60px)", letterSpacing: -2, lineHeight: 1, color: T.dsand, textAlign: "center" }}>
      {["Pipeline", "RAG", "do", "zero"].map((w, i) => {
        const s = spring01(f, fps, { delay: i * 5, damping: 200 });
        return <span key={i} style={{ display: "inline-block", marginRight: 14, opacity: Math.min(1, s), transform: `translateY(${(1 - Math.min(1, s)) * 38}px)`, color: w === "RAG" ? T.dterra : T.dsand }}>{w}</span>;
      })}
    </div>
  ),
  shape: (f, fps) => {
    const s = spring01(f, fps, { damping: 13 });
    const rot = interp(f, [0, 60], [0, 225]);
    return (
      <div style={{ position: "relative", width: 130, height: 130, transform: `scale(${Math.max(0, s)})` }}>
        <div style={{ position: "absolute", inset: 0, transform: `rotate(${rot + 45}deg)`, background: T.coral, borderRadius: 14 }}></div>
        <div style={{ position: "absolute", inset: "32%", transform: `rotate(${-rot}deg)`, background: T.dcyan, borderRadius: "50%" }}></div>
      </div>
    );
  },
  line: (f, fps) => {
    const pts = [[16, 150], [70, 120], [124, 128], [178, 80], [232, 96], [286, 40]];
    const path = pts.map((p, i) => (i ? "L" : "M") + p[0] + " " + p[1]).join(" ");
    const total = 360, p = easeOut(interp(f, [0, 2.0 * fps], [0, 1]));
    const lastVisible = Math.min(pts.length - 1, Math.floor(p * (pts.length - 1)));
    return (
      <svg viewBox="0 0 302 180" width="92%" style={{ overflow: "visible" }}>
        <g stroke={T.dline} strokeWidth="1">{[60, 100, 140].map((y) => <line key={y} x1="10" y1={y} x2="296" y2={y} />)}</g>
        <path d={path} fill="none" stroke={T.coral} strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" pathLength="1" style={{ strokeDasharray: 1, strokeDashoffset: 1 - p }} />
        <circle cx={pts[lastVisible][0]} cy={pts[lastVisible][1]} r="5" fill={T.coral} opacity={p > 0.05 ? 1 : 0} />
      </svg>
    );
  },
  donut: (f, fps) => {
    const C = 2 * Math.PI * 46, s = spring01(f, fps, { damping: 90 });
    const off = C - C * 0.42 * Math.min(1, s);
    return (
      <div style={{ position: "relative", display: "flex", alignItems: "center", justifyContent: "center" }}>
        <svg viewBox="0 0 120 120" width="56%">
          <circle cx="60" cy="60" r="46" fill="none" stroke={T.dsurface} strokeWidth="16" />
          <circle cx="60" cy="60" r="46" fill="none" stroke={T.coral} strokeWidth="16" strokeLinecap="round" transform="rotate(-90 60 60)" strokeDasharray={C} strokeDashoffset={off} />
        </svg>
        <div style={{ position: "absolute", fontFamily: F.display, fontWeight: 800, fontSize: 30, color: T.dsand }}>{Math.round(Math.min(1, s) * 42)}%</div>
      </div>
    );
  },
  lower: (f, fps) => {
    const p = easeOut(interp(f, [6, 6 + 0.6 * fps], [0, 1]));
    return (
      <div style={{ alignSelf: "flex-end", marginRight: "auto", display: "flex", alignItems: "stretch", opacity: p, transform: `translateX(${(1 - p) * -26}px)` }}>
        <div style={{ width: 5, background: T.coral, borderRadius: 3 }}></div>
        <div style={{ background: "rgba(36,27,22,0.92)", padding: "10px 16px", borderRadius: "0 8px 8px 0" }}>
          <div style={{ fontFamily: F.display, fontWeight: 700, fontSize: 18, color: T.dsand, whiteSpace: "nowrap" }}>Nicolas Lima</div>
          <div style={{ fontFamily: F.mono, fontSize: 11, letterSpacing: 1, color: T.dcyan }}>AI ENGINEER</div>
        </div>
      </div>
    );
  },
};

/* ── código real ──────────────────────────────────────────────────────────── */
const easeInOut = (p) => (p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2);
Object.assign(R, {
  typewriter: (f) => {
    const txt = "Engenharia de dados.";
    const n = Math.floor(interp(f, [6, 6 + txt.length * 2], [0, txt.length]));
    const caret = Math.floor(f / 15) % 2 === 0;
    return <div style={{ fontFamily: F.display, fontWeight: 800, fontSize: "clamp(24px,5vw,46px)", color: T.dsand, letterSpacing: -1, width: "100%", textAlign: "left" }}>{txt.slice(0, n)}<span style={{ color: T.coral, opacity: caret ? 1 : 0 }}>▌</span></div>;
  },
  wordswap: (f, fps) => {
    const W = ["Software", "Dados", "IA", "Soluções"], per = 22;
    const idx = Math.floor(f / per) % W.length, local = f % per;
    const enter = spring01(local, fps, { damping: 200 }), exit = interp(local, [per - 8, per], [1, 0]);
    return <div style={{ fontFamily: F.display, fontWeight: 800, fontSize: "clamp(26px,5.5vw,50px)", color: T.dsand, letterSpacing: -1 }}>Eu faço <span style={{ color: T.coral, display: "inline-block", opacity: Math.min(enter, exit), transform: `translateY(${(1 - Math.min(1, enter)) * 30}px)` }}>{W[idx]}</span></div>;
  },
  ring: (f, fps) => {
    const r = 70, C = 2 * Math.PI * r, s = spring01(f, fps, { damping: 90 }), pct = Math.min(1, s) * 78;
    return <svg width="46%" viewBox="0 0 180 180"><circle cx="90" cy="90" r={r} fill="none" stroke={T.dsurface} strokeWidth="14" /><circle cx="90" cy="90" r={r} fill="none" stroke={T.coral} strokeWidth="14" strokeLinecap="round" transform="rotate(-90 90 90)" strokeDasharray={C} strokeDashoffset={C * (1 - pct / 100)} /><text x="90" y="104" textAnchor="middle" fontFamily={F.display} fontWeight="800" fontSize="40" fill={T.dsand}>{Math.round(pct)}%</text></svg>;
  },
  logo: (f, fps) => {
    const n = spring01(f, fps, { damping: 14 }), i = spring01(f, fps, { delay: 8, damping: 14 });
    return <div style={{ fontFamily: F.math, fontStyle: "italic", fontSize: "clamp(70px,16vw,130px)", color: T.dsand, transform: `scale(${Math.max(0, n)})`, lineHeight: 1 }}>n<span style={{ fontSize: "0.58em", verticalAlign: "0.62em", color: T.coral, display: "inline-block", opacity: Math.min(1, i), transform: `translateY(${(1 - Math.min(1, i)) * -24}px)` }}>i</span></div>;
  },
  highlight: (f, fps) => {
    const p = easeOut(interp(f, [10, 10 + 0.7 * fps], [0, 1]));
    return <div style={{ fontFamily: F.display, fontWeight: 800, fontSize: "clamp(26px,5.5vw,48px)", color: T.dsand, letterSpacing: -1 }}>de dados a <span style={{ position: "relative", display: "inline-block" }}><span style={{ position: "absolute", left: -4, right: -4, bottom: 5, height: "40%", background: T.coral, opacity: 0.5, transform: `scaleX(${p})`, transformOrigin: "left", borderRadius: 3 }}></span>decisões</span></div>;
  },
  stagger: (f, fps) => {
    const cols = [T.coral, T.amber, T.dcyan, T.olive, T.terra, T.teal];
    return <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, width: "82%" }}>{cols.map((c, i) => { const s = Math.min(1, spring01(f, fps, { delay: i * 4, damping: 200 })); return <div key={i} style={{ height: 52, borderRadius: 10, background: c, opacity: s, transform: `translateY(${(1 - s) * 22}px)` }}></div>; })}</div>;
  },
  marquee: (f) => {
    const tags = ["python", "rag", "llmops", "spark", "dbt", "airflow", "vetores", "etl"];
    const shift = -((f * 3) % 320);
    const Row = (k) => <div key={k} style={{ display: "flex", gap: 12 }}>{tags.map((t, i) => <span key={i} style={{ fontFamily: F.mono, fontSize: 13, letterSpacing: 1, textTransform: "uppercase", color: T.dsand, border: `1px solid ${T.dline}`, padding: "7px 13px", borderRadius: 20, whiteSpace: "nowrap" }}>{t}</span>)}</div>;
    return <div style={{ width: "100%", overflow: "hidden" }}><div style={{ display: "flex", gap: 12, transform: `translateX(${shift}px)`, width: "max-content" }}>{Row(0)}{Row(1)}{Row(2)}</div></div>;
  },
  motionpath: (f, fps) => {
    const P = [[16, 160], [90, 20], [210, 190], [286, 50]];
    const t = easeInOut(interp(f, [0, 2.4 * fps], [0, 1])), u = 1 - t;
    const x = u * u * u * P[0][0] + 3 * u * u * t * P[1][0] + 3 * u * t * t * P[2][0] + t * t * t * P[3][0];
    const y = u * u * u * P[0][1] + 3 * u * u * t * P[1][1] + 3 * u * t * t * P[2][1] + t * t * t * P[3][1];
    return <svg viewBox="0 0 302 210" width="92%" style={{ overflow: "visible" }}><path d="M16 160 C 90 20 210 190 286 50" fill="none" stroke={T.dline} strokeWidth="2.5" strokeDasharray="5 5" /><circle cx={P[0][0]} cy={P[0][1]} r="5" fill={T.dcyan} /><circle cx={P[3][0]} cy={P[3][1]} r="5" fill={T.dcyan} /><circle cx={x} cy={y} r="9" fill={T.coral} /></svg>;
  },
});
const CODE_R = {
  countup: `import {useCurrentFrame, useVideoConfig, spring, interpolate} from 'remotion';

export const CountUp: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  const s = spring({frame, fps, config: {damping: 100}});
  const value = Math.floor(interpolate(s, [0, 1], [0, 1_200_000]));
  return (
    <div style={{fontFamily: 'Bricolage Grotesque', textAlign: 'center'}}>
      <div style={{fontSize: 84, fontWeight: 800, color: '#E8553A', letterSpacing: -3}}>
        {value.toLocaleString('pt-BR')}
      </div>
      <div style={{fontFamily: 'IBM Plex Mono', color: '#9C8674', letterSpacing: 2}}>
        EVENTOS / DIA
      </div>
    </div>
  );
};`,
  bars: `import {useCurrentFrame, useVideoConfig, spring, interpolate} from 'remotion';

const DATA = [38, 52, 47, 71, 60];

export const Bars: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  const max = Math.max(...DATA);
  return (
    <div style={{display: 'flex', gap: 16, alignItems: 'flex-end', height: 300}}>
      {DATA.map((v, i) => {
        const g = spring({frame, fps, delay: i * 5, config: {damping: 200}});
        const h = interpolate(g, [0, 1], [0, (v / max) * 260]);
        return (
          <div key={i} style={{width: 48, height: h, borderRadius: 6,
            background: i === 3 ? '#E8553A' : '#B6A892'}} />
        );
      })}
    </div>
  );
};`,
  title: `import {useCurrentFrame, useVideoConfig, spring} from 'remotion';

const WORDS = ['Pipeline', 'RAG', 'do', 'zero'];

export const Title: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  return (
    <h1 style={{fontFamily: 'Bricolage Grotesque', fontWeight: 800, fontSize: 72}}>
      {WORDS.map((w, i) => {
        const s = spring({frame, fps, delay: i * 5, config: {damping: 200}});
        return (
          <span key={i} style={{display: 'inline-block', marginRight: 16,
            opacity: s, transform: \`translateY(\${(1 - s) * 38}px)\`,
            color: w === 'RAG' ? '#D9714A' : '#211913'}}>{w}</span>
        );
      })}
    </h1>
  );
};`,
  shape: `import {useCurrentFrame, useVideoConfig, spring, interpolate} from 'remotion';

export const Shapes: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  // damping baixo = mais bounce (overshoot)
  const s = spring({frame, fps, config: {damping: 13, stiffness: 120}});
  const rot = interpolate(frame, [0, 60], [0, 225], {extrapolateRight: 'clamp'});
  return (
    <div style={{transform: \`scale(\${s})\`, position: 'relative', width: 130, height: 130}}>
      <div style={{position: 'absolute', inset: 0, borderRadius: 14,
        transform: \`rotate(\${rot + 45}deg)\`, background: '#E8553A'}} />
      <div style={{position: 'absolute', inset: '32%', borderRadius: '50%',
        transform: \`rotate(\${-rot}deg)\`, background: '#54C8D0'}} />
    </div>
  );
};`,
};
const CODE_H = {
  line: `<!-- Hyperframes: HTML + GSAP. O runtime controla o relógio e busca cada frame. -->
<div class="scene" data-duration="2.5s">
  <svg viewBox="0 0 302 180" width="100%">
    <polyline id="ln" points="16,150 70,120 124,128 178,80 232,96 286,40"
      fill="none" stroke="#E8553A" stroke-width="3.5"
      stroke-linecap="round" stroke-linejoin="round" />
  </svg>
</div>
<script>
  const ln = document.querySelector('#ln');
  const len = ln.getTotalLength();
  ln.style.strokeDasharray = len;
  ln.style.strokeDashoffset = len;
  // a timeline é seekada pelo Hyperframes (render determinístico)
  gsap.to(ln, {strokeDashoffset: 0, duration: 2.0, ease: 'power3.out'});
</script>`,
  donut: `<div class="scene" data-duration="2s">
  <svg viewBox="0 0 120 120" width="240">
    <circle cx="60" cy="60" r="46" fill="none" stroke="#241B16" stroke-width="16" />
    <circle id="arc" cx="60" cy="60" r="46" fill="none" stroke="#E8553A" stroke-width="16"
      stroke-linecap="round" transform="rotate(-90 60 60)" />
  </svg>
  <div id="pct" class="pct">0%</div>
</div>
<script>
  const arc = document.querySelector('#arc');
  const C = 2 * Math.PI * 46;
  arc.style.strokeDasharray = C;
  const target = 0.42; // 42%
  gsap.fromTo(arc, {strokeDashoffset: C},
    {strokeDashoffset: C * (1 - target), duration: 1.6, ease: 'power3.out',
     onUpdate() {
       const p = 1 - (gsap.getProperty(arc, 'strokeDashoffset') / C);
       document.querySelector('#pct').textContent = Math.round(p * 100) + '%';
     }});
</script>`,
  lower: `<!-- Timing por data-* + animação CSS (adapter seekável do Hyperframes). -->
<div class="lower" data-start="0.2s" data-duration="3s">
  <div class="bar"></div>
  <div class="txt"><b>Nicolas Lima</b><span>AI ENGINEER</span></div>
</div>
<style>
  .lower {display: flex; animation: slideIn .6s cubic-bezier(.2,.7,.3,1) both;}
  @keyframes slideIn {
    from {opacity: 0; transform: translateX(-26px);}
    to   {opacity: 1; transform: none;}
  }
  .bar {width: 5px; background: #E8553A; border-radius: 3px;}
  .txt {background: rgba(36,27,22,.92); padding: 10px 16px; border-radius: 0 8px 8px 0;}
  .txt b {display: block; font-family: 'Bricolage Grotesque'; font-size: 18px; color: #F2D8A7;}
  .txt span {font-family: 'IBM Plex Mono'; font-size: 11px; letter-spacing: 1px; color: #54C8D0;}
</style>`,
};

/* ── card de exemplo animado ─────────────────────────────────────────────── */
Object.assign(CODE_R, {
  typewriter: `import {useCurrentFrame, interpolate} from 'remotion';

export const Typewriter: React.FC = () => {
  const frame = useCurrentFrame();
  const text = 'Engenharia de dados.';
  const chars = Math.floor(interpolate(frame, [6, 6 + text.length * 2], [0, text.length],
    {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}));
  const caret = Math.floor(frame / 15) % 2 === 0;
  return (
    <div style={{fontFamily: 'Bricolage Grotesque', fontWeight: 800, fontSize: 56}}>
      {text.slice(0, chars)}
      <span style={{color: '#E8553A', opacity: caret ? 1 : 0}}>▌</span>
    </div>
  );
};`,
  wordswap: `import {useCurrentFrame, useVideoConfig, spring, interpolate} from 'remotion';

const WORDS = ['Software', 'Dados', 'IA', 'Soluções'];

export const WordSwap: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  const per = 22;                          // frames por palavra
  const idx = Math.floor(frame / per) % WORDS.length;
  const local = frame % per;
  const enter = spring({frame: local, fps, config: {damping: 200}});
  const exit = interpolate(local, [per - 8, per], [1, 0], {extrapolateLeft: 'clamp'});
  return (
    <h1 style={{fontFamily: 'Bricolage Grotesque', fontWeight: 800, fontSize: 60}}>
      Eu faço{' '}
      <span style={{color: '#E8553A', display: 'inline-block',
        opacity: Math.min(enter, exit), transform: \`translateY(\${(1 - enter) * 30}px)\`}}>
        {WORDS[idx]}
      </span>
    </h1>
  );
};`,
  ring: `import {useCurrentFrame, useVideoConfig, spring, interpolate} from 'remotion';

export const ProgressRing: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  const r = 70, C = 2 * Math.PI * r;
  const s = spring({frame, fps, config: {damping: 90}});
  const pct = interpolate(s, [0, 1], [0, 78]);
  return (
    <svg width={180} height={180} viewBox="0 0 180 180">
      <circle cx="90" cy="90" r={r} fill="none" stroke="#241B16" strokeWidth="14" />
      <circle cx="90" cy="90" r={r} fill="none" stroke="#E8553A" strokeWidth="14"
        strokeLinecap="round" transform="rotate(-90 90 90)"
        strokeDasharray={C} strokeDashoffset={C * (1 - pct / 100)} />
      <text x="90" y="103" textAnchor="middle" fontWeight="800" fontSize="42"
        fontFamily="Bricolage Grotesque" fill="#F2D8A7">{Math.round(pct)}%</text>
    </svg>
  );
};`,
  logo: `import {useCurrentFrame, useVideoConfig, spring} from 'remotion';

export const LogoReveal: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  const n = spring({frame, fps, config: {damping: 14, stiffness: 120}});
  const i = spring({frame: frame - 8, fps, config: {damping: 14}});
  return (
    <div style={{fontFamily: "'STIX Two Text', serif", fontStyle: 'italic',
      fontSize: 140, color: '#F2D8A7', transform: \`scale(\${n})\`}}>
      n
      <span style={{fontSize: '0.58em', verticalAlign: '0.62em', color: '#E8553A',
        display: 'inline-block', opacity: i, transform: \`translateY(\${(1 - i) * -24}px)\`}}>i</span>
    </div>
  );
};`,
});
Object.assign(CODE_H, {
  highlight: `<!-- Highlight que varre atrás da palavra-chave (GSAP). -->
<div class="scene" data-duration="1.6s">
  <h1 class="phrase">de dados a <span class="kw"><span class="bar"></span>decisões</span></h1>
</div>
<style>
  .phrase {font-family: 'Bricolage Grotesque'; font-weight: 800; font-size: 50px; color: #F2D8A7;}
  .kw {position: relative; display: inline-block;}
  .bar {position: absolute; left: -4px; right: -4px; bottom: 5px; height: 40%;
    background: #E8553A; opacity: .5; border-radius: 3px; z-index: -1;
    transform: scaleX(0); transform-origin: left;}
</style>
<script>
  gsap.to('.bar', {scaleX: 1, duration: .7, ease: 'power2.out', delay: .3});
</script>`,
  stagger: `<!-- Cards surgindo em cascata (GSAP stagger). -->
<div class="grid" data-duration="2s">
  <div class="card"></div><div class="card"></div><div class="card"></div>
  <div class="card"></div><div class="card"></div><div class="card"></div>
</div>
<style>
  .grid {display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;}
  .card {height: 52px; border-radius: 10px; background: #241B16; opacity: 0;}
</style>
<script>
  gsap.to('.card', {opacity: 1, y: 0, duration: .5, ease: 'power3.out',
    stagger: 0.08, startAt: {y: 22}});
</script>`,
  marquee: `<!-- Ticker infinito de tags (loop perfeito com xPercent). -->
<div class="wrap" data-duration="6s">
  <div class="track">
    <span>python</span><span>rag</span><span>llmops</span><span>spark</span>
    <!-- duplique o conteúdo para o loop ser contínuo -->
    <span>python</span><span>rag</span><span>llmops</span><span>spark</span>
  </div>
</div>
<style>
  .wrap {overflow: hidden;}
  .track {display: flex; gap: 12px; width: max-content;}
  .track span {font-family: 'IBM Plex Mono'; font-size: 13px; letter-spacing: 1px;
    text-transform: uppercase; color: #F2D8A7; border: 1px solid rgba(242,216,167,.14);
    padding: 7px 13px; border-radius: 20px; white-space: nowrap;}
</style>
<script>
  gsap.to('.track', {xPercent: -50, duration: 8, ease: 'none', repeat: -1});
</script>`,
  motionpath: `<!-- Ponto percorrendo uma curva (GSAP MotionPathPlugin). -->
<div class="scene" data-duration="2.4s">
  <svg viewBox="0 0 302 210" width="100%">
    <path id="route" d="M16 160 C 90 20 210 190 286 50"
      fill="none" stroke="#B6A892" stroke-width="2.5" stroke-dasharray="5 5" />
    <circle id="dot" r="9" fill="#E8553A" />
  </svg>
</div>
<script>
  gsap.registerPlugin(MotionPathPlugin);
  gsap.to('#dot', {duration: 2.4, ease: 'power1.inOut',
    motionPath: {path: '#route', align: '#route', alignOrigin: [0.5, 0.5]}});
</script>`,
});

/* ── card de exemplo animado ─────────────────────────────────────────────── */
function AnimCard({ title, sub, badge, badgeColor, durationInFrames, fps = 30, ratio, render, code }) {
  const [copied, setCopied] = React.useState(false);
  const [showCode, setShowCode] = React.useState(false);
  const copy = () => {
    const done = () => { setCopied(true); setTimeout(() => setCopied(false), 1500); };
    if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(code).then(done).catch(() => {});
    else { const ta = document.createElement("textarea"); ta.value = code; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); done(); } catch (e) {} document.body.removeChild(ta); }
  };
  return (
    <div style={{ background: T.card, border: `1px solid ${T.line}`, borderRadius: 16, overflow: "hidden" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "14px 18px", borderBottom: `1px solid ${T.line}`, gap: 12 }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 9, flexWrap: "wrap" }}>
            <span style={{ fontFamily: F.display, fontWeight: 700, fontSize: 15, whiteSpace: "nowrap" }}>{title}</span>
            <Pill color={badgeColor || T.cyan}>{badge}</Pill>
          </div>
          {sub && <div style={{ fontSize: 12, color: T.sub, marginTop: 3 }}>{sub}</div>}
        </div>
        <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
          <button onClick={() => setShowCode((s) => !s)} style={{ cursor: "pointer", fontFamily: F.mono, fontSize: 11, color: T.sub, background: "transparent", border: `1px solid ${T.line}`, padding: "6px 10px", borderRadius: 8 }}>{showCode ? "Preview" : "Código"}</button>
          <button onClick={copy} style={{ cursor: "pointer", fontFamily: F.mono, fontSize: 11, fontWeight: 600, color: copied ? T.card : T.ink, background: copied ? T.teal : "transparent", border: `1px solid ${copied ? T.teal : T.ink}`, padding: "6px 12px", borderRadius: 8 }}>{copied ? "Copiado ✓" : "Copiar"}</button>
        </div>
      </div>
      <div style={{ padding: 18 }}>
        {showCode
          ? <pre style={{ margin: 0, padding: "16px 18px", background: T.dbg, color: T.dsand, fontFamily: F.mono, fontSize: 11, lineHeight: 1.6, overflowX: "auto", borderRadius: 10, maxHeight: 360, overflowY: "auto", whiteSpace: "pre" }}>{code}</pre>
          : <Player durationInFrames={durationInFrames} fps={fps} render={render} ratio={ratio} dark />}
      </div>
    </div>
  );
}

/* ── seções ──────────────────────────────────────────────────────────────── */
function GMRemotion() {
  return (
    <Section id="remotion">
      <SecHead n="07" kicker="Animação · Remotion" title="Componentes animados em React."
        lead="No Remotion, vídeo é um componente React: cada quadro é uma função do tempo. Você dirige tudo por useCurrentFrame() com interpolate() e spring() — nada de CSS animation. Preview rodando ao vivo; copie o código TSX." />
      <Note title="O modelo mental">
        Pense por <strong>frame</strong>, não por transição. <span style={{ fontFamily: F.mono, fontSize: 13 }}>useCurrentFrame()</span> te dá o quadro atual; você calcula cada estilo a partir dele. Isso é o que torna o render determinístico e frame-accurate.
      </Note>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: 22, marginTop: 26 }}>
        <AnimCard title="KPI count-up" sub="spring + interpolate" badge="Remotion" badgeColor={T.coral} durationInFrames={90} render={R.countup} code={CODE_R.countup} />
        <AnimCard title="Barras crescendo" sub="stagger por delay" badge="Remotion" badgeColor={T.coral} durationInFrames={90} render={R.bars} code={CODE_R.bars} />
        <AnimCard title="Título revelando" sub="palavras escalonadas" badge="Remotion" badgeColor={T.coral} durationInFrames={75} render={R.title} code={CODE_R.title} />
        <AnimCard title="Formas em mola" sub="overshoot (damping baixo)" badge="Remotion" badgeColor={T.coral} durationInFrames={90} render={R.shape} code={CODE_R.shape} />
        <AnimCard title="Typewriter" sub="texto digitando + cursor" badge="Remotion" badgeColor={T.coral} durationInFrames={90} render={R.typewriter} code={CODE_R.typewriter} />
        <AnimCard title="Troca de palavra" sub="tipografia cinética" badge="Remotion" badgeColor={T.coral} durationInFrames={88} render={R.wordswap} code={CODE_R.wordswap} />
        <AnimCard title="Anel de progresso" sub="ring com spring" badge="Remotion" badgeColor={T.coral} durationInFrames={75} render={R.ring} code={CODE_R.ring} />
        <AnimCard title="Logo reveal" sub="nⁱ montando em mola" badge="Remotion" badgeColor={T.coral} durationInFrames={75} render={R.logo} code={CODE_R.logo} />
      </div>
    </Section>
  );
}

function GMHyperframes() {
  return (
    <Section id="hyperframes" alt>
      <SecHead n="08" kicker="Animação · Hyperframes" title="Componentes animados em HTML + GSAP."
        lead="No Hyperframes, vídeo é uma página HTML: você anima com GSAP, CSS ou WAAPI e o runtime faz o seek determinístico de cada frame. Timing vai em atributos data-*. Mesmo preview ao vivo; copie o HTML." />
      <Note title="O modelo mental">
        Escreva a animação como você já faria na web — uma <strong>timeline GSAP</strong> ou <span style={{ fontFamily: F.mono, fontSize: 13 }}>@keyframes</span> — e descreva o tempo com <span style={{ fontFamily: F.mono, fontSize: 13 }}>data-start</span> / <span style={{ fontFamily: F.mono, fontSize: 13 }}>data-duration</span>. O Hyperframes busca o frame e captura. Requer GSAP no projeto.
      </Note>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: 22, marginTop: 26 }}>
        <AnimCard title="Linha se desenhando" sub="stroke-dashoffset + GSAP" badge="Hyperframes" badgeColor={T.teal} durationInFrames={75} render={R.line} code={CODE_H.line} />
        <AnimCard title="Donut preenchendo" sub="GSAP + onUpdate (contador)" badge="Hyperframes" badgeColor={T.teal} durationInFrames={75} render={R.donut} code={CODE_H.donut} />
        <AnimCard title="Lower third entrando" sub="CSS keyframes + data-*" badge="Hyperframes" badgeColor={T.teal} durationInFrames={75} render={R.lower} code={CODE_H.lower} />
        <AnimCard title="Highlight de texto" sub="varredura GSAP" badge="Hyperframes" badgeColor={T.teal} durationInFrames={60} render={R.highlight} code={CODE_H.highlight} />
        <AnimCard title="Cards em cascata" sub="GSAP stagger" badge="Hyperframes" badgeColor={T.teal} durationInFrames={75} render={R.stagger} code={CODE_H.stagger} />
        <AnimCard title="Ticker infinito" sub="marquee em loop" badge="Hyperframes" badgeColor={T.teal} durationInFrames={120} render={R.marquee} code={CODE_H.marquee} />
        <AnimCard title="Ponto no caminho" sub="GSAP MotionPath" badge="Hyperframes" badgeColor={T.teal} durationInFrames={90} render={R.motionpath} code={CODE_H.motionpath} />
        <div style={{ background: T.panel, border: `1px dashed ${T.line2}`, borderRadius: 16, padding: 24, display: "flex", flexDirection: "column", justifyContent: "center" }}>
          <Mono color={T.clay} size={10.5}>Dica</Mono>
          <div style={{ fontFamily: F.display, fontWeight: 700, fontSize: 17, marginTop: 8, lineHeight: 1.25 }}>Reaproveite seus grafismos</div>
          <div style={{ fontSize: 13.5, color: T.sub, lineHeight: 1.5, marginTop: 8 }}>Qualquer SVG da seção <strong>Cenas & Grafismos</strong> vira animação aqui: anime <span style={{ fontFamily: F.mono, fontSize: 12.5 }}>stroke-dashoffset</span>, <span style={{ fontFamily: F.mono, fontSize: 12.5 }}>transform</span> ou <span style={{ fontFamily: F.mono, fontSize: 12.5 }}>opacity</span> com uma timeline GSAP e renderize.</div>
        </div>
      </div>
      <div style={{ marginTop: 24 }}>
        <Note title="Remotion ou Hyperframes?">
          <strong>Remotion</strong> se você já vive em React e quer tipagem e componentes reutilizáveis. <strong>Hyperframes</strong> se quer escrever HTML/CSS/GSAP direto (ótimo pra colar uma cena pronta ou gerar com IA). Os dois renderizam MP4 frame-accurate via headless Chrome — só muda o que você escreve.
        </Note>
      </div>
    </Section>
  );
}

Object.assign(window, { GMRemotion, GMHyperframes, Player, AnimCard, interp, spring01, easeOut, easeInOut });
