// components-hero.jsx — Single-focus hero. Left: copy + CTAs. Right: hero image slot. // No more decorative quill SVG; the typewriter mock lives as a fallback inside the slot. function useTypewriter(paragraphs, opts = {}) { const { charDelay = 80, paraGap = 1400, restart = 0 } = opts; const [text, setText] = React.useState(""); const [paraIdx, setParaIdx] = React.useState(0); React.useEffect(() => { setText(""); setParaIdx(0); }, [restart]); React.useEffect(() => { if (!paragraphs.length) return; let active = true; let i = 0; const para = paragraphs[paraIdx % paragraphs.length]; function typeNext() { if (!active) return; if (i <= para.length) { setText(para.slice(0, i)); i++; const jitter = (Math.random() * 60) - 20; const last = para[i - 1]; const punctPause = /[,。、;:!??!,.;:]/.test(last) ? 240 : 0; setTimeout(typeNext, charDelay + jitter + punctPause); } else { setTimeout(() => { if (!active) return; setParaIdx((p) => p + 1); }, paraGap); } } const start = setTimeout(typeNext, 500); return () => { active = false; clearTimeout(start); }; }, [paraIdx, paragraphs, charDelay, paraGap]); return text; } function DownloadSplit({ copy }) { const [open, setOpen] = React.useState(false); const ref = React.useRef(null); React.useEffect(() => { function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); } document.addEventListener("mousedown", onDoc); return () => document.removeEventListener("mousedown", onDoc); }, []); return (
); } // Typewriter mock — shown as a styled fallback INSIDE the hero PicSlot // until the user drops in a real screenshot at uploads/hero.webp. function HeroProseFallback({ copy, lang }) { const txt = useTypewriter(copy.typewriter.paragraphs, { charDelay: lang === "zh" ? 95 : 35, paraGap: 1600, restart: lang, }); return ({txt}
{copy.hero.sub}
{src}
{lang === "zh"
? "上传一张真实写作界面截图替换此区域"
: "Drop a real product screenshot here"}