// Shared primitives: Reveal, Placeholder, Icons, Buttons, Badge
const { useEffect, useRef, useState, useCallback } = React;

/* ---------- Reveal on scroll (IntersectionObserver) ---------- */
function Reveal({ children, as = "div", delay = 0, className = "", style = {}, ...rest }) {
  const ref = useRef(null);
  // Default to VISIBLE so content is never stuck hidden if observers are
  // throttled/paused (e.g. background tabs, capture iframes, reduced motion).
  const [shown, setShown] = useState(true);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const vh = window.innerHeight || document.documentElement.clientHeight;
    const r = el.getBoundingClientRect();
    // Already in view on mount → leave it visible (no entrance needed).
    if (r.top < vh && r.bottom > 0) return;
    // Below the fold → hide it (off-screen, no visible flash) then animate in.
    setShown(false);
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            setShown(true);
            io.unobserve(el);
          }
        });
      },
      { threshold: 0.1, rootMargin: "0px 0px -6% 0px" }
    );
    io.observe(el);
    // Safety net: never leave content hidden for more than ~2.5s.
    const t = setTimeout(() => setShown(true), 2500);
    return () => {io.disconnect();clearTimeout(t);};
  }, []);
  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={className}
      style={{
        opacity: shown ? 1 : 0,
        transform: shown ? "none" : "translateY(26px)",
        transition: `opacity .7s cubic-bezier(.16,.84,.44,1) ${delay}ms, transform .7s cubic-bezier(.16,.84,.44,1) ${delay}ms`,
        ...style
      }}
      {...rest}>
      
      {children}
    </Tag>);

}

/* ---------- Striped placeholder image block ---------- */
function Placeholder({ label, accent = "#2a2a2e", h = "100%", radius = 0, style = {}, dark = false, children }) {
  const stripe = dark ? "rgba(255,255,255,0.035)" : "rgba(255,255,255,0.05)";
  return (
    <div
      style={{
        position: "relative",
        width: "100%",
        height: h,
        borderRadius: radius,
        overflow: "hidden",
        background:
        `repeating-linear-gradient(135deg, ${stripe} 0 2px, transparent 2px 11px), ` +
        accent,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        ...style
      }}>
      
      {children}
      {label &&
      <span
        style={{
          fontFamily: "var(--font-mono)",
          fontSize: 11,
          letterSpacing: "0.04em",
          color: "rgba(255,255,255,0.62)",
          textTransform: "uppercase",
          padding: "5px 10px",
          border: "1px solid rgba(255,255,255,0.16)",
          borderRadius: 6,
          background: "rgba(0,0,0,0.28)",
          backdropFilter: "blur(2px)",
          textAlign: "center",
          maxWidth: "80%"
        }}>
        
          {label}
        </span>
      }
    </div>);

}

/* ---------- Icons (minimal stroke) ---------- */
const I = {
  home: (p) =>
  <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 10.5 12 3l9 7.5" /><path d="M5 9.5V21h14V9.5" /></svg>,

  work: (p) =>
  <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="7" width="18" height="13" rx="2" /><path d="M8 7V5.5A1.5 1.5 0 0 1 9.5 4h5A1.5 1.5 0 0 1 16 5.5V7" /><path d="M3 12h18" /></svg>,

  user: (p) =>
  <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="8" r="3.4" /><path d="M5 20c0-3.5 3-5.5 7-5.5s7 2 7 5.5" /></svg>,

  bolt: (p) =>
  <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z" /></svg>,

  linkedin: (p) =>
  <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM3 9h4v12H3V9Zm6 0h3.8v1.7h.05c.53-.95 1.83-1.95 3.77-1.95C20.4 8.75 21 11 21 14.1V21h-4v-6.1c0-1.45-.03-3.3-2-3.3-2 0-2.3 1.56-2.3 3.18V21H9V9Z" /></svg>,

  doc: (p) =>
  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z" /><path d="M14 3v5h5M9 13h6M9 17h6" /></svg>,

  arrow: (p) =>
  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 12h14M13 6l6 6-6 6" /></svg>,

  copy: (p) =>
  <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="9" y="9" width="11" height="11" rx="2" /><path d="M5 15V5a2 2 0 0 1 2-2h8" /></svg>,

  check: (p) =>
  <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 12.5 10 17.5 19.5 7" /></svg>,

  whatsapp: (p) =>
  <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M12 2a10 10 0 0 0-8.6 15l-1.3 4.7 4.8-1.3A10 10 0 1 0 12 2Zm5.6 14.2c-.24.67-1.4 1.28-1.93 1.32-.5.04-1.12.21-3.78-.8-3.18-1.25-5.2-4.5-5.36-4.72-.16-.21-1.28-1.7-1.28-3.25s.8-2.3 1.1-2.62a1.15 1.15 0 0 1 .83-.39c.21 0 .42 0 .6.01.2.01.46-.07.72.55.27.66.92 2.27 1 2.44.08.16.13.36.02.57-.1.21-.16.34-.32.53-.16.18-.34.41-.48.55-.16.16-.33.34-.14.66.18.32.82 1.36 1.77 2.2 1.22 1.09 2.25 1.43 2.57 1.6.32.16.5.13.69-.08.18-.21.79-.92 1-1.24.21-.32.42-.27.71-.16.29.1 1.85.87 2.17 1.03.32.16.53.24.6.37.08.13.08.74-.16 1.42Z" /></svg>,

  github: (p) =>
  <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" {...p}><path d="M12 2a10 10 0 0 0-3.16 19.49c.5.09.68-.22.68-.48v-1.7c-2.78.6-3.37-1.34-3.37-1.34-.45-1.16-1.1-1.47-1.1-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02a9.5 9.5 0 0 1 5 0c1.91-1.29 2.75-1.02 2.75-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.69-4.57 4.93.36.31.68.92.68 1.85v2.74c0 .27.18.58.69.48A10 10 0 0 0 12 2Z" /></svg>,

  mail: (p) =>
  <svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="5" width="18" height="14" rx="2.5" /><path d="m4 7.5 8 5.5 8-5.5" /></svg>,

  phone: (p) =>
  <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M6.6 3.5h2.9l1.3 4.2-2.1 1.4a12.5 12.5 0 0 0 5.7 5.7l1.4-2.1 4.2 1.3v2.9a2 2 0 0 1-2.2 2A16.8 16.8 0 0 1 4.6 5.7 2 2 0 0 1 6.6 3.5Z" /></svg>,

  wechat: (p) =>
  <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" {...p}><path d="M9.2 4C5.4 4 2.3 6.55 2.3 9.7c0 1.78 1 3.36 2.56 4.43L4.3 16l2.46-1.27c.45.12.9.2 1.38.24a5 5 0 0 1-.16-1.25c0-2.93 2.78-5.22 6.07-5.22l.5.02C14.06 5.78 11.86 4 9.2 4ZM6.95 7.3a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm4.5 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z" /><path d="M21.7 14.05c0-2.5-2.46-4.55-5.5-4.55s-5.5 2.05-5.5 4.55 2.46 4.55 5.5 4.55c.6 0 1.18-.08 1.72-.22l2.04 1.07-.56-1.84c1.42-.86 2.3-2.1 2.3-3.56Zm-7.3-1.02a.82.82 0 1 1 0 1.64.82.82 0 0 1 0-1.64Zm3.65 0a.82.82 0 1 1 0 1.64.82.82 0 0 1 0-1.64Z" /></svg>,

  xiaohongshu: (p) =>
  <svg viewBox="0 0 24 24" width="20" height="20" {...p}><rect x="2.4" y="4" width="19.2" height="16" rx="4.6" fill="currentColor" /><text x="12" y="15.6" textAnchor="middle" fontSize="9.2" fontWeight="800" fontFamily="var(--font-body), sans-serif" fill="#0a0a0b">书</text></svg>,

  handshake: (p) =>
  <svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M11 6.5 8.3 4.2a1.7 1.7 0 0 0-2 0L2 7.6v6.2l2.1 1.9" /><path d="M13 6.5l2.7-2.3a1.7 1.7 0 0 1 2 0L22 7.6v6.2l-4 3.4-2.4-2.1" /><path d="M11 6.5 6.4 10a1.6 1.6 0 0 0 .1 2.5 1.6 1.6 0 0 0 2.1-.1l2-1.8 2.3 2.1a1.5 1.5 0 0 0 2.1 0l1-1" /><path d="m8.6 12.4 2.3 2.1m-.2 2.1 1.6 1.4a1.5 1.5 0 0 0 2.1 0m-5.8-1.5 1.2 1.1a1.5 1.5 0 0 0 2.1 0" /></svg>,

  heart: (p) =>
  <svg viewBox="0 0 24 24" width="34" height="34" fill="currentColor" {...p}><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" /></svg>

};

/* ---------- Buttons ---------- */
function Btn({ children, variant = "solid", icon, dot, onClick, href, style = {} }) {
  const [hover, setHover] = useState(false);
  const base = {
    display: "inline-flex",
    alignItems: "center",
    gap: 9,
    fontFamily: "var(--font-body)",
    fontSize: 14.5,
    fontWeight: 600,
    letterSpacing: "-0.01em",
    padding: "13px 20px",
    borderRadius: 12,
    transition: "transform .25s cubic-bezier(.16,.84,.44,1), background .25s, border-color .25s, color .2s",
    transform: hover ? "translateY(-2px)" : "none",
    whiteSpace: "nowrap",
    ...style
  };
  const variants = {
    solid: {
      background: hover ? "#fff" : "var(--panel-2)",
      color: hover ? "#0a0a0b" : "var(--text)",
      border: "1px solid var(--line-2)"
    },
    outline: {
      background: hover ? "rgba(255,255,255,0.06)" : "transparent",
      color: "var(--text)",
      border: "1px solid var(--line-2)"
    },
    green: {
      background: hover ? "#52ef8c" : "var(--green)",
      color: "#04130a",
      border: "1px solid transparent"
    }
  };
  const cls = { ...base, ...variants[variant] };
  const inner =
  <>
      {dot && <span style={{ width: 8, height: 8, borderRadius: "50%", background: variant === "green" ? "#04130a" : "var(--green)", boxShadow: variant === "green" ? "none" : "0 0 8px var(--green)" }} />}
      {icon === "github" && <span style={{ display: "inline-flex" }}>{I.github()}</span>}
      <span>{children}</span>
      {icon === "arrow" && <I.arrow style={{ transform: hover ? "translateX(3px)" : "none", transition: "transform .25s" }} />}
    </>;

  const handlers = { onMouseEnter: () => setHover(true), onMouseLeave: () => setHover(false), onClick, style: cls };
  return href ? <a href={href} target="_blank" rel="noreferrer" {...handlers}>{inner}</a> : <button {...handlers}>{inner}</button>;
}

/* ---------- Pill badge with live dot ---------- */
function Badge({ children }) {
  return (
    <span style={{
      display: "inline-flex", whiteSpace: "nowrap",
      borderRadius: 100,
      background: "var(--green-dim)", border: "1px solid rgba(63,225,122,0.28)",
      fontWeight: 600, letterSpacing: "0.01em", alignItems: "center", fontSize: "14px", color: "rgb(169, 233, 146)", height: "53px", width: "254px", opacity: "1", padding: "10px 15px 8px 13px", gap: "13px", lineHeight: "15.45"
    }}>
      <span style={{ position: "relative", width: 8, height: 8 }}>
        <span style={{ position: "absolute", inset: 0, borderRadius: "50%", background: "var(--green)" }} />
        <span style={{ position: "absolute", inset: 0, borderRadius: "50%", background: "var(--green)", animation: "pulse 2s infinite" }} />
      </span>
      {children}
    </span>);

}

/* keyframes injected once */
(function injectKf() {
  if (document.getElementById("__kf")) return;
  const s = document.createElement("style");
  s.id = "__kf";
  s.textContent = `
    @keyframes pulse{0%{transform:scale(1);opacity:.7}70%{transform:scale(3);opacity:0}100%{opacity:0}}
    @keyframes marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
  `;
  document.head.appendChild(s);
})();

/* ---------- User-fillable image slot (drag & drop, persists) ---------- */
function Slot({ id, placeholder, radius = 0, shape = "rect", mask, fit = "cover", style = {} }) {
  const props = {
    id,
    shape,
    fit,
    placeholder: placeholder || "Drop an image",
    style: { display: "block", width: "100%", height: "100%", ...style }
  };
  if (radius) props.radius = String(radius);
  if (mask) props.mask = mask;
  return React.createElement("image-slot", props);
}

Object.assign(window, { Reveal, Placeholder, Slot, I, Btn, Badge });