/* app.jsx — Overlay UI for mediaquery portfolio */

const { useState, useEffect, useRef, useMemo } = React;

// ---------------- Portfolio covers (abstract SVG placeholders) ----------------
function Cover({ id }) {
  // Uploaded image: render as <img>
  if (typeof id === "string" && (id.startsWith("/") || /^https?:\/\//.test(id))) {
    return (
      <div className="cover-img">
        <img src={id} alt="" loading="lazy" />
      </div>
    );
  }
  const variants = {
    "cover-01": { hue1: "#1b2a48", hue2: "#3a5a8a", accent: "#8fd7ff", shape: "grid" },
    "cover-02": { hue1: "#2a1b4a", hue2: "#5a3a8a", accent: "#b7b5ff", shape: "orbit" },
    "cover-03": { hue1: "#0e2a2a", hue2: "#2a4a4a", accent: "#8affcf", shape: "tokens" },
    "cover-04": { hue1: "#3a1b2a", hue2: "#6a2a44", accent: "#ffb0c8", shape: "chart" },
    "cover-05": { hue1: "#14203a", hue2: "#2a3a6a", accent: "#b7d0ff", shape: "lines" },
    "cover-06": { hue1: "#241028", hue2: "#4a2050", accent: "#e0a8ff", shape: "dots" }
  };
  const v = variants[id] || variants["cover-01"];
  return (
    <svg viewBox="0 0 320 200" preserveAspectRatio="xMidYMid slice">
      <defs>
        <linearGradient id={`g-${id}`} x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stopColor={v.hue1} />
          <stop offset="1" stopColor={v.hue2} />
        </linearGradient>
        <radialGradient id={`r-${id}`} cx="0.7" cy="0.3" r="0.7">
          <stop offset="0" stopColor={v.accent} stopOpacity="0.5" />
          <stop offset="1" stopColor={v.accent} stopOpacity="0" />
        </radialGradient>
      </defs>
      <rect width="320" height="200" fill={`url(#g-${id})`} />
      <rect width="320" height="200" fill={`url(#r-${id})`} />
      {v.shape === "grid" && Array.from({length: 8}).map((_,i) => (
        <line key={i} x1={i*40} y1="0" x2={i*40} y2="200" stroke={v.accent} strokeOpacity="0.15" />
      ))}
      {v.shape === "grid" && Array.from({length: 5}).map((_,i) => (
        <line key={"h"+i} x1="0" y1={i*40} x2="320" y2={i*40} stroke={v.accent} strokeOpacity="0.15" />
      ))}
      {v.shape === "orbit" && (
        <g stroke={v.accent} fill="none" strokeOpacity="0.45">
          <ellipse cx="160" cy="100" rx="110" ry="46" />
          <ellipse cx="160" cy="100" rx="80" ry="30" strokeOpacity="0.3" />
          <circle cx="160" cy="100" r="14" fill={v.accent} fillOpacity="0.3" stroke="none" />
        </g>
      )}
      {v.shape === "tokens" && (
        <g>
          {[20,70,120,170,220,270].map((x,i) => (
            <rect key={i} x={x} y={70} width="30" height="60" fill={v.accent} fillOpacity={0.15 + (i%3)*0.1} />
          ))}
        </g>
      )}
      {v.shape === "chart" && (
        <polyline points="10,160 50,120 90,140 130,80 170,100 210,60 250,90 310,50"
          stroke={v.accent} strokeWidth="1.5" fill="none" />
      )}
      {v.shape === "lines" && Array.from({length: 18}).map((_,i)=>(
        <line key={i} x1="0" y1={i*12} x2="320" y2={i*12 + 20} stroke={v.accent} strokeOpacity={0.05 + (i%4)*0.04} />
      ))}
      {v.shape === "dots" && Array.from({length: 40}).map((_,i) => (
        <circle key={i} cx={(i*37)%320} cy={(i*53)%200} r={1 + (i%3)} fill={v.accent} fillOpacity={0.4} />
      ))}
      <rect x="0.5" y="0.5" width="319" height="199" fill="none" stroke="rgba(255,255,255,0.08)" />
    </svg>
  );
}

// ---------------- HUD ----------------
function HUD({ time, onToggleTweaks }) {
  const jst = new Date(time).toLocaleTimeString("ja-JP", {
    timeZone: "Asia/Tokyo",
    hour12: false,
    hour: "2-digit", minute: "2-digit", second: "2-digit"
  });
  return (
    <div className="hud">
      <div className="corner tl">
        <span className="dot" />
        <span className="brand">mediaquery</span>
        <span className="sep">—</span>
        <span>portfolio / 2026</span>
      </div>
      <div className="corner tr">
        <span className="hint">JST {jst}</span>
        <span className="sep">—</span>
        <span className="coord">LAT 35.6° N · LON 139.7° E</span>
      </div>
      <div className="corner bl">
        <span>T. Tsuchiya</span>
        <span className="sep">—</span>
        <span className="hint">web engineer · frontend · interaction</span>
      </div>
      <div className="corner br">
        <span>drag to rotate · click node to open</span>
        <span className="hint">v 1.0 — built with three.js</span>
      </div>
    </div>
  );
}

// ---------------- Panels ----------------
function PanelShell({ eyebrow, title, onClose, open, children, wide }) {
  useEffect(() => {
    function esc(e){ if (e.key === "Escape") onClose(); }
    if (open) window.addEventListener("keydown", esc);
    return () => window.removeEventListener("keydown", esc);
  }, [open, onClose]);

  return (
    <div className={"panel-layer" + (open ? " open" : "")}>
      <div className="scrim" onClick={onClose} />
      <div className="panel" style={wide ? { width: "min(1160px, 88vw)" } : {}}>
        <div className="panel-head">
          <div>
            <div className="eyebrow">{eyebrow}</div>
            <h2>{title}</h2>
          </div>
          <button className="close" onClick={onClose} aria-label="Close">✕</button>
        </div>
        <div className="panel-body">{children}</div>
      </div>
    </div>
  );
}

function ProfilePanel({ open, onClose }) {
  return (
    <PanelShell
      open={open} onClose={onClose}
      eyebrow="NODE · M · PROFILE"
      title="Identity / Biography"
    >
      <div className="profile-grid">
        <div className="profile-meta">
          <div className="profile-card name">
            <div className="k">NAME</div>
            <div className="v">土屋 友明</div>
            <div className="sub">Tomoaki Tsuchiya</div>
          </div>
          <div className="profile-card">
            <div className="kvs">
              <div className="row"><div className="k">Born</div><div className="v">1985.04.10</div></div>
              <div className="row"><div className="k">From</div><div className="v">Kanagawa</div></div>
              <div className="row"><div className="k">Based</div><div className="v">Tokyo, JP</div></div>
              <div className="row"><div className="k">Role</div><div className="v">Web Engineer</div></div>
            </div>
          </div>
          <div className="profile-card">
            <div className="k">FOCUS</div>
            <div style={{fontSize: "13px", color: "var(--ink-dim)", lineHeight: 1.7, marginTop: "4px"}}>
              Frontend architecture, interaction & motion design, design systems, and team leadership bridging design and engineering.
            </div>
          </div>
        </div>

        <div className="timeline">
          <div className="item">
            <div className="year">2008 — 2010 · STUDY</div>
            <div className="title">Information Technology, 2-year program</div>
            <div className="desc">情報系専門学校で、Web・プログラミングの基礎を2年間学ぶ。</div>
          </div>
          <div className="item">
            <div className="year">2010 — 2016 · PRODUCTION</div>
            <div className="title">Web Designer, Production Studio</div>
            <div className="desc">
              制作会社にて約6年、Webデザイナーとして従事。住宅関連企業の案件を中心に、
              ディレクター・コーダーと連携しながら、デザインからフロントエンド実装まで一貫して担当。
            </div>
          </div>
          <div className="item">
            <div className="year">2016 — 2018 · IN-HOUSE</div>
            <div className="title">Brand Site Owner · In-house Production</div>
            <div className="desc">
              事業会社へ転職。自社ブランドサイトの提案・制作・保守を担当。
              併せて社内イベントの企画運営や映像制作など、Web領域を越えた業務にも従事。
            </div>
          </div>
          <div className="item">
            <div className="year">2018 — 2021 · ENGINEERING</div>
            <div className="title">PM / Tech Lead · Reservation Platform</div>
            <div className="desc">
              エンジニア組織へ異動し、自社予約システム開発プロジェクトの立ち上げに参画。
              PM として要件定義などの上流工程から担当し、チームリーダー・スクラムマスターも兼任。
              JavaScript / PHP によるフロント・DB・API 開発まで担当。
            </div>
          </div>
          <div className="item">
            <div className="year">2021 — 2024 · WEB ENGINEER</div>
            <div className="title">Frontend-led Product Engineer</div>
            <div className="desc">
              自社Webサイト・Webアプリ・自社メディアの新規開発と運用保守に従事。
              フロントエンドを軸に、UI 改善と SEO 対応を通じて、
              ユーザー体験と事業成果の両方を意識した改善を推進。
            </div>
          </div>
          <div className="item">
            <div className="year">2024 — Present · TECH LEAD</div>
            <div className="title">Content Engineering & Marketing Tech</div>
            <div className="desc">
              事業会社にて自社サービスの Web コンテンツ開発とマーケティング技術支援を担当。
              チームビルディングとテックリードとしての役割も兼任。
            </div>
          </div>
        </div>
      </div>
    </PanelShell>
  );
}

function usePerPage() {
  const getPer = () => (typeof window === "undefined" ? 3
    : window.innerWidth <= 600 ? 1
    : window.innerWidth <= 900 ? 2
    : 3);
  const [per, setPer] = useState(getPer);
  useEffect(() => {
    const on = () => setPer(getPer());
    window.addEventListener("resize", on);
    return () => window.removeEventListener("resize", on);
  }, []);
  return per;
}

function PortfolioPanel({ open, onClose, projects }) {
  const [index, setIndex] = useState(0);
  const per = usePerPage();
  const max = Math.max(0, projects.length - per);
  useEffect(() => { setIndex(i => Math.min(i, max)); }, [max]);
  const go = (d) => setIndex(i => Math.min(max, Math.max(0, i + d)));

  return (
    <PanelShell
      open={open} onClose={onClose}
      eyebrow="NODE · AQ · PORTFOLIO"
      title="Selected Works / Archive"
      wide
    >
      <div className="pf-controls">
        <div className="count">
          {String(index + 1).padStart(2, "0")}–{String(Math.min(index + per, projects.length)).padStart(2,"0")} / {String(projects.length).padStart(2,"0")} projects
        </div>
        <div className="nav">
          <button className="pf-nav-btn" onClick={() => go(-1)} disabled={index === 0} aria-label="Previous">◀</button>
          <button className="pf-nav-btn" onClick={() => go(1)} disabled={index === max} aria-label="Next">▶</button>
        </div>
      </div>
      <div className="pf-track-wrap">
        <div
          className="pf-track"
          style={{
            transform: `translateX(calc(${-index} * (100% / ${per}) - ${index} * 4px))`,
            gridAutoColumns: `calc((100% - ${(per-1)*12}px) / ${per})`
          }}
        >
          {projects.map(p => (
            <article key={p.id} className="pf-card">
              <div className="cover"><Cover id={p.image} /></div>
              <div className="period">{p.period}</div>
              <h3>{p.title}</h3>
              <div className="role">{p.role}</div>
              <p className="summary">{p.summary}</p>
              <div className="stack">
                {p.stack && p.stack.map(s => <span key={s} className="chip">{s}</span>)}
              </div>
              {p.url && <a className="link" href={p.url} target="_blank" rel="noreferrer">View Case ↗</a>}
            </article>
          ))}
        </div>
      </div>
    </PanelShell>
  );
}

function PrivacyPanel({ open, onClose }) {
  // Capture-phase ESC handler: when stacked over ContactPanel, only close this
  // panel instead of both (stopImmediatePropagation blocks ContactPanel's bubble-phase ESC).
  useEffect(() => {
    if (!open) return;
    function esc(e) {
      if (e.key === "Escape") {
        e.stopImmediatePropagation();
        onClose();
      }
    }
    window.addEventListener("keydown", esc, true);
    return () => window.removeEventListener("keydown", esc, true);
  }, [open, onClose]);

  return (
    <PanelShell
      open={open} onClose={onClose}
      eyebrow="POLICY · PRIVACY"
      title="プライバシーポリシー"
    >
      <div className="policy">
        <p>
          mediaquery.info(以下「当サイト」といいます)は、ユーザーの個人情報の保護を重要な責務と考え、
          以下の方針に基づき適切に取り扱います。
        </p>

        <h3>1. 取得する情報</h3>
        <p>当サイトでは、お問い合わせフォームを通じて以下の情報を取得します。</p>
        <ul>
          <li>お名前</li>
          <li>メールアドレス</li>
          <li>お問い合わせ内容</li>
          <li>IPアドレス・ユーザーエージェント(不正送信対策のためログとして記録)</li>
        </ul>

        <h3>2. 利用目的</h3>
        <p>取得した情報は、以下の目的の範囲内で利用します。</p>
        <ul>
          <li>お問い合わせへの回答およびご連絡のため</li>
          <li>サービス改善・運営上の分析のため</li>
          <li>不正アクセス・スパムの防止のため</li>
        </ul>

        <h3>3. 第三者への提供</h3>
        <p>
          取得した個人情報を、ご本人の同意を得ずに第三者へ提供することはありません。
          ただし、法令に基づく開示請求があった場合はこの限りではありません。
        </p>

        <h3>4. 安全管理措置</h3>
        <p>
          個人情報への不正アクセス、紛失、改ざん、漏えい等を防止するため、
          合理的な技術的・組織的対策を講じます。
        </p>

        <h3>5. Cookie の利用</h3>
        <p>
          当サイトでは、ユーザー体験の向上のため Cookie を利用する場合があります。
          ブラウザの設定により Cookie の受け入れを拒否することができますが、一部機能が利用できない可能性があります。
        </p>

        <h3>6. 個人情報の開示・訂正・削除</h3>
        <p>
          ユーザーご本人から個人情報の開示・訂正・削除のご依頼があった場合は、
          ご本人確認のうえ、合理的な期間内に対応いたします。
        </p>

        <h3>7. ポリシーの変更</h3>
        <p>
          本ポリシーは、法令の変更やサービス内容の変更に応じて、予告なく改定される場合があります。
          変更後の内容は、当サイトへの掲載をもって効力を生じるものとします。
        </p>

        <h3>8. お問い合わせ</h3>
        <p>
          本ポリシーに関するお問い合わせは、お問い合わせフォームよりご連絡ください。
        </p>

        <div className="meta">制定日: 2026年4月25日</div>
      </div>
    </PanelShell>
  );
}

function ContactPanel({ open, onClose, onOpenPrivacy }) {
  const [form, setForm] = useState({ name: "", email: "", message: "", agree: false, website: "" });
  const [errors, setErrors] = useState({});
  const [sent, setSent] = useState(false);
  const [sending, setSending] = useState(false);
  const [serverError, setServerError] = useState("");

  const valid = form.name.trim() && /\S+@\S+\.\S+/.test(form.email) && form.message.trim().length >= 5 && form.agree;

  const set = (k, v) => {
    setForm(f => ({ ...f, [k]: v }));
    setErrors(e => ({ ...e, [k]: "" }));
    setServerError("");
  };

  const submit = async (e) => {
    e.preventDefault();
    const next = {};
    if (!form.name.trim()) next.name = "お名前を入力してください";
    if (!/\S+@\S+\.\S+/.test(form.email)) next.email = "正しいメールアドレスを入力してください";
    if (form.message.trim().length < 5) next.message = "5文字以上で入力してください";
    if (!form.agree) next.agree = "プライバシーポリシーへの同意が必要です";
    setErrors(next);
    if (Object.keys(next).length > 0) return;

    setSending(true);
    setServerError("");
    try {
      const res = await fetch("api/contact.php", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(form)
      });
      const data = await res.json().catch(() => ({}));
      if (res.ok && data.ok) {
        setSent(true);
        setTimeout(() => {
          setSent(false);
          setForm({ name: "", email: "", message: "", agree: false, website: "" });
        }, 2800);
      } else if (data && data.errors) {
        setErrors(data.errors);
      } else {
        setServerError((data && data.error) || "送信に失敗しました。時間をおいて再度お試しください。");
      }
    } catch (err) {
      setServerError("通信エラーが発生しました。ネットワークを確認して再度お試しください。");
    } finally {
      setSending(false);
    }
  };

  return (
    <PanelShell
      open={open} onClose={onClose}
      eyebrow="NODE · Y · TRANSMIT"
      title="Contact / Work Inquiry"
    >
      <div className="contact-wrap">
        <div className="contact-side">
          <p>
            お仕事のご依頼やお問い合わせはこちらからお願いします。<br/>
            フロントエンド・デザインシステム構築・テックリードとしての参画など、幅広くご相談いただけます。
          </p>
          <div className="channels">
            <a href="https://mediaquery.info" target="_blank" rel="noreferrer">→ mediaquery.info</a>
            <a href="https://x.com/mediaquery_info" target="_blank" rel="noreferrer">→ x.com / @mediaquery_info</a>
            <a href="https://github.com/mediaquery-info" target="_blank" rel="noreferrer">→ github.com / mediaquery-info</a>
          </div>
        </div>

        <form className="form" onSubmit={submit} noValidate>
          <div className={"field" + (errors.name ? " error" : "")}>
            <div className="label"><span>お名前</span><span className="req">REQUIRED</span></div>
            <input
              value={form.name}
              onChange={e => set("name", e.target.value)}
              placeholder="Your name"
              autoComplete="name"
            />
            <div className="err-msg">{errors.name || ""}</div>
          </div>
          <div className={"field" + (errors.email ? " error" : "")}>
            <div className="label"><span>メールアドレス</span><span className="req">REQUIRED</span></div>
            <input
              type="email"
              value={form.email}
              onChange={e => set("email", e.target.value)}
              placeholder="you@company.com"
              autoComplete="email"
            />
            <div className="err-msg">{errors.email || ""}</div>
          </div>
          <div className={"field" + (errors.message ? " error" : "")}>
            <div className="label"><span>お問い合わせ内容</span><span className="req">REQUIRED</span></div>
            <textarea
              value={form.message}
              onChange={e => set("message", e.target.value)}
              placeholder="プロジェクトの概要や、気になっているポイントなど、自由にお書きください。"
            />
            <div className="err-msg">{errors.message || ""}</div>
          </div>

          <label className="check">
            <input type="checkbox" checked={form.agree} onChange={e => set("agree", e.target.checked)} />
            <span className="box" />
            <span>
              <a href="#" onClick={e=>{ e.preventDefault(); onOpenPrivacy && onOpenPrivacy(); }}>プライバシーポリシー</a> に同意します。
              {errors.agree && <span style={{color:"var(--danger)", display:"block", marginTop:"4px", fontFamily:"var(--mono)", fontSize:"10.5px"}}>{errors.agree}</span>}
            </span>
          </label>

          {/* Honeypot — bots tend to fill every visible input */}
          <div aria-hidden="true" style={{ position:"absolute", left:"-9999px", width:"1px", height:"1px", overflow:"hidden" }}>
            <label>
              Website
              <input
                type="text" tabIndex="-1" autoComplete="off"
                value={form.website}
                onChange={e => set("website", e.target.value)}
              />
            </label>
          </div>

          {serverError && (
            <div style={{color:"var(--danger)", fontFamily:"var(--mono)", fontSize:"11px", letterSpacing:"0.08em", marginTop:"4px"}}>
              {serverError}
            </div>
          )}

          <button type="submit" className={"submit" + (sent ? " sent" : "")} disabled={sending || (!valid && !sent)}>
            {sending ? "送信中…" : sent ? "✓ 送信完了" : "Transmit ↗"}
          </button>
        </form>
      </div>
    </PanelShell>
  );
}

// ---------------- Hotspot labels (DOM overlay) ----------------
function HotspotLabels({ hotspots, hovered, openPanel }) {
  return (
    <div style={{ position: "fixed", inset: 0, pointerEvents: "none", zIndex: 3 }}>
      {hotspots.map(h => (
        <React.Fragment key={h.id}>
          <div
            className={"hotspot-label" + (hovered === h.id ? " visible" : "")}
            style={{ left: h.x + "px", top: (h.y - 44) + "px" }}
          >
            {h.label}
            <span className="hint">{h.hint}</span>
          </div>
          <button
            className="hotspot-btn"
            aria-label={h.label}
            style={{ left: (h.x - 28) + "px", top: (h.y - 28) + "px" }}
            onClick={(e) => {
              e.stopPropagation();
              if (window.MQScene && window.MQScene.playClick) window.MQScene.playClick();
              openPanel(h.panel);
            }}
          />
        </React.Fragment>
      ))}
    </div>
  );
}

// ---------------- Tweaks panel ----------------
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "cyan",
  "starDensity": 1.0,
  "rotationSpeed": 0.08
}/*EDITMODE-END*/;

function Tweaks({ visible, tweaks, setTweaks }) {
  if (!visible) return null;
  const swatches = [
    { name: "cyan", color: "#8fd7ff" },
    { name: "violet", color: "#b7b5ff" },
    { name: "emerald", color: "#8affcf" },
    { name: "amber", color: "#f5c784" }
  ];
  return (
    <div className="tweaks visible">
      <h4>TWEAKS <span className="dot-mini" /></h4>
      <div className="group">
        <div className="row"><label>accent</label><span>{tweaks.accent}</span></div>
        <div className="swatches">
          {swatches.map(s => (
            <button
              key={s.name}
              className={"swatch" + (tweaks.accent === s.name ? " active" : "")}
              style={{ background: s.color }}
              onClick={() => setTweaks({ accent: s.name })}
              aria-label={s.name}
            />
          ))}
        </div>
      </div>
      <div className="group">
        <div className="row"><label>star density</label><span>{tweaks.starDensity.toFixed(2)}</span></div>
        <input
          type="range" min="0.2" max="1.6" step="0.05"
          value={tweaks.starDensity}
          onChange={e => setTweaks({ starDensity: parseFloat(e.target.value) })}
        />
      </div>
      <div className="group">
        <div className="row"><label>rotation speed</label><span>{tweaks.rotationSpeed.toFixed(2)}</span></div>
        <input
          type="range" min="0" max="0.4" step="0.01"
          value={tweaks.rotationSpeed}
          onChange={e => setTweaks({ rotationSpeed: parseFloat(e.target.value) })}
        />
      </div>
    </div>
  );
}

// ---------------- App ----------------
function App() {
  const [panel, setPanel] = useState(null); // profile | portfolio | contact
  const [privacyOpen, setPrivacyOpen] = useState(false);
  const [hovered, setHovered] = useState(null);
  const [hotspotPos, setHotspotPos] = useState([]);
  const [projects, setProjects] = useState([]);
  const [now, setNow] = useState(Date.now());
  const [tweaksVisible, setTweaksVisible] = useState(false);
  const [tweakState, setTweakState] = useState(TWEAK_DEFAULTS);

  // Load portfolio JSON
  useEffect(() => {
    fetch("portfolio.json").then(r => r.json()).then(d => setProjects(d.projects || []))
      .catch(() => setProjects([]));
  }, []);

  // Clock
  useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, []);

  // Scene init
  useEffect(() => {
    if (!window.MQScene) return;
    window.MQScene.init({
      onReady: () => {
        document.getElementById("intro").classList.add("hidden");
      },
      onHotspotClick: (panelId) => setPanel(panelId),
      onHotspotHover: (id) => setHovered(id)
    });
    // poll screen positions
    let raf;
    const loop = () => {
      setHotspotPos(window.MQScene.getHotspots());
      raf = requestAnimationFrame(loop);
    };
    loop();
    return () => cancelAnimationFrame(raf);
  }, []);

  // Apply tweaks to scene
  useEffect(() => {
    if (!window.MQScene) return;
    window.MQScene.setAccent(tweakState.accent);
    window.MQScene.setDensity(tweakState.starDensity);
    window.MQScene.setRotationSpeed(tweakState.rotationSpeed);
  }, [tweakState]);

  // Disable hotspot raycast (and sounds) while any panel is open
  useEffect(() => {
    if (!window.MQScene || !window.MQScene.setHotspotsInteractive) return;
    window.MQScene.setHotspotsInteractive(panel === null);
  }, [panel]);

  const updateTweaks = (patch) => {
    setTweakState(s => {
      const next = { ...s, ...patch };
      window.parent.postMessage({ type: "__edit_mode_set_keys", edits: patch }, "*");
      return next;
    });
  };

  // Edit mode protocol
  useEffect(() => {
    function onMsg(e) {
      if (!e.data) return;
      if (e.data.type === "__activate_edit_mode") setTweaksVisible(true);
      if (e.data.type === "__deactivate_edit_mode") setTweaksVisible(false);
    }
    window.addEventListener("message", onMsg);
    window.parent.postMessage({ type: "__edit_mode_available" }, "*");
    return () => window.removeEventListener("message", onMsg);
  }, []);

  return (
    <>
      <HUD time={now} />
      <HotspotLabels hotspots={hotspotPos} hovered={hovered} openPanel={setPanel} />
      <div className="tagline">
        <span className="line" />
        web engineer · frontend · interaction
        <span className="line" />
      </div>
      <ProfilePanel   open={panel === "profile"}   onClose={() => setPanel(null)} />
      <PortfolioPanel open={panel === "portfolio"} onClose={() => setPanel(null)} projects={projects} />
      <ContactPanel   open={panel === "contact"}   onClose={() => setPanel(null)} onOpenPrivacy={() => setPrivacyOpen(true)} />
      <PrivacyPanel   open={privacyOpen}           onClose={() => setPrivacyOpen(false)} />
      <Tweaks visible={tweaksVisible} tweaks={tweakState} setTweaks={updateTweaks} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("overlay")).render(<App />);
