// CHUDMATE landing — MOTION MOCK layer.
// Simulates the app's cutout rig (eyes as separate layers) using ONLY
// place/scale/crop of the master art: white masks sit over the lens
// interiors and cropped pupil patches translate within them. No linework
// is redrawn. All ambient animation respects prefers-reduced-motion.
const { SpeechBubble: MSB } = window.CHUDMATEDesignSystem_3041b6;

const M_BODY = (window.__resources && window.__resources.body) || "assets/chudmate-fullbody.png";
const M_FACE = (window.__resources && window.__resources.face) || "assets/chudmate-face.png";
const MASTER = 942; // fullbody master is a 942×942 square

// measured lens-interior + pupil rects (master px)
const EYES = {
  maskL:  { x: 431, y: 266, w: 98, h: 54 },
  maskR:  { x: 612, y: 259, w: 74, h: 61 },
  pupilL: { x: 466, y: 274, w: 58, h: 44, clampX: [-8, 4], clampY: [-5, 2] },
  pupilR: { x: 618, y: 282, w: 64, h: 36, clampX: [-6, 4], clampY: [-4, 1] },
};

const pct = (v) => (v / MASTER) * 100 + "%";

// NOTE: this is a motion MOCK — animations run unconditionally so the demo is
// visible everywhere. A production build should gate ambient motion behind
// prefers-reduced-motion; the rig itself (eyes etc.) is event-driven and fine.
function reducedMotion() {
  return false;
}

/* one-time keyframe injection */
(function injectMotionCSS() {
  if (document.getElementById("cm-motion-css")) return;
  const s = document.createElement("style");
  s.id = "cm-motion-css";
  s.textContent = `
  @keyframes cm-rise { from { transform: translateY(46px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
  @keyframes cm-pop { 0% { transform: scale(.55); opacity: 0; } 70% { transform: scale(1.07); opacity: 1; } 100% { transform: scale(1); opacity: 1; } }
  @keyframes cm-breathe { 0%,100% { transform: translateY(0) scaleY(1); } 50% { transform: translateY(2.5px) scaleY(.992); } }
  @keyframes cm-float { from { transform: translateY(-4px) rotate(var(--tilt, 0deg)); } to { transform: translateY(5px) rotate(var(--tilt, 0deg)); } }
  @keyframes cm-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
  @keyframes cm-nudge { 0%, 92% { transform: rotate(0); } 94% { transform: rotate(-2.5deg); } 96% { transform: rotate(2deg); } 98% { transform: rotate(-1deg); } 100% { transform: rotate(0); } }
  .cm-enter-body { animation: cm-rise .65s cubic-bezier(0.2, 0.9, 0.25, 1.25) both; }
  .cm-enter-bubble { animation: cm-pop .4s .4s cubic-bezier(0.2, 0.9, 0.25, 1.25) both; transform-origin: 20% 100%; }
  .cm-breathe { animation: cm-breathe 3.4s ease-in-out infinite; transform-origin: 50% 100%; }
  .cm-float { animation: cm-float 4.6s ease-in-out infinite alternate; }
  .cm-marquee-track { animation: cm-marquee 26s linear infinite; }
  .cm-marquee:hover .cm-marquee-track { animation-play-state: paused; }
  .cm-nudge { animation: cm-nudge 7s ease-in-out 3s infinite; }
  .cm-reveal { opacity: 0; transform: translateY(26px) rotate(var(--tilt, 0deg)); transition: opacity .55s ease, transform .55s cubic-bezier(0.2, 0.9, 0.25, 1.25); }
  .cm-reveal.cm-in { opacity: 1; transform: translateY(0) rotate(var(--tilt, 0deg)); }
  .cm-bubble-reveal { opacity: 0; transform: scale(.55); transition: opacity .45s ease, transform .45s cubic-bezier(0.2, 0.9, 0.25, 1.25); }
  .cm-bubble-reveal.cm-in { opacity: 1; transform: scale(1); }
.cm-scatter:hover { transform: rotate(0deg) !important; }
.cm-scatter { transition: transform .25s cubic-bezier(0.2, 0.9, 0.25, 1.25); }
`;
  document.head.appendChild(s);
})();

/* reveal-on-scroll hook — poll-driven (scroll/IO/rAF events are unreliable in
   embedded previews; a 300ms rect check works everywhere and is cheap) */
function useInView() {
  const ref = React.useRef(null);
  const [seen, setSeen] = React.useState(false);
  React.useEffect(() => {
    if (!ref.current || seen) return;
    const el = ref.current;
    const check = () => {
      const r = el.getBoundingClientRect();
      if (r.top < window.innerHeight * 0.88 || r.bottom < 0) { setSeen(true); return true; }
      return false;
    };
    if (check()) return;
    const int = setInterval(() => { if (check()) clearInterval(int); }, 300);
    return () => clearInterval(int);
  }, [seen]);
  return [ref, seen];
}

/* a cropped patch of the master, as a % -positioned layer over the body img */
function Patch({ rect, white, style, innerRef }) {
  return (
    <div
      ref={innerRef}
      aria-hidden
      style={{
        position: "absolute",
        left: pct(rect.x), top: pct(rect.y),
        width: pct(rect.w), height: pct(rect.h),
        overflow: "hidden",
        background: white ? "var(--art-white, #FFFFFF)" : "transparent",
        ...style,
      }}
    >
      {!white && (
        <img
          src={M_BODY}
          alt=""
          draggable={false}
          style={{
            position: "absolute",
            width: (MASTER / rect.w) * 100 + "%",
            height: "auto",
            left: -(rect.x / rect.w) * 100 + "%",
            top: -(rect.y / rect.h) * 100 + "%",
            maxWidth: "none",
          }}
        />
      )}
    </div>
  );
}

/*  The living hero chud: breathing, cursor-tracked eyes, hover recoil, poke squash. */
function ChudHero({ heckle, onPoke, hovering, setHovering }) {
  const pupL = React.useRef(null);
  const pupR = React.useRef(null);
  const wrapRef = React.useRef(null);
  const idleTimer = React.useRef(null);

  const setEyes = (nx, ny) => { // nx, ny in [-1, 1]
    const place = (ref, r) => {
      if (!ref.current) return;
      const dx = nx < 0 ? -nx * r.clampX[0] : nx * r.clampX[1];
      const dy = ny < 0 ? -ny * r.clampY[0] : ny * r.clampY[1];
      ref.current.style.transform = `translate(${dx.toFixed(1)}px, ${dy.toFixed(1)}px)`;
    };
    place(pupL, EYES.pupilL);
    place(pupR, EYES.pupilR);
  };

  React.useEffect(() => {
    if (reducedMotion()) return;
    let wanderInt = null;
    const wander = () => {
      clearInterval(wanderInt);
      wanderInt = setInterval(() => setEyes(Math.random() * 2 - 1, Math.random() * 2 - 1), 2400);
    };
    const onMove = (e) => {
      if (!wrapRef.current) return;
      clearInterval(wanderInt); // stop idle wandering while the cursor drives
      const b = wrapRef.current.getBoundingClientRect();
      const cx = b.left + b.width * 0.59, cy = b.top + b.height * 0.31; // eyes centre
      const nx = Math.max(-1, Math.min(1, (e.clientX - cx) / 260));
      const ny = Math.max(-1, Math.min(1, (e.clientY - cy) / 220));
      setEyes(nx, ny);
      clearTimeout(idleTimer.current);
      idleTimer.current = setTimeout(wander, 3000);
    };
    wander();
    window.addEventListener("mousemove", onMove);
    return () => { window.removeEventListener("mousemove", onMove); clearInterval(wanderInt); clearTimeout(idleTimer.current); };
  }, []);

  const [squash, setSquash] = React.useState(false);
  const poke = () => {
    setSquash(true);
    setTimeout(() => setSquash(false), 220);
    onPoke();
  };

  return (
    <div
      ref={wrapRef}
      className="cm-enter-body"
      onMouseEnter={() => setHovering(true)}
      onMouseLeave={() => setHovering(false)}
      onClick={poke}
      style={{ position: "relative", width: "min(440px, 94%)", cursor: "pointer", zIndex: 2, marginBottom: "-5%" }}
    >
      <div className="cm-breathe" style={{ position: "relative" }}>
        <div style={{
          position: "relative",
          transition: "transform .22s cubic-bezier(0.2,0.9,0.25,1.25)",
          transform: squash ? "scaleY(.93) scaleX(1.03)" : hovering ? "translateY(-7px) rotate(-1.6deg)" : "none",
          transformOrigin: "50% 100%",
        }}>
          <img src={M_BODY} alt="CHUDMATE" draggable={false} style={{ width: "100%", height: "auto", display: "block" }} />
          {/* rig mock: masks + movable pupils (place/crop only — no redraw) */}
          <Patch rect={EYES.maskL} white />
          <Patch rect={EYES.maskR} white />
          <Patch rect={EYES.pupilL} innerRef={pupL} />
          <Patch rect={EYES.pupilR} innerRef={pupR} />
        </div>
      </div>
    </div>
  );
}

/* fixed corner gremlin that peeks further in & mutters per section */
const PEEKS = {
  features: { rise: 0.46, tilt: -7, say: "oh good, a feature list." },
  heckles:  { rise: 0.62, tilt: 5,  say: "my best material, honestly." },
  pricing:  { rise: 0.54, tilt: -4, say: "you're not gonna pay. we both know." },
  footer:   { rise: 0.7,  tilt: 8,  say: "still scrolling? clingy." },
};

function PeekChud() {
  const [zone, setZone] = React.useState(null);
  const [hidden, setHidden] = React.useState(false);
  const [onDark, setOnDark] = React.useState(false);

  React.useEffect(() => {
    if (reducedMotion()) return;
    const ids = [
      ["features", () => document.getElementById("features")],
      ["heckles", () => document.getElementById("heckles")],
      ["pricing", () => document.getElementById("pricing")],
      ["footer", () => document.querySelector("footer")],
    ];
    let last = null, lastDark = null;
    const measure = () => {
      const vh = window.innerHeight;
      let best = null, bestPx = vh * 0.16; // needs >16% of viewport to claim him
      for (const [name, get] of ids) {
        const el = get();
        if (!el) continue;
        const r = el.getBoundingClientRect();
        const visible = Math.min(r.bottom, vh) - Math.max(r.top, 0);
        if (visible > bestPx) { best = name; bestPx = visible; }
      }
      if (best !== last) { last = best; setZone(best); }
      // what's actually BEHIND the peeker (bottom-right corner)? Under the
      // dark law the dark page AND its bg-inset footer are both dark — he
      // stays inverted everywhere in dark. In light, only the footer chrome
      // slab is dark, so he inverts over it.
      const themeDark = document.documentElement.getAttribute("data-theme") !== "light";
      const f = document.querySelector("footer");
      const behindFooter = !!f && f.getBoundingClientRect().top < vh - 70;
      const dark = themeDark || behindFooter;
      if (dark !== lastDark) { lastDark = dark; setOnDark(dark); }
    };
    measure();
    const int = setInterval(measure, 250); // poll — scroll events are unreliable in embedded previews
    return () => clearInterval(int);
  }, []);

  if (reducedMotion()) return null;
  const p = zone ? PEEKS[zone] : null;
  const up = p && !hidden ? p.rise : 0;
  const H = 150;
  return (
    <div style={{ position: "fixed", right: 26, bottom: 0, zIndex: 90, pointerEvents: "none" }}>
      {p && !hidden && (
        <div key={zone} className="cm-enter-bubble" style={{ position: "absolute", right: 60, bottom: H * up + 12, zIndex: 2, width: "max-content" }}>
          <MSB tail="bottom-right" variant={onDark ? "paper" : "ink"} size="sm" maxWidth="280px">{p.say}</MSB>
        </div>
      )}
      <div
        onClick={() => { setHidden(true); setTimeout(() => setHidden(false), 9000); }}
        title="he'll be back"
        style={{
          width: 120, height: H, overflow: "hidden", display: "flex", alignItems: "flex-start", justifyContent: "center",
          pointerEvents: "auto", cursor: p && !hidden ? "pointer" : "default",
        }}
      >
        <img
          src={M_FACE}
          alt=""
          draggable={false}
          style={{
            width: 110, height: "auto", display: "block",
            transform: `translateY(${(1 - up) * 100}%) rotate(${p && !hidden ? p.tilt : 0}deg)`,
            transition: "transform .6s cubic-bezier(0.2,0.9,0.25,1.25), filter .3s ease",
            // over dark backdrops: brand "inverted" treatment (white linework).
            // over light: the plain art, just a soft contact shadow — no halo.
            filter: onDark
              ? "invert(1) drop-shadow(0 4px 8px rgba(0,0,0,.45))"
              : "drop-shadow(0 4px 8px rgba(24,20,14,.25))",
          }}
        />
      </div>
    </div>
  );
}

/* scrolling ticker version of the trust strip — the band fill is pinned to
   the art-ink plate (lime-on-ink is the signature); its top/bottom rules
   ride the treatment seam: ink strokes in light, hairlines in dark */
function MarqueeStrip({ items }) {
  const doubled = items.concat(items);
  return (
    <div className="cm-marquee" style={{ borderTop: "var(--edge-control)", borderBottom: "var(--edge-control)", background: "var(--art-ink, #18140E)", overflow: "hidden" }}>
      <div className="cm-marquee-track" style={{ display: "flex", gap: 44, padding: "14px 0", width: "max-content" }}>
        {doubled.map((t, k) => (
          <span key={k} style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent)", whiteSpace: "nowrap" }}>✶ {t}</span>
        ))}
      </div>
    </div>
  );
}

/* reveal-on-scroll wrapper (cards, bubbles) */
function Reveal({ children, delay = 0, tilt = 0, bubble = false, style }) {
  const [ref, seen] = useInView();
  return (
    <div
      ref={ref}
      className={(bubble ? "cm-bubble-reveal" : "cm-reveal") + (seen ? " cm-in" : "")}
      style={{ "--tilt": tilt + "deg", transitionDelay: delay + "ms", ...style }}
    >
      {children}
    </div>
  );
}

Object.assign(window, { ChudHero, PeekChud, MarqueeStrip, Reveal, useInView });
