// Homepage
const { useState: useStateH, useRef: useRefH, useEffect: useEffectH } = React;

/* Shared project data (reused by Projects page) */
const PROJECTS = [
{ id: 1, name: "SignalRadar 出海产品机会情报雷达", nameParts: [{ t: "Signal" }, { t: "Radar", grad: true }, { t: "出海产品机会情报雷达", br: true }], subtitle: "持续追踪海外用户声音、开发者反馈和产品社区信号，自动发现正在升温的痛点、需求和产品机会。",
  cat: ["AI 产品"], tags: ["Next.js", "PostgreSQL", "Python ETL", "SBERT", "RAG", "LLM Agent", "LLM Workflow", "You.com Research API", "Playwright QA"], accent: "#17b3c4",
  desc: "SignalRadar 是一个面向出海产品和独立开发者场景的机会情报系统。它每天从海外社区、开发者平台、应用评论和产品发布渠道中采集真实用户反馈，通过自动化数据管线完成清洗、筛选、语义聚类、机会评分和趋势计算，最终将高噪声的碎片信息整理成可排序、可追踪、可调研的产品机会地图。智能调研模块用于对重点机会进行证据补充和分析解释，但产品核心是持续更新的机会情报数据库与趋势监控系统。",
  img: "机会情报看板 — 趋势与机会地图", mediaAspect: "4082 / 2160", mediaVer: "0607b", wallpaper: "assets/wallpaper/wp-signal.png", grad: "linear-gradient(135deg,#1f7fe0,#19c7c0 55%,#15d39a)" },
{ id: 2, name: "VoxLens AI 驱动的用户调研引擎", nameParts: [{ t: "Vox" }, { t: "Lens", grad: true }, { t: "AI 驱动的用户调研引擎", br: true }], subtitle: "不依赖问卷和访谈排期，用 LLM 流水线把 20+ 平台的海量用户声音转化为可决策的产品洞察",
  cat: ["用户调研"], tags: ["Python", "Playwright", "Flask API", "PostgreSQL", "LLM Pipeline", "Multi-Model Routing", "Prompt Engineering", "Data ETL"], accent: "#8b91d6",
  desc: "VoxLens 是一套我独立设计并落地的用户调研。我定义了从数据采集到洞察交付的完整调研框架：针对不同品类设计差异化的标注体系与痛点分类维度，制定四维质量评分标准控制进入分析环节的数据质量，为每个分析阶段设计 Prompt 与上下文策略并持续调优输出效果，根据任务特性完成模型选型与路由规则配置，搭建了覆盖 10+ 平台的多源数据采集能力。最终交付的不是原始数据，而是按品牌、痛点、人群、区域可切片、每条结论可溯源到原文 URL 的结构化调研资产，直接支撑产品决策。",
  img: "电商 — 桌面端与移动端店面", image: "assets/proj-voxlens-arch.png?v=2", wallpaper: "assets/wallpaper/wp-vox.jpg", grad: "linear-gradient(135deg,#cfd3e8,#aab0d6 60%,#9097cf)" },
{ id: 3, name: "《朱鹮》", mediaVer: "1", nameParts: [{ t: "《朱鹮》" }, { t: "AI 动画短片", br: true }], subtitle: "动画专业转 AI 的我，想知道现在的生成式工具到底能不能撑一个人做出一部完整的片。于是把大模型、文生图、图生视频、AI 配音配乐一路试过去，做出了这部 5 分钟的 AI 动画短片。",
  tags: ["Prompt Engineering", "Midjourney", "Nano Banana", "Claude", "DeepSeek", "MiniMax", "Seedance 2.0", "剪映"], accent: "#e0473a",
  desc: "我对新出的 AI 工具一直有「上手就想试」的习惯。真做起来才发现，难的不是某一张图好不好看，而是让同一只朱鹮在上百个镜头里保持一致、把零碎的生成片段剪成有节奏的故事，以及在一堆能力参差的工具里反复比较、选型、返工到能用。最后它确实被我一个人做完、剪成了片。这个过程也让我大致摸清了文、图、视频、音频几类生成模型各自能做什么、边界在哪。它不证明我懂某个具体业务，但能说明两件我觉得在 AI 时代下挺重要的事：我愿意主动折腾新东西，也能把一个想法真的落地做出来——而不是停在「听说过、用过」。",
  img: "《朱鹮》— AI 3D 动画短片", wallpaper: "assets/wallpaper/wp-zhuhuan.jpg", grad: "linear-gradient(135deg,#e8473a,#c4382f)", cat: ["AIGC 创作"], controls: true, poster: "assets/proj-3-poster.jpg?v=2" }];


const TAG_COLORS = ["#5fe39a", "#f59e6b", "#a98bff", "#5fb6ff", "#f47ab0"];

const TESTIMONIALS = [
{ q: "他们不只是设计界面，而是真正花时间理解我们的核心使命，把摩擦重重的流程转化为平静、直觉的用户旅程。一位以产品负责人思维去构建的设计师。", n: "Maya Chen", r: "OwnerWise 创始人" },
{ q: "始终对其专业与投入印象深刻。不仅设计功底扎实，合作起来也极为出色——任何团队都会因他而加分。", n: "Thurga Devi", r: "NorthStar Global 产品经理" },
{ q: "随叫随到，专业又乐于助人。他交付的设计真的很特别，强烈推荐——团队的宝贵财富。", n: "Omar Zaour", r: "MaskPay 负责人" },
{ q: "他的专业与条理令人印象深刻。几天之内落地页就上线并开始转化用户，速度与精度兼具，十分难得。", n: "Yuliia B.", r: "NoDressCode 创始人" },
{ q: "毫无疑问的顶尖人才——处理任务的方式令人惊艳。时间管理与可靠度俱佳，也很擅长带人，富有创意与资源整合力。", n: "A. Chhada", r: "AGS International CEO" },
{ q: "出色的体验设计师。擅长重塑用户体验，对设计系统的把控确保了产品的一致性与可扩展性。", n: "Ayman S.", r: "Helios 运营负责人" },
{ q: "任何疯狂的想法他都接得住——尽管把事情丢给他，放心他能搞定。期待再次合作，他正是每个初创团队所需要的人。", n: "M. Shihade", r: "AGS 商务拓展" }];


/* ----- Hero ----- */
/* ----- Contact icons + popovers ----- */
const CONTACTS = [
{ id: "mail", icon: "mail", label: "邮箱", value: "yanrongz52@gmail.com", action: "copy", hint: "点击复制邮箱地址" },
{ id: "phone", icon: "phone", label: "电话", value: "15219382905", action: "copy", hint: "点击复制电话号码" },
{ id: "wechat", icon: "wechat", label: "微信", value: "Yr-12071229", action: "copy", hint: "点击复制微信号" },
{ id: "xiaohongshu", icon: "xiaohongshu", label: "小红书", value: "你的小红书昵称", action: "copy", hint: "点击复制小红书账号" }];


function ContactRow() {
  const [open, setOpen] = useStateH(null);
  const [copied, setCopied] = useStateH(false);
  const wrapRef = useRefH(null);

  useEffectH(() => {
    function onDoc(e) {if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(null);}
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);

  function toggle(c) {
    setCopied(false);
    setOpen((o) => o === c.id ? null : c.id);
  }
  function copy(c) {
    try {navigator.clipboard.writeText(c.value);} catch (e) {}
    setCopied(true);
    setTimeout(() => setCopied(false), 1600);
  }

  return (
    <div ref={wrapRef} style={{ display: "flex", gap: 14, marginTop: 44, flexWrap: "wrap" }}>
      {CONTACTS.map((c) => {
        const active = open === c.id;
        return (
          <div key={c.id} style={{ position: "relative" }}>
            <button
              onClick={() => toggle(c)}
              aria-label={c.label}
              style={{
                width: 52, height: 52, borderRadius: 14, display: "grid", placeItems: "center",
                cursor: "pointer", color: active ? "#0a0a0b" : "var(--ink)",
                background: active ? "var(--green)" : "rgba(255,255,255,0.04)",
                border: active ? "1px solid var(--green)" : "1px solid rgba(255,255,255,0.12)",
                transition: "transform .18s ease, background .18s ease, color .18s ease, border-color .18s ease"
              }}
              onMouseEnter={(e) => {if (!active) e.currentTarget.style.background = "rgba(255,255,255,0.09)";e.currentTarget.style.transform = "translateY(-2px)";}}
              onMouseLeave={(e) => {if (!active) e.currentTarget.style.background = "rgba(255,255,255,0.04)";e.currentTarget.style.transform = "translateY(0)";}}>
              
              {I[c.icon]()}
            </button>
            {active &&
            <div style={{
              position: "absolute", top: "calc(100% + 12px)", left: 0, zIndex: 30, minWidth: 234,
              background: "#141416", border: "1px solid rgba(255,255,255,0.13)", borderRadius: 14,
              padding: "16px 18px", boxShadow: "0 18px 48px rgba(0,0,0,0.55)"
            }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, color: "var(--green)", marginBottom: 8 }}>
                  {I[c.icon]()}
                  <span style={{ fontSize: 12.5, fontWeight: 700, letterSpacing: "0.04em" }}>{c.label}</span>
                </div>
                <div style={{ fontSize: 16, fontWeight: 600, wordBreak: "break-all", lineHeight: 1.4 }}>{c.value}</div>
                <button
                onClick={() => copy(c)}
                style={{
                  marginTop: 14, width: "100%", padding: "9px 12px", borderRadius: 9, cursor: "pointer",
                  fontSize: 13, fontWeight: 700, border: "none",
                  background: copied ? "rgba(63,225,122,0.18)" : "var(--green)",
                  color: copied ? "var(--green)" : "#0a0a0b", transition: "background .18s ease, color .18s ease"
                }}>
                
                  {copied ? "✓ 已复制" : "复制" + c.label}
                </button>
              </div>}
          </div>);

      })}
    </div>);

}

function HeroBackdrop() {
  return (
    <div aria-hidden="true" style={{
      position: "absolute", top: 0, right: 0, height: "100%", width: "min(58%, 720px)",
      zIndex: 0, pointerEvents: "none", overflow: "hidden",
      WebkitMaskImage: "linear-gradient(to bottom, #000 84%, transparent 99%)",
      maskImage: "linear-gradient(to bottom, #000 84%, transparent 99%)"
    }}>
      <img src="assets/hero-astronaut.png" alt="" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", objectPosition: "var(--hero-x, 83%) var(--hero-y, 61%)", transform: "scale(var(--hero-scale, 1.05))", transformOrigin: "var(--hero-x, 83%) var(--hero-y, 61%)", display: "block" }} />
      {/* left scrim keeps the headline crisp; right + top fades melt the image edges into the page */}
      <div style={{ position: "absolute", inset: 0, background: "linear-gradient(to right, #0a0a0b 0%, #0a0a0b 12%, rgba(10,10,11,0.5) 40%, rgba(10,10,11,0) 66%), linear-gradient(to left, #0a0a0b 0%, rgba(10,10,11,0) 10%), linear-gradient(to top, #0a0a0b 0%, rgba(10,10,11,0) 8%)" }} />
    </div>);

}

function Hero() {
  return (
    <section style={{ position: "relative", minHeight: "clamp(640px, 82vh, 900px)", padding: "0 0 40px", letterSpacing: "0.6px" }}>
      {/* text + image are capped together as one tight unit so they never drift apart on wide screens */}
      <div style={{ position: "relative", maxWidth: 1340, margin: "0 auto", minHeight: "inherit", display: "flex", alignItems: "center" }}>
        <HeroBackdrop />
        <div style={{ position: "relative", zIndex: 2, maxWidth: 720 }}>
          <Reveal><Badge>可随时到岗—稳定实习3-6个月</Badge></Reveal>
          <Reveal delay={80}>
            <h1 className="display" style={{ marginTop: 28, maxWidth: 720, lineHeight: "1", fontSize: "57px" }}>
              从搭 AI 系统到做出业务判断，<br />我一个人就能跑通
            </h1>
          </Reveal>
          <Reveal delay={150}>
            <div style={{ marginTop: 32 }}>
              <p style={{ fontSize: 21, fontWeight: 600, lineHeight: 1.5, letterSpacing: "-0.01em" }}>
                <span style={{ color: "var(--green)", lineHeight: "1.2" }}>你好，我是郑彦荣。</span><br />
                我懂 AI 怎么落地，也懂它对实际业务意味着什么。
              </p>
              <p style={{ marginTop: 18, maxWidth: 480, fontSize: "clamp(15px, 1.05vw, 17px)", lineHeight: 1.85, color: "var(--muted)" }}>
                我独立搭过两套 AI 研究系统，处理过千万+条真实用户声音，从数据采集、质量打分到 LLM 标注、机会聚类，整条流水线都是我自己设计和写出来的。但系统只是手段，我更在意用它把杂乱的信号变成能进决策的东西——竞品该怎么打、产品该做什么、哪些先做、哪些放弃。比起堆单点技能，我习惯把一个问题拆成能跑通、可复用、每一步都说得清为什么的系统，再一个人把它从想清楚做到落地。现在想找一段能深度参与、把好想法真正做出来的机会。
              </p>
            </div>
          </Reveal>
          <Reveal delay={220}>
            <ContactRow />
          </Reveal>
        </div>
      </div>
    </section>);

}

/* email button that copies on click, toggles label */
function CopyEmailBtn({ green }) {
  const [copied, setCopied] = useStateH(false);
  const onClick = () => {
    navigator.clipboard?.writeText("yanrongz52@gmail.com").catch(() => {});
    setCopied(true);
    setTimeout(() => setCopied(false), 1800);
  };
  return (
    <Btn variant={green ? "green" : "solid"} dot={green && !copied} onClick={onClick}>
      {copied ? "已复制 — yanrongz52@gmail.com" : "预约 30 分钟免费沟通"}
    </Btn>);

}

/* ----- Tag pill ----- */
function Pill({ children, i }) {
  const c = TAG_COLORS[i % TAG_COLORS.length];
  return (
    <span style={{
      padding: "6px 13px", borderRadius: 100, fontSize: 12.5, fontWeight: 600, letterSpacing: "0.01em", whiteSpace: "nowrap",
      color: c, background: `color-mix(in srgb, ${c} 13%, transparent)`,
      border: `1px solid color-mix(in srgb, ${c} 30%, transparent)`
    }}>{children}</span>);

}

/* ----- Media frame: looping demo video, falls back to a placeholder ----- */
function MediaFrame({ p }) {
  const [attempt, setAttempt] = useStateH(0);
  const [failed, setFailed] = useStateH(false);
  const triesRef = useRefH(0);
  if (p.image) {
    return (
      <img src={p.image} alt={p.name} draggable={false}
      style={{ width: "100%", height: "100%", objectFit: "cover", display: "block", background: "#0d0d10" }} />);

  }
  if (!failed) {
    // Render the real video in the DOM so it loads at normal priority. A first
    // error (e.g. transient stall while the page hammers the network on load)
    // retries a few times before we give up and show the placeholder.
    // A project flagged `controls` is a real film: show a poster and let the user
    // press play (with sound), seek, mute and go fullscreen — no autoplay/loop,
    // and only fetch metadata until they actually press play.
    const playback = p.controls
      ? { controls: true, poster: p.poster, preload: "metadata" }
      : { autoPlay: true, muted: true, loop: true, preload: "auto",
          onLoadedData: (e) => { const v = e.currentTarget; if (v.paused) v.play().catch(() => {}); } };
    return (
      <video
        key={attempt}
        src={`assets/proj-${p.id}.mp4${p.mediaVer ? "?v=" + p.mediaVer : ""}`}
        playsInline
        controlsList="nodownload"
        disablePictureInPicture
        onContextMenu={(e) => e.preventDefault()}
        {...playback}
        onError={() => {
          if (triesRef.current < 3) { triesRef.current += 1; setTimeout(() => setAttempt((a) => a + 1), 700); }
          else setFailed(true);
        }}
        style={{ width: "100%", height: "100%", objectFit: "cover", display: "block", background: "#0d0d10" }} />);

  }
  return (
    <div style={{ width: "100%", height: "100%", display: "grid", placeItems: "center", background: `radial-gradient(120% 90% at 50% 38%, color-mix(in srgb, ${p.accent} 18%, #131318), #0e0e12 70%)` }}>
        <div style={{ textAlign: "center", color: "var(--muted-2)", fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.04em", textTransform: "uppercase", padding: "0 20px", lineHeight: 1.7 }}>
          <div style={{ width: 56, height: 56, margin: "0 auto 16px", borderRadius: "50%", display: "grid", placeItems: "center", background: "rgba(255,255,255,0.08)", border: "1px solid rgba(255,255,255,0.16)", color: "#fff", fontSize: 18, paddingLeft: 3 }}>▶</div>
          <div style={{ color: "var(--muted)" }}>{p.img}</div>
          <div style={{ marginTop: 8, opacity: 0.6 }}>将演示视频放到 assets/proj-{p.id}.mp4</div>
        </div>
      </div>);

}

/* ----- Project showcase ----- */
function ProjectCard({ p, idx, actions }) {
  const [hover, setHover] = useStateH(false);
  return (
    <Reveal>
      <article style={{ display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", width: "100%" }}>
        <h3 className="display" style={{ fontSize: "56px", fontWeight: "800", letterSpacing: "-1.7px", textAlign: "center", lineHeight: 1.12 }}>{p.nameParts ? p.nameParts.map((s, i) => <React.Fragment key={i}>{s.br ? <br /> : null}{s.grad ? <span style={{ background: "linear-gradient(180deg,#7aa0ff 0%,#3b63f0 100%)", WebkitBackgroundClip: "text", backgroundClip: "text", color: "transparent" }}>{s.t}</span> : <span>{s.t}</span>}</React.Fragment>) : p.name}</h3>
        {p.subtitle ? <p style={{ marginTop: 15, fontSize: 19, lineHeight: 1.55, color: "var(--muted)", maxWidth: 720, textWrap: "pretty" }}>{p.subtitle}</p> : null}
        {/* tags */}
        <div style={{ display: "flex", gap: 9, justifyContent: "center", flexWrap: "wrap", marginTop: 22 }}>
          {p.tags.map((t, i) => <Pill key={t} i={i}>{t}</Pill>)}
        </div>
        {/* framed demo video — full width, cinematic 16:9 */}
        <div
          onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
          style={{
            marginTop: 50, width: "100%", borderRadius: 30, background: p.wallpaper ? `url(${p.wallpaper}) center / cover no-repeat` : p.grad,
            padding: "clamp(20px,3vw,52px)", position: "relative", overflow: "hidden",
            transform: hover ? "translateY(-5px)" : "none", transition: "transform .4s cubic-bezier(.16,.84,.44,1)",
            boxShadow: hover ? `0 50px 110px ${p.accent}26` : "0 30px 70px rgba(0,0,0,0.4)", fontSize: "16px"
          }}>
          
          <div style={{ borderRadius: 14, overflow: "hidden", aspectRatio: p.mediaAspect || "16 / 9", background: "linear-gradient(180deg,#17171d,#0e0e12)", boxShadow: "0 40px 90px rgba(0,0,0,0.55), 0 2px 0 rgba(255,255,255,0.14) inset" }}>
            <MediaFrame p={p} />
          </div>
        </div>
        {/* description */}
        <p style={{ marginTop: 40, fontSize: 18, lineHeight: 1.75, color: "#c7c7cc", maxWidth: 1000, textWrap: "pretty" }}>{p.desc}</p>
        {/* actions */}
        <div style={{ display: "flex", gap: 12, justifyContent: "center", marginTop: 26, flexWrap: "wrap" }}>
          {actions || (
            <React.Fragment>
              <Btn variant="solid" icon="arrow">查看案例</Btn>
              <Btn variant="outline">在线预览</Btn>
            </React.Fragment>)}
        </div>
      </article>
    </Reveal>);

}

/* ----- Tools & Softwares: app-icon wall of the AI products I use daily ----- */
const TOOLS = [
{ id: "claudecode", name: "Claude Code", bg: "#ffffff", pad: 18 },
{ id: "codex", name: "Codex", bg: "transparent", pad: 0 },
{ id: "claude", name: "Claude", bg: "transparent", pad: 0 },
{ id: "chatgpt", name: "ChatGPT", bg: "transparent", pad: 0 },
{ id: "gemini", name: "Gemini", bg: "#ffffff", pad: 18 },
{ id: "cursor", name: "Cursor", bg: "#0d0d0f", pad: 6, filter: "brightness(0.62) contrast(1.08)" },
{ id: "perplexity", name: "Perplexity", bg: "transparent", pad: 0 },
{ id: "openclaw", name: "OpenClaw", bg: "#ffffff", pad: 16 },
{ id: "figma", name: "Figma", bg: "#ffffff", pad: 18 }];

const TOOL_CARD_W = 118;
const TOOL_GAP = 18;

function ToolCard({ t }) {
  const [hover, setHover] = useStateH(false);
  return (
    <div
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        flex: "0 0 auto", width: TOOL_CARD_W, marginRight: TOOL_GAP, boxSizing: "border-box",
        display: "flex", flexDirection: "column", alignItems: "center", gap: 13,
        padding: "16px 10px 18px", borderRadius: 18, border: "1px solid var(--line)",
        background: hover ? "rgba(255,255,255,0.045)" : "rgba(255,255,255,0.018)",
        transform: hover ? "translateY(-4px)" : "none", transition: "transform .35s cubic-bezier(.16,.84,.44,1), background .25s"
      }}>
      <span style={{ width: 7, height: 7, borderRadius: "50%", background: "var(--muted-2)", opacity: 0.7 }} />
      <div style={{
        width: 80, aspectRatio: "1", borderRadius: 20, background: t.bg, overflow: "hidden",
        boxShadow: "0 10px 26px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.05) inset"
      }}>
        <img src={`assets/tools/${t.id}.png`} alt={t.name} draggable={false}
        style={{ width: "100%", height: "100%", objectFit: t.pad ? "contain" : "cover", padding: t.pad, display: "block", filter: t.filter || "none" }} />
      </div>
      <span style={{ fontSize: 14, color: "var(--muted)", letterSpacing: "0.01em", whiteSpace: "nowrap" }}>{t.name}</span>
    </div>);

}

function TechStack() {
  return (
    <section style={{ paddingTop: 96 }}>
      <Reveal>
        <div style={{ textAlign: "center", marginBottom: 8 }}></div>
      </Reveal>
      <Reveal delay={80}>
        <div style={{
          maxWidth: 7 * (TOOL_CARD_W + TOOL_GAP) + 2, margin: "0 auto", borderRadius: 24, border: "1px solid var(--line)", background: "var(--panel)",
          overflow: "hidden"
        }}>
          <div style={{
            textAlign: "center", padding: "16px 20px", borderBottom: "1px solid var(--line)",
            fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.16em", color: "var(--muted-2)", textTransform: "uppercase"
          }}>
            我当前的工具栈
          </div>
          <div className="tool-marquee-mask" style={{ overflow: "hidden", padding: "24px 0" }}>
            <div className="tool-marquee-track" style={{ display: "flex", width: "max-content", paddingLeft: TOOL_GAP }}>
              {[...TOOLS, ...TOOLS].map((t, i) => <ToolCard key={t.id + "-" + i} t={t} />)}
            </div>
          </div>
        </div>
      </Reveal>
      <style>{`
        @keyframes toolScroll { from { transform: translateX(0); } to { transform: translateX(-${TOOLS.length * (TOOL_CARD_W + TOOL_GAP)}px); } }
        .tool-marquee-track { animation: toolScroll 34s linear infinite; }
        .tool-marquee-mask:hover .tool-marquee-track { animation-play-state: paused; }
        @media (prefers-reduced-motion: reduce) { .tool-marquee-track { animation: none; } }
      `}</style>
    </section>);

}

/* ----- Featured projects ----- */
function IsoBlocks({ size = 60, color = "rgba(255,255,255,0.4)", sw = 1.25 }) {
  const bg = "var(--bg)";
  const ox = 32,oy = 44,a = 6.2,b = 3.2,c = 5.4;
  const P = (gx, gy, gz) => [ox + (gx - gy) * a, oy + (gx + gy) * b - gz * c];
  const pt = ([x, y]) => `${x.toFixed(2)} ${y.toFixed(2)}`;
  const quad = (p0, p1, p2, p3) => `M ${pt(p0)} L ${pt(p1)} L ${pt(p2)} L ${pt(p3)} Z`;
  const Box = ({ x0, y0, x1, y1, zb, zt }, key) =>
  <g key={key} fill={bg} stroke={color} strokeWidth={sw} strokeLinejoin="round" strokeLinecap="round">
      <path d={quad(P(x1, y0, zb), P(x1, y1, zb), P(x1, y1, zt), P(x1, y0, zt))} />
      <path d={quad(P(x0, y1, zb), P(x1, y1, zb), P(x1, y1, zt), P(x0, y1, zt))} />
      <path d={quad(P(x0, y0, zt), P(x1, y0, zt), P(x1, y1, zt), P(x0, y1, zt))} />
    </g>;
  // cluster of growth columns [x0,y0,x1,y1,height] — drawn back-to-front
  const cols = [
  [0.00, 0.00, 0.92, 0.92, 4.6],
  [1.08, 0.00, 2.00, 0.92, 3.8],
  [0.00, 1.08, 0.92, 2.00, 3.4],
  [0.58, 0.58, 1.42, 1.42, 5.4],
  [1.08, 1.08, 2.00, 2.00, 3.0]];

  return (
    <svg width={size} height={size} viewBox="0 0 64 64" fill="none" aria-hidden="true" style={{ strokeWidth: "69px", width: "167px", height: "154px" }}>
      {cols.map((v, i) => Box({ x0: v[0], y0: v[1], x1: v[2], y1: v[3], zb: 0, zt: v[4] }, i))}
    </svg>);
}

/* SignalRadar (项目一) 的链接 — 占位，待替换为真实 URL */
const SIGNAL_LINKS = { preview: "https://signalradar.yrzz.top", notes: "案例 - SignalRadar 思考复盘.html", github: "https://github.com/yanrongz52-create/opportunity-research-kit" };

function SignalActions() {
  return (
    <React.Fragment>
      <Btn variant="solid" icon="arrow" href={SIGNAL_LINKS.preview || undefined}>在线预览</Btn>
      <Btn variant="outline" onClick={() => { try { saveReturnScroll(); } catch (e) {} window.location.href = SIGNAL_LINKS.notes; }}>思考复盘</Btn>
      <Btn variant="outline" icon="github" href={SIGNAL_LINKS.github || undefined}>GitHub</Btn>
    </React.Fragment>);

}

function VoxActions({ open, onToggle }) {
  return (
    <React.Fragment>
      <Btn variant="solid" icon="arrow" onClick={onToggle}>{open ? "收起案例" : "查看案例"}</Btn>
    </React.Fragment>);

}

function Featured() {
  const [voxOpen, setVoxOpen] = useStateH(true);
  const toggleVox = () => setVoxOpen((o) => !o);
  return (
    <section style={{ paddingTop: 64 }}>
      <Reveal>
        <div style={{ textAlign: "center", marginBottom: 24 }}>
          <h2 className="display" style={{
            letterSpacing: "0.2px", lineHeight: "0", fontSize: "58px", textAlign: "center"
          }}>我亲手做出来的项目</h2>
          <div style={{ marginTop: 22 }}>
            <IsoBlocks />
          </div>
        </div>
      </Reveal>
      <div style={{ display: "flex", flexDirection: "column", gap: "clamp(76px,9vw,124px)" }}>
        {PROJECTS.slice(0, 3).map((p, i) =>
        p.id === 1 ?
        <ProjectCard key={p.id} p={p} idx={i} actions={<SignalActions />} /> :
        p.id === 2 ?
        <div key={p.id} style={{ display: "flex", flexDirection: "column" }}>
            <ProjectCard p={p} idx={i} actions={<VoxActions open={voxOpen} onToggle={toggleVox} />} />
            {window.VoxLensShowcase && <window.VoxLensShowcase open={voxOpen} onToggle={toggleVox} />}
          </div> :
        <ProjectCard key={p.id} p={p} idx={i} actions={<React.Fragment />} />
        )}
      </div>
    </section>);

}

/* ----- Testimonials marquee ----- */
function TCard({ t }) {
  return (
    <div style={{
      flex: "0 0 380px", width: 380, background: "var(--panel)", border: "1px solid var(--line)",
      borderRadius: 18, padding: "24px 24px 22px", display: "flex", flexDirection: "column", gap: 18
    }}>
      <p style={{ fontSize: 14.5, lineHeight: 1.62, color: "#d7d7da" }}>“{t.q}”</p>
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginTop: "auto" }}>
        <div style={{ width: 40, height: 40, borderRadius: "50%", overflow: "hidden", flex: "0 0 40px" }}>
          <Placeholder label="" accent="#2c2c30" radius={40} />
        </div>
        <div>
          <div style={{ fontSize: 14, fontWeight: 600 }}>{t.n}</div>
          <div style={{ fontSize: 12.5, color: "var(--muted)" }}>{t.r}</div>
        </div>
      </div>
    </div>);

}

function Testimonials() {
  const row = [...TESTIMONIALS, ...TESTIMONIALS];
  return (
    <section style={{ paddingTop: 96 }}>
      <Reveal>
        <h2 className="display" style={{ fontSize: "clamp(30px,3.6vw,46px)", marginBottom: 40 }}>暖心的评价，<br />更暖心的人</h2>
      </Reveal>
      <Reveal>
        <div style={{ position: "relative", overflow: "hidden", margin: "0 -40px", padding: "0 40px",
          maskImage: "linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent)",
          WebkitMaskImage: "linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent)" }}>
          <div style={{ display: "flex", gap: 18, width: "max-content", animation: "marquee 48s linear infinite" }}
          onMouseEnter={(e) => e.currentTarget.style.animationPlayState = "paused"}
          onMouseLeave={(e) => e.currentTarget.style.animationPlayState = "running"}>
            {row.map((t, i) => <TCard key={i} t={t} />)}
          </div>
        </div>
      </Reveal>
    </section>);

}

/* ----- Photography — personal interest section (方案 B: two opposing rows, mixed ratios, drop-in photos) ----- */
const PH_H = 224; // uniform tile height; widths derive from orientation
const PH_W = { land: 396, port: 150 };
const PHOTOS_ROW1 = [
  { src: "assets/photography/p1.jpg", shape: "land", cap: "纳凉·林间" },
  { src: "assets/photography/p10.jpg", shape: "port", cap: "静水鹤影" },
  { src: "assets/photography/p18.jpg", shape: "land", cap: "桥下橙影" },
  { src: "assets/photography/p11.jpg", shape: "port", cap: "林溪渡舟" },
  { src: "assets/photography/p12.jpg", shape: "port", cap: "枝上群猫" },
  { src: "assets/photography/p20.jpg", shape: "land", cap: "树下对弈" },
  { src: "assets/photography/p2.jpg", shape: "port", cap: "牵手回家" },
  { src: "assets/photography/p3.jpg", shape: "port", cap: "暮色公交" },
  { src: "assets/photography/p21.jpg", shape: "land", cap: "屋顶之上" },
  { src: "assets/photography/p13.jpg", shape: "port", cap: "雨后凝望" },
  { src: "assets/photography/p14.jpg", shape: "port", cap: "黄村站台" },
  { src: "assets/photography/p25.jpg", shape: "land", cap: "暮云一隅" },
  { src: "assets/photography/p5.jpg", shape: "port", cap: "林间橘猫" },
  { src: "assets/photography/p27.jpg", shape: "port", cap: "巷口独坐" },
  { src: "assets/photography/p28.jpg", shape: "port", cap: "晚霞渔港" }];

const PHOTOS_ROW2 = [
  { src: "assets/photography/p7.jpg", shape: "land", cap: "中大校训" },
  { src: "assets/photography/p15.jpg", shape: "port", cap: "天下为公" },
  { src: "assets/photography/p19.jpg", shape: "land", cap: "夜半屏前" },
  { src: "assets/photography/p16.jpg", shape: "port", cap: "五羊石像" },
  { src: "assets/photography/p17.jpg", shape: "port", cap: "花间小憩" },
  { src: "assets/photography/p22.jpg", shape: "land", cap: "街边舒展" },
  { src: "assets/photography/p6.jpg", shape: "port", cap: "栈道父子" },
  { src: "assets/photography/p8.jpg", shape: "port", cap: "红砖旧舍" },
  { src: "assets/photography/p4.jpg", shape: "land", cap: "影下独行" },
  { src: "assets/photography/p23.jpg", shape: "port", cap: "祖国母校" },
  { src: "assets/photography/p24.jpg", shape: "port", cap: "凭栏之人" },
  { src: "assets/photography/p26.jpg", shape: "port", cap: "此刻·记录" },
  { src: "assets/photography/p9.jpg", shape: "port", cap: "池畔双鹤" },
  { src: "assets/photography/p29.jpg", shape: "land", cap: "夜色厂区" }];

function PhotoTile({ p }) {
  const w = PH_W[p.shape];
  return (
    <figure style={{ position: "relative", margin: 0, width: w, height: PH_H, flex: `0 0 ${w}px`,
      borderRadius: 12, overflow: "hidden", border: "1px solid rgba(255,255,255,0.08)", background: "#151517" }}>
      <img src={p.src} alt={p.cap} loading="lazy"
        style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
    </figure>);

}

function PhotoRow({ photos, anim }) {
  const row = [...photos, ...photos];
  return (
    <div style={{ position: "relative", overflow: "hidden", margin: "0 -40px", padding: "0 40px",
      maskImage: "linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent)",
      WebkitMaskImage: "linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent)" }}>
      <div style={{ display: "flex", gap: 14, width: "max-content", animation: anim }}
        onMouseEnter={(e) => e.currentTarget.style.animationPlayState = "paused"}
        onMouseLeave={(e) => e.currentTarget.style.animationPlayState = "running"}>
        {row.map((p, i) => <PhotoTile key={p.id + "-" + i} p={p} />)}
      </div>
    </div>);

}

function Photography() {
  return (
    <section style={{ paddingTop: 96 }}>
      <Reveal>
        <div style={{ fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--green)", marginBottom: 14 }}>Photography · 兴趣</div>
        <h2 className="display" style={{ fontSize: "clamp(30px,3.6vw,46px)", lineHeight: 1.12 }}>镜头之外，<br />我也在按快门</h2>
        <p style={{ marginTop: 16, fontSize: 16, lineHeight: 1.7, color: "var(--muted)", maxWidth: 560 }}>工作之余，我用相机记录下的一些光影与瞬间。</p>
      </Reveal>
      <Reveal>
        <div style={{ display: "flex", flexDirection: "column", gap: 14, marginTop: 38 }}>
          <PhotoRow photos={PHOTOS_ROW1} anim="phMarqueeL 52s linear infinite" />
          <PhotoRow photos={PHOTOS_ROW2} anim="phMarqueeR 58s linear infinite" />
        </div>
      </Reveal>
      <style>{`
        @keyframes phMarqueeL { from { transform: translateX(0); } to { transform: translateX(-50%); } }
        @keyframes phMarqueeR { from { transform: translateX(-50%); } to { transform: translateX(0); } }
        @media (prefers-reduced-motion: reduce) { [style*="phMarquee"] { animation: none !important; } }
      `}</style>
    </section>);

}

/* ----- CTA: "let's build something meaningful together" (adapted from the light reference into the dark theme) ----- */
function ContactCard({ icon, label, value, copyValue, actions }) {
  const [copied, setCopied] = useStateH(false);
  function doCopy() {
    try {navigator.clipboard.writeText(copyValue || value);} catch (e) {}
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  }
  return (
    <div style={{
      display: "flex", alignItems: "center", justifyContent: "space-between", gap: 18, flexWrap: "wrap",
      background: "rgba(255,255,255,0.035)", border: "1px solid rgba(255,255,255,0.10)",
      borderRadius: 18, padding: "18px 22px"
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 13, minWidth: 0 }}>
        <span style={{ color: "var(--green)", display: "grid", placeItems: "center", flex: "0 0 auto" }}>{I[icon]()}</span>
        <span style={{ fontSize: 16.5, fontWeight: 600, color: "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{value}</span>
        <button onClick={doCopy} aria-label="复制" style={{
          flex: "0 0 auto", width: 26, height: 26, borderRadius: 7, cursor: "pointer", display: "grid", placeItems: "center",
          background: "transparent", border: "none", color: copied ? "var(--green)" : "var(--muted-2)", transition: "color .16s ease"
        }}>{copied ? I.check() : I.copy()}</button>
      </div>
      <div style={{ display: "flex", gap: 10, flex: "0 0 auto" }}>
        {actions.map((a) =>
        <a key={a.label} href={a.href || "#"} target={a.href ? "_blank" : undefined} rel="noreferrer"
        style={{
          display: "inline-flex", alignItems: "center", gap: 7, padding: "9px 16px", borderRadius: 11,
          fontSize: 14, fontWeight: 700, whiteSpace: "nowrap", cursor: "pointer", transition: "background .16s ease, color .16s ease, border-color .16s ease",
          ...(a.primary ?
          { background: "var(--green)", color: "#0a0a0b", border: "1px solid var(--green)" } :
          { background: "transparent", color: "var(--text)", border: "1px solid rgba(255,255,255,0.18)" })
        }}
        onMouseEnter={(e) => {if (!a.primary) e.currentTarget.style.background = "rgba(255,255,255,0.08)";}}
        onMouseLeave={(e) => {if (!a.primary) e.currentTarget.style.background = "transparent";}}>
            {a.label}{a.icon && <span style={{ display: "grid", placeItems: "center" }}>{I[a.icon]()}</span>}
          </a>
        )}
      </div>
    </div>);

}

const BUILT_WITH = [
{ name: "Claude Design", top: "CLAUDE", accent: "#D97757", kind: "claude", postmark: true },
{ name: "Claude", top: "ANTHROPIC", accent: "#D97757", kind: "claude" },
{ name: "Codex", top: "OPENAI", accent: "#10a37f", kind: "codex" }];


function ToolStamp({ tool, i }) {
  // page-colour dots punched over warm paper → perforation shows only in the 5px frame ring
  const PAPER = "#e9e6df";
  const perf = `radial-gradient(circle at center, var(--bg) 0 2.1px, transparent 2.1px) 0 0 / 8px 8px, ${PAPER}`;
  return (
    <div style={{ position: "relative", transform: `rotate(${i % 2 ? 2 : -2.5}deg)`, filter: "drop-shadow(0 12px 24px rgba(0,0,0,0.5))" }}>
      <div style={{ width: 108, padding: 5, background: perf, borderRadius: 2 }}>
        <div style={{ background: PAPER, padding: "9px 9px 11px", position: "relative" }}>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 8, letterSpacing: "0.14em", color: "#8a8780", textAlign: "left", marginBottom: 8 }}>
            {tool.top}
          </div>
          <div style={{ width: 62, height: 62, margin: "0 auto", borderRadius: 15, display: "grid", placeItems: "center", background: `radial-gradient(125% 125% at 30% 22%, color-mix(in srgb, ${tool.accent} 26%, #fff), color-mix(in srgb, ${tool.accent} 70%, #fff))` }}>
            {tool.kind === "codex" ?
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 20, fontWeight: 700, color: "#fff" }}>{">_"}</span> :
            <svg viewBox="0 0 24 24" width="32" height="32" fill="#fff"><path d="M12 1.4c.52 3.05 1.05 4.55 2.1 5.6s2.55 1.58 5.6 2.1c-3.05.52-4.55 1.05-5.6 2.1s-1.58 2.55-2.1 5.6c-.52-3.05-1.05-4.55-2.1-5.6S5.85 9.62 2.8 9.1c3.05-.52 4.55-1.05 5.6-2.1S11.48 4.45 12 1.4Z" /></svg>}
          </div>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 9.5, fontWeight: 600, letterSpacing: "0.01em", color: "#3a382f", textAlign: "center", marginTop: 9 }}>
            {tool.name}
          </div>
        </div>
      </div>
      {tool.postmark &&
      <svg viewBox="0 0 70 60" width="70" height="60" style={{ position: "absolute", top: -6, left: -14, color: "rgba(255,255,255,0.32)", pointerEvents: "none" }} fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round">
        <path d="M2 14c6-4 10 4 16 0s10 4 16 0 10 4 16 0" />
        <path d="M2 20c6-4 10 4 16 0s10 4 16 0 10 4 16 0" />
        <path d="M4 46c6-4 10 4 16 0s10 4 16 0 10 4 16 0" transform="translate(0 0)" />
        <path d="M4 52c6-4 10 4 16 0s10 4 16 0 10 4 16 0" />
      </svg>}
    </div>);

}

function CTA() {
  return (
    <section style={{ paddingTop: 100 }}>
      <Reveal>
        <div style={{ borderRadius: 28, border: "1px solid var(--line)", background: "var(--panel)", padding: "clamp(34px,4vw,58px)", fontSize: "17px" }}>
          <div style={{ display: "grid", gridTemplateColumns: "minmax(260px, 0.82fr) minmax(0, 1.18fr)", gap: "clamp(32px,4vw,64px)", alignItems: "center" }}>
            {/* left: heading + book a call */}
            <div>
              <span style={{ color: "var(--green)", display: "inline-flex" }}>{I.handshake()}</span>
              <h2 className="display" style={{ fontSize: "clamp(34px,3.4vw,52px)", lineHeight: 1.08, marginTop: 26 }}>
                一起做点<br />有意义的事。
              </h2>
            </div>
            {/* right: stacked contact cards */}
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              <ContactCard icon="phone" value="15219382905" copyValue="15219382905"
              actions={[{ label: "拨打", href: "tel:15219382905" }]} />
              <ContactCard icon="mail" value="yanrongz52@gmail.com" copyValue="yanrongz52@gmail.com"
              actions={[{ label: "发邮件", href: "mailto:yanrongz52@gmail.com", primary: true }]} />
              <ContactCard icon="wechat" value="Yr-12071229" copyValue="Yr-12071229"
              actions={[{ label: "复制微信号", primary: true }]} />
            </div>
          </div>
        </div>
      </Reveal>
    </section>);

}

/* ----- Footer ----- */
function Footer() {
  return (
    <footer style={{ paddingTop: 84, paddingBottom: 48 }}>
      <div style={{ display: "grid", gridTemplateColumns: "minmax(0,1fr) auto", gap: "clamp(32px,5vw,64px)", alignItems: "start" }}>
        {/* left: stamps + built-with caption */}
        <div>
          <div style={{ marginBottom: 30 }}>
            <img src="assets/built-stamps.png?v=3" alt="Claude Code 与 Claude Design 邮票" style={{ display: "block", height: "226px", width: "296px", opacity: "1" }} />
          </div>
          <p style={{ letterSpacing: "0.01em", fontFamily: "Inter", fontSize: "26px", color: "rgb(170, 170, 209)" }}>这个网站，我一个人独立设计并搭建</p>
          <p className="display" style={{ marginTop: 14, fontFamily: "\"Noto Sans SC\"", fontSize: "32px", lineHeight: "1.22" }}>
            Claude Code<br />与 Claude Design，约 24 小时
          </p>
        </div>
        {/* right: sign-off wordmark — two lines, oblique, right-aligned (1:1 with reference) */}
        <div style={{ textAlign: "right", justifySelf: "end" }}>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 14, letterSpacing: "0.04em", color: "var(--muted-2)", marginBottom: 10 }}>为热爱而设计</div>
          <div className="display" style={{ lineHeight: 1.04, color: "var(--muted)", transform: "skewX(-9deg)", transformOrigin: "right" }}>
            <div style={{ fontSize: "70px" }}>Design for</div>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 12, fontSize: "70px" }}>
              Love
              <span style={{ color: "#D97757", display: "inline-flex", transform: "skewX(9deg)" }}>{I.heart()}</span>
            </div>
          </div>
        </div>
      </div>
    </footer>);

}

function Home() {
  return (
    <div style={{ maxWidth: 1480, margin: "0 auto" }}>
      <Hero />
      <div style={{ maxWidth: 1180, margin: "0 auto" }}>
        <TechStack />
        <Featured />
        <Photography />
        <CTA />
        <Footer />
      </div>
    </div>);

}

Object.assign(window, { Home, HeroBackdrop, MediaFrame, ProjectCard, Pill, TechStack, PROJECTS, TESTIMONIALS, CopyEmailBtn, Footer, CTA, SignalActions, VoxActions });