/* global React, ReactDOM, I18N, POSTS */
const { useState, useEffect, useMemo } = React;

const IOS_QR = 'https://quickchart.io/qr?size=300&margin=1&ecLevel=M&dark=ffffff&light=00000000&text=' +
  encodeURIComponent('https://apps.apple.com/app/sayyo/id6504123159');
const AND_QR = 'https://quickchart.io/qr?size=300&margin=1&ecLevel=M&dark=ffffff&light=00000000&text=' +
  encodeURIComponent('https://play.google.com/store/apps/details?id=com.teambro.sayyo');
const IOS_URL = "https://apps.apple.com/app/sayyo/id6504123159";
const AND_URL = "https://play.google.com/store/apps/details?id=com.teambro.sayyo";

const HEADLINE = {
  vi: ["Hôm nay", "ai sẽ giúp", "bạn?"],
  ko: ["오늘", "누가 도와줄까요?", ""],
  en: ["Who's helping", "you", "today?"],
  jp: ["今日は誰が", "あなたを", "助けますか?"]
};
const FINAL_HEAD = {
  vi: "Sẵn sàng kiếm thêm, hay cần một tay?",
  ko: "수익 시작, 또는 도움 받기.",
  en: "Earn more, or get a hand.",
  jp: "副収入を始める、それとも助けが必要?"
};
const COMP = {
  vi: {
    them: "Trong nhóm chat",
    them_head: "Mỗi câu nói trôi đi rồi mất.",
    us: "Ở Sayyo",
    us_head: "Mỗi giao dịch có một hồ sơ thật.",
    them_list: [
      "Cuộc trò chuyện trôi mất trong feed.",
      "Không có cách xác minh ai là ai.",
      "Không lưu lại được uy tín giữa người lạ.",
      "Không ai đứng ra khi có trục trặc."
    ],
    us_list: [
      "Mỗi giao dịch có hồ sơ và địa chỉ.",
      "Lịch sử giao dịch công khai trên hồ sơ.",
      "Đội ngũ Sayyo hỗ trợ khi cần."
    ]
  },
  ko: {
    them: "단톡방에서는",
    them_head: "한 마디가 흘러가면 사라집니다.",
    us: "Sayyo에서는",
    us_head: "모든 거래에 진짜 프로필이 따릅니다.",
    them_list: [
      "대화가 피드에서 흘러가 버립니다.",
      "누구인지 확인할 방법이 없습니다.",
      "낯선 사람끼리는 평판이 남지 않습니다.",
      "문제가 생겨도 중재해 줄 사람이 없습니다."
    ],
    us_list: [
      "모든 거래에 프로필과 주소가 있습니다.",
      "거래 이력이 프로필에 공개로 표시.",
      "Sayyo 팀이 분쟁을 중재합니다."
    ]
  },
  en: {
    them: "In group chats",
    them_head: "Every word slips down the feed.",
    us: "On Sayyo",
    us_head: "Every deal has a real profile attached.",
    them_list: [
      "Conversations get lost in the scroll.",
      "No way to verify who anyone really is.",
      "No reputation between strangers.",
      "No one to mediate when things break."
    ],
    us_list: [
      "Every deal has a profile and an address.",
      "Public deal history on every profile.",
      "The Sayyo team mediates if needed."
    ]
  },
  jp: {
    them: "グループチャットでは",
    them_head: "一言が流れて消えてしまいます。",
    us: "Sayyoでは",
    us_head: "すべての取引に本物のプロフィールが。",
    them_list: [
      "会話がフィードで流れて消えます。",
      "誰なのか確認する方法がありません。",
      "見知らぬ人同士に評判は残りません。",
      "問題が起きても仲裁する人がいません。"
    ],
    us_list: [
      "すべての取引にプロフィールと住所。",
      "取引履歴がプロフィールに公開で表示。",
      "Sayyoチームが必要時に紛争を仲裁。"
    ]
  }
};
const INSIDE_CAPS = {
  vi: ["Nguồn cấp khu phố", "Chat trực tiếp", "Nhờ giúp", "Mua bán đồ"],
  ko: ["동네 피드", "실시간 채팅", "요청 보기", "중고 매물"],
  en: ["Neighborhood feed", "Live chat", "Asks", "Marketplace"],
  jp: ["近所フィード", "ライブチャット", "お願い", "マーケットプレイス"]
};
// Map UI language to image-asset language.
// JP has no screen captures, so it falls back to EN screenshots.
const IMG_LANG = { vi: "vi", ko: "ko", en: "en", jp: "en" };
/* ============ SplitText — Ogaki-style per-word mask reveal ============ */
const SplitText = ({ text, base = 0, gap = 70 }) => {
  if (!text) return null;
  const flat = String(text).replace(/\n/g, " ").replace(/\s+/g, " ").trim();
  const words = flat.split(" ");
  return (
    <span className="split-reveal">
      {words.map((w, i) => (
        <span className="split-mask" key={i}>
          <span
            className="split-word"
            style={{ transitionDelay: (base + i * gap) + "ms" }}
          >{w}{i < words.length - 1 ? " " : ""}</span>
        </span>
      ))}
    </span>
  );
};

const PHOSPHOR_BY_KEY = {
  "cat-language":"ph-translate","cat-delivery":"ph-package","cat-cleaning":"ph-sparkle",
  "cat-moving":"ph-truck","cat-interpret":"ph-chats-circle","cat-repair":"ph-wrench",
  "cat-jobs":"ph-briefcase","cat-sell":"ph-shopping-bag"
};

/* ============ Nav ============ */
const V3Nav = ({ lang, setLang }) => (
  <nav className="v3-nav">
    <div className="v3-nav-inner">
      <a href="#" className="v3-nav-logo" aria-label="Sayyo">
        <img src="assets/sayyo_logo_whtbg.png" alt="Sayyo"/>
      </a>
      <div className="v3-lang">
        {["vi","ko","en","jp"].map((l,i) => (
          <React.Fragment key={l}>
            <button aria-current={lang===l?"true":undefined} onClick={()=>setLang(l)}>{l.toUpperCase()}</button>
            {i<3 && <span className="sep">·</span>}
          </React.Fragment>
        ))}
      </div>
    </div>
  </nav>
);

/* ============ Badges — dark pill (Round 5 style) + QR inside, no magnetic ============ */
const Badges = ({ lang, dark = false }) => {
  const cls = "dl-tile dl-tile-qr" + (dark ? " is-dark" : "");
  return (
    <div className={"dlstack" + (dark ? " is-dark" : "")}>
      <a className={cls} href={IOS_URL} target="_blank" rel="noopener" aria-label="Download on the App Store">
        <div className="dl-tile-main">
          <img src="assets/ic_applelogo.png" width="24" height="24" alt="" aria-hidden="true"/>
          <div className="dl-tile-text">
            <div className="store-s">{lang==="vi"?"Tải về trên":lang==="ko"?"다운로드":lang==="jp"?"ダウンロード":"Download on the"}</div>
            <div className="store-l">App Store</div>
          </div>
        </div>
        <div className="dl-tile-qr-slot"><img src={IOS_QR} alt=""/></div>
      </a>
      <a className={cls} href={AND_URL} target="_blank" rel="noopener" aria-label="Get it on Google Play">
        <div className="dl-tile-main">
          <svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M3.6 1.7C3.2 2 3 2.5 3 3.2v17.6c0 .7.2 1.2.6 1.5L13.4 12 3.6 1.7z" fill="#00A939"/>
            <path d="M16.8 8.6L4.7 1.5c-.4-.2-.8-.3-1.1-.2L13.4 12l3.4-3.4z" fill="#FFB300"/>
            <path d="M20.2 10.6L17 8.7l-3.6 3.3 3.6 3.3 3.2-1.9c1-.6 1-2.2 0-2.8z" fill="#E53935"/>
            <path d="M3.6 22.3c.3.1.7.1 1.1-.1l12-7.2-3.4-3.4-9.7 10.7z" fill="#1976D2"/>
          </svg>
          <div className="dl-tile-text">
            <div className="store-s">{lang==="vi"?"Có trên":lang==="ko"?"다운로드":lang==="jp"?"ダウンロード":"Get it on"}</div>
            <div className="store-l">Google Play</div>
          </div>
        </div>
        <div className="dl-tile-qr-slot"><img src={AND_QR} alt=""/></div>
      </a>
    </div>
  );
};

/* ============ Hero ============ */
const V3Hero = ({ t, lang }) => {
  const head = HEADLINE[lang];
  const words = head.join(" ").split(/\s+/).filter(Boolean);
  return (
    <section className="v3-hero">
      <div className="v3-aurora"/>
      <div className="v3-wrap v3-hero-inner">
        <div className="v3-hero-grid">
          <div className="v3-hero-left reveal-bi in">
            <div className="v3-eyebrow">01 — {lang==="vi"?"Chào bạn":lang==="ko"?"환영합니다":lang==="jp"?"ようこそ":"Welcome"}</div>
            <h1 className="v3-display">
              {words.map((w,i)=>(
                <span className="v3-word" key={i} style={{animationDelay: (600 + i*70)+"ms"}}>{w}{i<words.length-1?"\u00a0":""}</span>
              ))}
            </h1>
            <p className="v3-hero-sub">{t.hero.sub}</p>
            <Badges lang={lang}/>
          </div>
          <div className="v3-hero-right reveal-bi in" style={{transitionDelay:"160ms"}}>
            <div className="v3-phone-glow" aria-hidden="true">
              <span className="orb orb-1"/>
              <span className="orb orb-2"/>
              <span className="orb orb-3"/>
            </div>
            <div className="v3-hero-phone" data-tilt>
              <img src={`assets/screens/${IMG_LANG[lang]||"en"}_home.png`} alt=""/>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
};

/* ============ Three Ways ============ */
const V3Ways = ({ t, lang }) => {
  const ways = [
    { key:"offer", cls:"is-green", screen:"detail_offer",
      eyebrow: "02 — "+(lang==="vi"?"Trở thành Provider":lang==="ko"?"Provider 되기":lang==="jp"?"Providerになる":"Become a Provider"),
      head: lang==="vi"?"Biến kỹ năng thành thu nhập.":lang==="ko"?"내 재능, 돈이 되는 곳.":lang==="jp"?"スキルを収入に変える。":"Turn your skills into income.",
      body: t.types.offer.body },
    { key:"ask", cls:"is-flip", screen:"detail_ask",
      eyebrow: "03 — "+(lang==="vi"?"Đăng yêu cầu":lang==="ko"?"요청 올리기":lang==="jp"?"依頼を投稿":"Post a request"),
      head: lang==="vi"?"Nhờ một tay từ hàng xóm.":lang==="ko"?"동네 이웃에게 부탁하기.":lang==="jp"?"ご近所さんに手助けをお願い。":"Ask a neighbor for help.",
      body: t.types.ask.body },
    { key:"sell", cls:"", screen:"detail_sell",
      eyebrow: "04 — "+(lang==="vi"?"Mua bán trong khu":lang==="ko"?"동네 중고거래":lang==="jp"?"近所で売買":"Buy and sell locally"),
      head: lang==="vi"?"Mua bán đồ trong khu phố.":lang==="ko"?"동네 안에서 사고팔기.":lang==="jp"?"近所で売買する。":"Buy and sell on your block.",
      body: t.types.sell.body }
  ];
  return (
    <>
    {ways.map(w => (
      <section key={w.key} className={"v3-way "+w.cls}>
        {w.cls.includes("is-green") && (
          <>
            <video
              className="v3-way-video"
              src="assets/ways-bg.mp4"
              autoPlay
              loop
              muted
              playsInline
              preload="auto"
              poster=""
              aria-hidden="true"
              ref={(el) => {
                if (!el) return;
                // Force-load + play once mounted; some browsers defer autoplay until told
                el.muted = true;
                el.playsInline = true;
                const tryPlay = () => { const p = el.play(); if (p && p.catch) p.catch(()=>{}); };
                el.load();
                tryPlay();
                // Retry on first user interaction (autoplay policy fallback)
                const once = () => { tryPlay(); window.removeEventListener("pointerdown", once); window.removeEventListener("scroll", once); };
                window.addEventListener("pointerdown", once, { once: true });
                window.addEventListener("scroll", once, { once: true, passive: true });
              }}
            />
            <div className="v3-way-video-tint" aria-hidden="true"/>
            <div className="v3-way-ambient" aria-hidden="true">
              <div className="amb amb-1"/>
              <div className="amb amb-2"/>
              <div className="amb amb-3"/>
            </div>
          </>
        )}
        <div className="v3-wrap v3-way-inner">
          <div className="v3-way-grid">
            <div className="reveal-bi">
              <div className="v3-eyebrow">{w.eyebrow}</div>
              <h2 className="v3-display"><SplitText text={w.head}/></h2>
              <p className="v3-way-body">{w.body}</p>
            </div>
            <div style={{transitionDelay:"120ms"}}>
              <div className="v3-way-phone" data-tilt data-phone-reveal>
                <img src={`assets/screens/${IMG_LANG[lang]||"en"}_${w.screen}.png`} alt=""/>
              </div>
            </div>
          </div>
        </div>
      </section>
    ))}
    </>
  );
};

/* ============ Best Picks ============ */
const MWEB_URL = "https://sayyo.mobileto.io/mweb";
const V3Picks = ({ t, lang }) => {
  // Live data pulled from sayyo.mobileto.io/mweb — Vietnamese-native titles
  const items = useMemo(() => POSTS.slice(0, 12), []);
  const moreLabel = lang === "vi" ? "Xem thêm trên Sayyo" : lang === "ko" ? "더보기" : lang === "jp" ? "Sayyoでもっと見る" : "See more on Sayyo";
  return (
    <section className="v3-picks">
      <div className="v3-wrap">
        <div className="v3-picks-head reveal-bi">
          <div className="v3-eyebrow">05 — {t.picks.eyebrow}</div>
          <h2 className="v3-display"><SplitText text={t.picks.title.replace("\n"," ")}/></h2>
          <p>{t.picks.sub}</p>
        </div>
        <div className="v3-picks-grid reveal-bi">
          {items.map((p, idx) => (
            <a key={p.id} className="v3-card reveal-bi" style={{transitionDelay: (idx * 60) + "ms"}} href={MWEB_URL} target="_blank" rel="noopener">
              <div className="v3-card-photo">
                <img src={p.photo} alt="" loading="lazy"/>
              </div>
              <div className="v3-card-body">
                <h3 className="v3-card-title">{p.title}</h3>
                <div className="v3-card-price">{p.price}</div>
                <div className="v3-card-foot">
                  <span className="v3-card-loc">
                    <i className="ph ph-map-pin" aria-hidden="true"></i>
                    {p.district}
                  </span>
                  {(p.views > 0 || p.likes > 0) && (
                    <span className="v3-card-meta">
                      {p.views > 0 && <span><i className="ph ph-eye" aria-hidden="true"></i> {p.views}</span>}
                      {p.likes > 0 && <span><i className="ph ph-heart" aria-hidden="true"></i> {p.likes}</span>}
                    </span>
                  )}
                </div>
              </div>
            </a>
          ))}
        </div>
        <div className="v3-picks-more reveal-bi">
          <a href={MWEB_URL} target="_blank" rel="noopener">{moreLabel}</a>
        </div>
      </div>
    </section>
  );
};

/* ============ Trust ============ */
const V3Trust = ({ t, lang }) => {
  const icons = ["ph-seal-check","ph-handshake","ph-medal"];
  return (
    <section className="v3-trust">
      <div className="v3-aurora"/>
      <div className="v3-wrap v3-trust-inner">
        <div className="v3-trust-head reveal-bi">
          <div className="v3-eyebrow">06 — {t.safety.eyebrow}</div>
          <h2 className="v3-display"><SplitText text={t.safety.title.replace("\n"," ")}/></h2>
          <p>{t.safety.sub}</p>
        </div>
        <div className="v3-trust-grid">
          {t.safety.items.map((it,i) => (
            <div className="v3-trust-cell reveal-bi" key={i} style={{transitionDelay:(i*100)+"ms"}}>
              <div className="v3-trust-icon"><i className={"ph "+icons[i]}/></div>
              <h3>{it.title}</h3>
              <p>{it.body}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

/* ============ Inside the app ============ */
const V3Inside = ({ t, lang }) => {
  const screens = ["chat","detail_offer","detail_ask","detail_sell"];
  const caps = INSIDE_CAPS[lang];
  return (
    <section className="v3-inside">
      <div className="v3-wrap">
        <div className="v3-inside-head reveal-bi">
          <div className="v3-eyebrow">07 — {t.showcase.eyebrow}</div>
          <h2 className="v3-display"><SplitText text={t.showcase.title.replace("\n"," ")}/></h2>
        </div>
        <div className="v3-inside-row">
          {screens.map((s,i) => (
            <div key={s} className="v3-inside-cell" style={{transitionDelay:(i*90)+"ms"}}>
              <div className="v3-inside-phone" data-tilt data-phone-reveal><img src={`assets/screens/${IMG_LANG[lang]||"en"}_${s}.png`} alt=""/></div>
              <div className="v3-inside-cap">{caps[i]}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

/* ============ Categories — editorial index list (Awwwards-tier) ============ */
const V3Cats = ({ t, lang }) => (
  <section className="v3-cats">
    <div className="v3-wrap">
      <div className="v3-cats-head reveal-bi">
        <div className="v3-eyebrow">08 — {t.cats.eyebrow}</div>
        <h2 className="v3-display"><SplitText text={t.cats.title.replace("\n"," ")}/></h2>
      </div>
      <ul className="v3-cats-list">
        {t.cats.list.map((c, i) => (
          <li key={c.key} className="v3-cat-row reveal-bi" style={{transitionDelay:(i*45)+"ms"}}>
            <a className="v3-cat-link" href={MWEB_URL} target="_blank" rel="noopener">
              <span className="v3-cat-idx">{String(i+1).padStart(2,"0")}</span>
              <span className="v3-cat-lbl">{c.label}</span>
              <span className="v3-cat-arrow" aria-hidden="true">→</span>
            </a>
          </li>
        ))}
      </ul>
    </div>
  </section>
);

/* ============ Comparison ============ */
const V3Comp = ({ lang }) => {
  const c = COMP[lang];
  return (
    <section className="v3-comp">
      <div className="v3-wrap">
        <div className="v3-comp-grid">
          <div className="v3-comp-card is-them reveal-bi">
            <div className="v3-comp-icon"><i className="ph ph-chats" aria-hidden="true"></i></div>
            <div className="v3-comp-label">{c.them}</div>
            <h3 className="v3-comp-head"><SplitText text={c.them_head} gap={45}/></h3>
            <ul className="v3-comp-list">
              {c.them_list.map((x,i)=>(
                <li key={i}>
                  <span className="v3-comp-mark"><i className="ph ph-x" aria-hidden="true"></i></span>
                  <span>{x}</span>
                </li>
              ))}
            </ul>
          </div>
          <div className="v3-comp-card is-us reveal-bi" style={{transitionDelay:"160ms"}}>
            <div className="v3-comp-icon"><i className="ph ph-seal-check" aria-hidden="true"></i></div>
            <div className="v3-comp-label">{c.us}</div>
            <h3 className="v3-comp-head"><SplitText text={c.us_head} base={120} gap={45}/></h3>
            <ul className="v3-comp-list">
              {c.us_list.map((x,i)=>(
                <li key={i}>
                  <span className="v3-comp-mark"><i className="ph ph-check" aria-hidden="true"></i></span>
                  <span>{x}</span>
                </li>
              ))}
            </ul>
          </div>
        </div>
      </div>
    </section>
  );
};

/* ============ Final CTA ============ */
const V3Final = ({ t, lang }) => (
  <section className="v3-final">
    <div className="v3-aurora"/>
    <div className="v3-wrap v3-final-inner">
      <div className="v3-final-sub reveal-bi">09 — {lang==="vi"?"Tải app":lang==="ko"?"앱 받기":lang==="jp"?"アプリ取得":"Get the app"}</div>
      <h2 className="v3-display" style={{transitionDelay:"100ms"}}><SplitText text={FINAL_HEAD[lang]}/></h2>
      <div className="reveal-bi" style={{transitionDelay:"240ms"}}><Badges lang={lang}/></div>
    </div>
  </section>
);

/* ============ Footer ============ */
const V3Foot = ({ t, lang, setLang }) => (
  <footer className="v3-foot">
    <div className="v3-wrap">
      <div className="v3-foot-grid">
        <div className="v3-foot-brand">
          <div className="v3-mark">Sayyo</div>
          <p className="v3-foot-tag">{t.footer.tagline}</p>
        </div>
        <div className="v3-foot-cols">
          {["product","legal","support"].map((col) => (
            <div key={col}>
              <h5>{t.footer[col]}</h5>
              <ul>{(t.footer.links[col] || []).map((l, i) => {
                const isExternal = typeof l.href === "string" && l.href.startsWith("http");
                return (
                  <li key={i}>
                    <a
                      href={l.href}
                      {...(isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {})}
                    >{l.label}</a>
                  </li>
                );
              })}</ul>
            </div>
          ))}
        </div>
        <div className="v3-foot-right">
          <div className="v3-lang">
            {["vi","ko","en","jp"].map((l,i)=>(
              <React.Fragment key={l}>
                <button aria-current={lang===l?"true":undefined} onClick={()=>setLang(l)}>{l.toUpperCase()}</button>
                {i<3 && <span className="sep">·</span>}
              </React.Fragment>
            ))}
          </div>
        </div>
      </div>
      <div className="v3-foot-bottom">
        <span>{t.footer.copy}</span>
        <span>HCMC</span>
      </div>
    </div>
  </footer>
);

/* ============ Mobile bar ============ */
const V3MobileBar = () => (
  <div className="v3-mobile-bar">
    <a href={IOS_URL} target="_blank" rel="noopener">App Store</a>
    <a href={AND_URL} target="_blank" rel="noopener" className="is-green">Google Play</a>
  </div>
);

/* ============ Kinetic hooks (V3.1 Stage 1) ============ */

// Lenis smooth scroll — Apple-grade momentum
const useLenis = () => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    // Some UMD builds expose as window.Lenis.default
    const LenisCtor = window.Lenis && (window.Lenis.default || window.Lenis);
    if (!LenisCtor) return;

    const lenis = new LenisCtor({
      duration: 1.2,
      easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
      smoothWheel: true,
      smoothTouch: false,
      wheelMultiplier: 1
    });

    let rafId = 0;
    const raf = (time) => {
      lenis.raf(time);
      rafId = requestAnimationFrame(raf);
    };
    rafId = requestAnimationFrame(raf);

    return () => {
      cancelAnimationFrame(rafId);
      lenis.destroy();
    };
  }, []);
};

// Nav scroll-aware (>8px → frosted state) + Hero aurora parallax
const useScrollKinetics = () => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    let ticking = false;
    const onScroll = () => {
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(() => {
        const nav = document.querySelector(".v3-nav");
        if (nav) nav.classList.toggle("is-scrolled", window.scrollY > 8);

        const hero = document.querySelector(".v3-hero");
        if (hero) {
          const rect = hero.getBoundingClientRect();
          if (rect.bottom > 0 && rect.top < window.innerHeight) {
            const aurora = hero.querySelector(".v3-aurora");
            if (aurora) {
              const y = (-rect.top * 0.18).toFixed(2);
              aurora.style.setProperty("--aurora-y", y);
            }
          }
        }
        ticking = false;
      });
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
};

// Cursor 3D tilt on [data-tilt] elements
const useCursorTilt = (lang) => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) return;

    const MAX = 5; // max tilt degrees
    const FALLOFF = 1.4; // distance multiplier; >1 = pull effect even outside element bounds

    const findEls = () => Array.from(document.querySelectorAll("[data-tilt]"));
    let els = findEls();
    let frame = 0;
    let pending = false;

    const onMove = (e) => {
      if (pending) return;
      pending = true;
      frame = requestAnimationFrame(() => {
        els.forEach((el) => {
          const img = el.querySelector("img");
          if (!img) return;
          const rect = el.getBoundingClientRect();
          if (rect.width === 0) return;
          const cx = rect.left + rect.width / 2;
          const cy = rect.top + rect.height / 2;
          const dx = (e.clientX - cx) / (rect.width / 2);
          const dy = (e.clientY - cy) / (rect.height / 2);
          const dist = Math.hypot(dx, dy);
          if (dist > FALLOFF) {
            img.style.setProperty("--tilt-x", "0deg");
            img.style.setProperty("--tilt-y", "0deg");
            return;
          }
          img.style.setProperty("--tilt-x", (-dy * MAX).toFixed(2) + "deg");
          img.style.setProperty("--tilt-y", (dx * MAX).toFixed(2) + "deg");
        });
        pending = false;
      });
    };

    const onLeave = () => {
      els.forEach((el) => {
        const img = el.querySelector("img");
        if (!img) return;
        img.style.setProperty("--tilt-x", "0deg");
        img.style.setProperty("--tilt-y", "0deg");
      });
    };

    // re-collect after a short delay (img may not be in DOM yet on first paint)
    const refreshT = setTimeout(() => { els = findEls(); }, 200);

    window.addEventListener("mousemove", onMove, { passive: true });
    document.addEventListener("mouseleave", onLeave);
    return () => {
      clearTimeout(refreshT);
      cancelAnimationFrame(frame);
      window.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseleave", onLeave);
    };
  }, [lang]); // re-attach on language change (DOM rerenders new img elements)
};

// Apple-style scroll-bound phone entrance for [data-phone-reveal]
// Transform is scroll-bound (continuous), opacity is discrete (via .is-visible
// class + CSS transition) so the phone never sits at a translucent mid-state.
const usePhoneReveal = (lang) => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      document.querySelectorAll("[data-phone-reveal]").forEach(el => {
        el.style.setProperty("--phone-p", "1");
        el.classList.add("is-visible");
      });
      return;
    }

    let pending = false;
    let phones = Array.from(document.querySelectorAll("[data-phone-reveal]"));

    // IntersectionObserver flips .is-visible once the phone is meaningfully
    // in view (>= 15% visible) — opacity then fades 0 → 1 via CSS transition.
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && e.intersectionRatio > 0.15) {
          e.target.classList.add("is-visible");
        } else if (!e.isIntersecting) {
          const r = e.target.getBoundingClientRect();
          if (r.bottom < 0 || r.top > window.innerHeight) {
            e.target.classList.remove("is-visible");
          }
        }
      });
    }, { threshold: [0, 0.15, 0.3, 0.5], rootMargin: "0px 0px -5% 0px" });
    phones.forEach(p => io.observe(p));

    const update = () => {
      pending = false;
      const vh = window.innerHeight;
      phones.forEach((el) => {
        const r = el.getBoundingClientRect();
        // Begin entrance when element top crosses bottom of viewport,
        // fully revealed when element top reaches ~38% of viewport height
        const start = vh * 1.0;
        const end   = vh * 0.38;
        const range = start - end;
        const dist  = start - r.top;
        let p = dist / range;
        if (p < 0) p = 0;
        else if (p > 1) p = 1;
        el.style.setProperty("--phone-p", p.toFixed(3));
      });
    };

    const onScroll = () => {
      if (pending) return;
      pending = true;
      requestAnimationFrame(update);
    };

    // Late-mount catch (DOM may not be ready on first run)
    const refresh = setTimeout(() => {
      const fresh = Array.from(document.querySelectorAll("[data-phone-reveal]"));
      fresh.forEach(p => { if (!phones.includes(p)) io.observe(p); });
      phones = fresh;
      update();
    }, 200);

    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      clearTimeout(refresh);
      io.disconnect();
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, [lang]);
};

// Section indicator — Obys-style scroll-position number in mono small caps
const useSectionIndicator = (lang) => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    const labels = {
      vi: ["Chào bạn","Trở thành Provider","Đăng yêu cầu","Mua bán","Live Picks","Tin Sayyo","Bên trong app","Khu phố","Niềm tin","Tải app"],
      ko: ["환영합니다","Provider 되기","요청 올리기","중고거래","Live Picks","Sayyo 신뢰","앱 내부","동네","비교","앱 받기"],
      en: ["Welcome","Become Provider","Post a request","Buy & sell","Live Picks","Trust","Inside the app","Neighborhood","On Sayyo","Get the app"],
      jp: ["ようこそ","Providerになる","依頼を投稿","売買","Live Picks","信頼","アプリの中","近所","比較","アプリ取得"]
    };
    let pending = false;
    let lastIdx = -1;

    const update = () => {
      pending = false;
      const sections = Array.from(document.querySelectorAll(".v3-hero, .v3-way, .v3-picks, .v3-trust, .v3-inside, .v3-cats, .v3-comp, .v3-final"));
      if (!sections.length) return;
      const center = window.innerHeight / 2;
      let bestIdx = 0;
      let bestDist = Infinity;
      sections.forEach((s, i) => {
        const r = s.getBoundingClientRect();
        const sm = (r.top + r.bottom) / 2;
        const dist = Math.abs(sm - center);
        if (dist < bestDist) { bestDist = dist; bestIdx = i; }
      });
      if (bestIdx === lastIdx) return;
      lastIdx = bestIdx;
      const total = sections.length;
      const cur = document.querySelector(".v3-section-nav .curr");
      const lbl = document.querySelector(".v3-section-nav .label");
      const tot = document.querySelector(".v3-section-nav .total");
      if (cur) cur.textContent = String(bestIdx + 1).padStart(2, "0");
      if (tot) tot.textContent = String(total).padStart(2, "0");
      if (lbl) lbl.textContent = (labels[lang] || labels.en)[bestIdx] || "";
    };

    const onScroll = () => {
      if (pending) return;
      pending = true;
      requestAnimationFrame(update);
    };
    // Small initial delay so DOM is fully mounted
    const t = setTimeout(update, 200);
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      clearTimeout(t);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, [lang]);
};

const SectionIndicator = () => (
  <div className="v3-section-nav" aria-hidden="true">
    <span className="curr">01</span>
    <span className="sep">/</span>
    <span className="total">10</span>
    <span className="label">Chào bạn</span>
  </div>
);

// Custom cat cursor that follows the mouse pointer
const useCatCursor = () => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) return;

    document.body.classList.add("has-cat-cursor");

    const cursor = document.createElement("div");
    cursor.className = "cat-cursor";
    cursor.setAttribute("aria-hidden", "true");
    cursor.innerHTML = '<img class="cat-cursor-img" src="assets/cat.png" alt="" draggable="false"/>'
      + '<span class="cat-eyelid cat-eyelid-l"></span>'
      + '<span class="cat-eyelid cat-eyelid-r"></span>';
    document.body.appendChild(cursor);

    let mouseX = -100, mouseY = -100;
    let renderX = -100, renderY = -100;
    let rafId = 0;
    const lerp = (a, b, t) => a + (b - a) * t;
    const tick = () => {
      renderX = lerp(renderX, mouseX, 0.32);
      renderY = lerp(renderY, mouseY, 0.32);
      // Cat is 45×35; center on pointer
      cursor.style.transform = "translate3d(" + (renderX - 22) + "px, " + (renderY - 17) + "px, 0)";
      rafId = requestAnimationFrame(tick);
    };
    rafId = requestAnimationFrame(tick);

    const onMove = (e) => { mouseX = e.clientX; mouseY = e.clientY; };
    const onLeave = () => { cursor.style.opacity = "0"; };
    const onEnter = () => { cursor.style.opacity = "1"; };

    window.addEventListener("mousemove", onMove, { passive: true });
    document.addEventListener("mouseleave", onLeave);
    document.addEventListener("mouseenter", onEnter);

    return () => {
      cancelAnimationFrame(rafId);
      window.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseleave", onLeave);
      document.removeEventListener("mouseenter", onEnter);
      cursor.remove();
      document.body.classList.remove("has-cat-cursor");
    };
  }, []);
};

// Bidirectional scroll reveal — adds .in on enter, removes on leave
const useScrollReveal = (lang) => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) {
      document.querySelectorAll(".reveal-bi, .split-reveal").forEach(el => el.classList.add("in"));
      return;
    }
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          e.target.classList.add("in");
        } else {
          // Only un-reveal once it has fully left the viewport (top or bottom)
          const r = e.target.getBoundingClientRect();
          if (r.bottom < 0 || r.top > window.innerHeight) {
            e.target.classList.remove("in");
          }
        }
      });
    }, { threshold: [0, 0.15], rootMargin: "0px 0px -8% 0px" });

    let els = Array.from(document.querySelectorAll(".reveal-bi, .split-reveal"));
    els.forEach(el => io.observe(el));
    // Late-mount catch
    const refresh = setTimeout(() => {
      Array.from(document.querySelectorAll(".reveal-bi, .split-reveal")).forEach(el => {
        if (!els.includes(el)) io.observe(el);
      });
    }, 300);
    return () => { clearTimeout(refresh); io.disconnect(); };
  }, [lang]);
};

// Magnetic pull on [data-magnetic] elements
const useMagnetic = (lang) => {
  useEffect(() => {
    if (typeof window === "undefined") return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) return;

    const PROXIMITY = 80; // px around the element where pull engages
    const STRENGTH = 0.30;

    const findEls = () => Array.from(document.querySelectorAll("[data-magnetic]"));
    let els = findEls();
    let pending = false;

    const onMove = (e) => {
      if (pending) return;
      pending = true;
      requestAnimationFrame(() => {
        els.forEach((el) => {
          const rect = el.getBoundingClientRect();
          if (rect.width === 0) return;
          const cx = rect.left + rect.width / 2;
          const cy = rect.top + rect.height / 2;
          const dx = e.clientX - cx;
          const dy = e.clientY - cy;
          // shortest distance from element bounds
          const distX = Math.max(0, Math.abs(dx) - rect.width / 2);
          const distY = Math.max(0, Math.abs(dy) - rect.height / 2);
          const dist = Math.hypot(distX, distY);
          if (dist > PROXIMITY) {
            el.style.setProperty("--mx", "0px");
            el.style.setProperty("--my", "0px");
            return;
          }
          const t = 1 - dist / PROXIMITY;
          el.style.setProperty("--mx", (dx * STRENGTH * t).toFixed(2) + "px");
          el.style.setProperty("--my", (dy * STRENGTH * t).toFixed(2) + "px");
        });
        pending = false;
      });
    };

    const refreshT = setTimeout(() => { els = findEls(); }, 200);

    window.addEventListener("mousemove", onMove, { passive: true });
    return () => {
      clearTimeout(refreshT);
      window.removeEventListener("mousemove", onMove);
    };
  }, [lang]);
};

/* ============ App ============ */
const App = () => {
  const [lang, setLangState] = useState("vi");
  const setLang = (l) => {
    setLangState(l);
    document.documentElement.setAttribute("data-lang", l);
    document.documentElement.lang = l;
  };
  const t = I18N[lang];

  useLenis();
  useScrollKinetics();
  useCursorTilt(lang);
  useMagnetic(lang);
  useScrollReveal(lang);
  usePhoneReveal(lang);
  useCatCursor();

  return (
    <div data-lang={lang}>
      <V3Nav lang={lang} setLang={setLang}/>
      <V3Hero t={t} lang={lang}/>
      <V3Ways t={t} lang={lang}/>
      <V3Picks t={t} lang={lang}/>
      <V3Trust t={t} lang={lang}/>
      <V3Inside t={t} lang={lang}/>
      <V3Cats t={t} lang={lang}/>
      <V3Comp lang={lang}/>
      <V3Final t={t} lang={lang}/>
      <V3Foot t={t} lang={lang} setLang={setLang}/>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById("v3-root")).render(<App/>);
