/* === Portfolio components & app === */
const { useState, useEffect, useMemo, useRef, useCallback } = React;

const PASSWORD = "mj0123pf";
const AUTH_KEY = "wenting_portfolio_auth_v1";
const TWEAKS_KEY = "wenting_portfolio_tweaks_v1";

/* ---------- Helpers ---------- */
function uid() { return "p_" + Math.random().toString(36).slice(2, 9); }

function getCategoryKey(catId) {
  const c = CATEGORIES.find(c => c.id === catId);
  return c ? c.key : "category_web";
}

const FOCUS_POSITIONS = [
  "0% 0%",   "50% 0%",   "100% 0%",
  "0% 50%",  "50% 50%",  "100% 50%",
  "0% 100%", "50% 100%", "100% 100%",
];

// Only close modal when both mousedown AND mouseup happened directly on backdrop.
// Prevents accidental close when drag-selecting text inside an input.
function useBackdropClose(onClose) {
  const downOnBackdrop = useRef(false);
  return {
    onMouseDown: (e) => { downOnBackdrop.current = e.target === e.currentTarget; },
    onClick: (e) => {
      if (downOnBackdrop.current && e.target === e.currentTarget) onClose();
      downOnBackdrop.current = false;
    },
  };
}

/* ---------- Password gate ---------- */
function PasswordGate({ onPass, t }) {
  const [val, setVal] = useState("");
  const [err, setErr] = useState(false);
  const inputRef = useRef(null);
  const title = "PORTFOLIO";
  useEffect(() => { setTimeout(() => inputRef.current && inputRef.current.focus(), 1200); }, []);

  function submit(e) {
    e.preventDefault();
    if (val === PASSWORD) {
      onPass();
    } else {
      setErr(true);
      setTimeout(() => setErr(false), 600);
    }
  }

  return (
    <div className="gate">
      <div className="gate-grid-bg"></div>
      <div className="gate-inner">
        <div className="gate-mark"><span className="dot"></span> PRIVATE / 비공개</div>
        <h1 className="gate-title" aria-label={title}>
          {title.split("").map((c, i) => (
            <span key={i} className="char" style={{ animationDelay: `${0.06 * i}s` }}>{c}</span>
          ))}
        </h1>
        <div className="gate-subtitle">{t.gate_subtitle}</div>
        <form className="gate-form" onSubmit={submit}>
          <input
            ref={inputRef}
            type="password"
            className={"gate-input" + (err ? " error" : "")}
            placeholder={t.gate_placeholder}
            value={val}
            onChange={(e) => setVal(e.target.value)}
            autoComplete="off"
          />
          <button className="gate-btn" type="submit">{t.gate_enter} →</button>
        </form>
        <div className="gate-error-msg">{err ? t.gate_error : ""}</div>
        <div className="gate-hint">{t.gate_hint}</div>
      </div>
    </div>
  );
}

/* ---------- Header ---------- */
function Header({ t, lang, setLang, admin, setAdmin }) {
  return (
    <header className="site-header">
      <div className="site-header-inner">
        <div className="brand">
          <div className="brand-mark" aria-label="Sun">
            <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="4" />
              <path d="M12 2v2" />
              <path d="M12 20v2" />
              <path d="M4.93 4.93l1.41 1.41" />
              <path d="M17.66 17.66l1.41 1.41" />
              <path d="M2 12h2" />
              <path d="M20 12h2" />
              <path d="M4.93 19.07l1.41-1.41" />
              <path d="M17.66 6.34l1.41-1.41" />
            </svg>
          </div>
          <div className="brand-name">
            {lang === "ko" ? "선민정" : "Minjung Sun"}
            <small>{lang === "ko" ? "포트폴리오" : "portfolio"}</small>
          </div>
        </div>
        <div className="header-actions">
          <div className="lang-toggle">
            <button className={lang === "ko" ? "active" : ""} onClick={() => setLang("ko")}>KR</button>
            <button className={lang === "en" ? "active" : ""} onClick={() => setLang("en")}>EN</button>
          </div>
          <button className={"admin-toggle" + (admin ? " on" : "")} onClick={() => setAdmin(!admin)}>
            <span className="admin-dot"></span>
            {admin ? t.admin_on : t.admin_off}
          </button>
        </div>
      </div>
    </header>
  );
}

/* ---------- Hero ---------- */
function Hero({ t, lang, postCount }) {
  const marqueeItems = [
    "Figma", "Photoshop", "Illustrator", "HTML5", "CSS3", "JavaScript",
    "React", "WordPress", "PHP", "Claude AI", "Premiere Pro", "VLLO",
    "Character Design", "Card News", "Web/App UI", "Publishing",
  ];

  return (
    <section className="hero">
      <div className="hero-blobs" aria-hidden="true">
        <div className="blob blob-1"></div>
        <div className="blob blob-2"></div>
        <div className="blob blob-3"></div>
        <div className="blob blob-4"></div>
      </div>
      <div className="hero-inner">
        <div className="hero-eyebrow">
          <span>2026 · PORTFOLIO</span>
        </div>
        <h1>
          <span className="hero-line l1"><span>{t.hero_intro}</span></span>
          <span className="hero-line l2"><span>{t.hero_name}</span></span>
          <span className="hero-line l3"><span><i className="hero-accent">{t.hero_role}</i></span></span>
        </h1>
        <div className="hero-tagline">
          <p>
            {t.hero_tagline_1}<br />
            <span>{t.hero_tagline_2}</span>
          </p>
          <div className="hero-stats">
            <div>
              <div className="stat-num">12<small>{t.hero_yrs}</small></div>
              <div className="stat-label">{t.hero_yrs_label}</div>
            </div>
            <div>
              <div className="stat-num">{postCount}</div>
              <div className="stat-label">{t.hero_projects_label}</div>
            </div>
            <div>
              <div className="stat-num">15+</div>
              <div className="stat-label">{t.hero_tools_label}</div>
            </div>
          </div>
        </div>
        <div className="marquee">
          <div className="marquee-track">
            <span>{marqueeItems.join(" ✶ ")}</span>
            <span>{marqueeItems.join(" ✶ ")}</span>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- Card ---------- */
function Card({ post, t, lang, onClick, admin, onEdit, onDelete, density }) {
  const cat = CATEGORIES.find(c => c.id === post.category);
  const catLabel = t[cat ? cat.key : "category_web"];
  const tools = post.tools || [];
  const hasImg = post.thumb && post.thumb.length > 0;
  const titleStr = (post.title && post.title[lang]) || post.title?.ko || post.title?.en || "Untitled";
  const phStyle = { background: placeholderBg(post.id + (post.palette || 0)) };
  return (
    <article className="card" data-cat={post.category} onClick={() => onClick(post)}>
      <div className="card-thumb">
        {hasImg ? (
          <img
            className="card-thumb-img"
            src={post.thumb}
            alt={titleStr}
            style={{ objectPosition: post.thumb_focus || "50% 50%" }}
          />
        ) : (
          <div className="card-thumb-placeholder" style={phStyle}>
            <div className="ph-mark">{catLabel}</div>
          </div>
        )}
        <div className="card-cat-tag">{catLabel}</div>
        <div className="card-thumb-overlay">
          <div className="ov-cat">{tools.slice(0, 3).join(" · ")}</div>
          <div className="ov-cta">{t.view_detail} →</div>
        </div>
        {admin && (
          <div className="card-admin-controls" onClick={(e) => e.stopPropagation()}>
            <button className="card-admin-btn" onClick={() => onEdit(post)}>{t.edit}</button>
            <button className="card-admin-btn danger" onClick={() => onDelete(post)}>{t.delete}</button>
          </div>
        )}
      </div>
      <div className="card-info">
        <h3 className="card-title">{titleStr}</h3>
        <div className="card-meta">
          <span>{post.period || "—"}</span>
          {tools[0] && <><span className="dot">·</span><span>{tools[0]}</span></>}
        </div>
      </div>
    </article>
  );
}

/* ---------- Detail modal ---------- */
function DetailModal({ post, t, lang, onClose }) {
  const [idx, setIdx] = useState(0);
  const backdropProps = useBackdropClose(onClose);
  const cat = CATEGORIES.find(c => c.id === post.category);
  const catLabel = t[cat ? cat.key : "category_web"];
  const titleStr = (post.title && post.title[lang]) || post.title?.ko || post.title?.en || "Untitled";
  const descStr = (post.desc && post.desc[lang]) || post.desc?.ko || post.desc?.en || "";
  const tools = post.tools || [];
  const images = [post.thumb, ...(post.extras || [])].filter(Boolean);
  const showingImg = images[idx];

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();
      if (e.key === "ArrowRight" && images.length > 1) setIdx((i) => (i + 1) % images.length);
      if (e.key === "ArrowLeft" && images.length > 1) setIdx((i) => (i - 1 + images.length) % images.length);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [images.length, onClose]);

  return (
    <div className="modal-backdrop" {...backdropProps}>
      <div className="modal detail" data-cat={post.category} onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose} aria-label={t.detail_close}>✕</button>
        <div className="modal-body">
          <div className="detail-grid">
            <div>
              <div className="detail-media">
                {showingImg ? (
                  <img className="detail-media-img" src={showingImg} alt={titleStr} />
                ) : (
                  <div className="detail-media-ph" style={{ background: placeholderBg(post.id + (post.palette || 0)) }}>
                    <div className="ph-mark">{catLabel} · PLACEHOLDER</div>
                  </div>
                )}
                {images.length > 1 && (
                  <>
                    <button className="detail-nav-btn prev" onClick={() => setIdx((idx - 1 + images.length) % images.length)} aria-label={t.detail_prev}>←</button>
                    <button className="detail-nav-btn next" onClick={() => setIdx((idx + 1) % images.length)} aria-label={t.detail_next}>→</button>
                    <div className="detail-counter">{idx + 1} / {images.length}</div>
                  </>
                )}
              </div>
              {images.length > 1 && (
                <div className="detail-thumb-strip">
                  {images.map((src, i) => (
                    <div key={i} className={"detail-thumb" + (i === idx ? " active" : "")} onClick={() => setIdx(i)}>
                      <img src={src} alt="" />
                    </div>
                  ))}
                </div>
              )}
            </div>
            <div className="detail-info">
              <span className="cat-tag">{catLabel}</span>
              <h3>{titleStr}</h3>
              <div className={"desc" + (descStr ? "" : " muted")}>{descStr || "—"}</div>

              {tools.length > 0 && (
                <div className="kv">
                  <div className="k">{t.detail_tools}</div>
                  <div className="v">
                    <div className="tool-tags">
                      {tools.map((tool, i) => <span key={i} className="tool-tag">{tool}</span>)}
                    </div>
                  </div>
                </div>
              )}
              {post.period && (
                <div className="kv">
                  <div className="k">{t.detail_period}</div>
                  <div className="v">{post.period}</div>
                </div>
              )}
              {post.link && (
                <a href={post.link} className="detail-link" target="_blank" rel="noreferrer">
                  {t.detail_link}
                  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M4 12L12 4M12 4H6M12 4V10" /></svg>
                </a>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- Editor modal ---------- */
function EditorModal({ post, t, lang, onSave, onClose }) {
  const isNew = !post;
  const backdropProps = useBackdropClose(onClose);
  const [form, setForm] = useState(() => post ? JSON.parse(JSON.stringify(post)) : {
    id: uid(),
    title: { ko: "", en: "" },
    category: "web",
    desc: { ko: "", en: "" },
    tools: [],
    period: "",
    link: "",
    thumb: "",
    thumb_focus: "50% 50%",
    extras: [],
    palette: Math.floor(Math.random() * 8),
  });
  const [drag, setDrag] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");
  const thumbInputRef = useRef(null);
  const extraInputRef = useRef(null);

  function setField(key, value) {
    setForm((f) => ({ ...f, [key]: value }));
  }
  function setTitle(l, v) { setForm(f => ({ ...f, title: { ...f.title, [l]: v } })); }
  function setDesc(l, v) { setForm(f => ({ ...f, desc: { ...f.desc, [l]: v } })); }

  async function onThumbFile(file) {
    if (!file) return;
    setUploading(true); setError("");
    try {
      const url = await window.SB.uploadImage(file, form.id);
      setField("thumb", url);
    } catch (e) {
      console.error(e);
      setError(e.message || "upload failed");
    } finally {
      setUploading(false);
    }
  }
  async function onExtraFiles(files) {
    const arr = Array.from(files || []);
    if (!arr.length) return;
    setUploading(true); setError("");
    try {
      const urls = [];
      for (const f of arr) {
        const url = await window.SB.uploadImage(f, form.id);
        urls.push(url);
      }
      setForm((f) => ({ ...f, extras: [...(f.extras || []), ...urls] }));
    } catch (e) {
      console.error(e);
      setError(e.message || "upload failed");
    } finally {
      setUploading(false);
    }
  }

  async function submit(e) {
    e.preventDefault();
    if (saving || uploading) return;
    const next = { ...form };
    if (!next.title.ko && !next.title.en) {
      next.title = { ...next.title, ko: lang === "ko" ? "(제목 없음)" : "(Untitled)" };
    }
    setSaving(true); setError("");
    try {
      await onSave(next);
    } catch (e) {
      console.error(e);
      setError(t.save_error);
      setSaving(false);
    }
  }

  return (
    <div className="modal-backdrop" {...backdropProps}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <form className="editor" onSubmit={submit}>
          <div className="editor-head">
            <h3>{isNew ? t.new_post : t.edit_post}</h3>
            <button type="button" className="modal-close" style={{ position: "static", width: 30, height: 30 }} onClick={onClose}>✕</button>
          </div>
          <div className="editor-body">
            {/* Thumbnail */}
            <div className="field">
              <label>{t.field_thumb}</label>
              <div
                className={"dropzone" + (drag ? " drag" : "") + (form.thumb ? " has-image" : "")}
                style={{ position: "relative" }}
                onClick={() => thumbInputRef.current.click()}
                onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
                onDragLeave={() => setDrag(false)}
                onDrop={(e) => {
                  e.preventDefault(); setDrag(false);
                  const f = e.dataTransfer.files && e.dataTransfer.files[0];
                  if (f) onThumbFile(f);
                }}
              >
                {form.thumb ? (
                  <>
                    <img
                      className="dropzone-preview"
                      src={form.thumb}
                      alt=""
                      style={{ objectPosition: form.thumb_focus || "50% 50%" }}
                    />
                    <span className="dropzone-replace">REPLACE</span>
                  </>
                ) : (
                  <div className="dropzone-text">
                    {t.field_thumb_hint}
                    <small>JPG · PNG · WEBP</small>
                  </div>
                )}
                <input
                  ref={thumbInputRef}
                  type="file"
                  accept="image/*"
                  style={{ display: "none" }}
                  onChange={(e) => onThumbFile(e.target.files[0])}
                />
              </div>
            </div>

            {/* Thumbnail focal point */}
            {form.thumb && (
              <div className="field">
                <label>{t.field_focus}</label>
                <div className="focus-picker">
                  {FOCUS_POSITIONS.map((val) => (
                    <button
                      key={val}
                      type="button"
                      className={"focus-btn" + ((form.thumb_focus || "50% 50%") === val ? " active" : "")}
                      onClick={() => setField("thumb_focus", val)}
                      aria-label={val}
                    />
                  ))}
                </div>
                <small className="field-hint">{t.field_focus_hint}</small>
              </div>
            )}

            {/* Title (KR/EN) */}
            <div className="field-grid-2">
              <div className="field">
                <label>{t.field_title} (KR)</label>
                <input type="text" value={form.title.ko || ""} onChange={(e) => setTitle("ko", e.target.value)} placeholder="예) 티트리 — 근무시간 관리 SaaS" />
              </div>
              <div className="field">
                <label>{t.field_title} (EN)</label>
                <input type="text" value={form.title.en || ""} onChange={(e) => setTitle("en", e.target.value)} placeholder="e.g. T-tree — SaaS" />
              </div>
            </div>

            {/* Category + Period */}
            <div className="field-grid-2">
              <div className="field">
                <label>{t.field_category}</label>
                <select value={form.category} onChange={(e) => setField("category", e.target.value)}>
                  {CATEGORIES.map(c => <option key={c.id} value={c.id}>{t[c.key]}</option>)}
                </select>
              </div>
              <div className="field">
                <label>{t.field_period}</label>
                <input type="text" value={form.period || ""} onChange={(e) => setField("period", e.target.value)} placeholder="2024 — 재직중" />
              </div>
            </div>

            {/* Description (KR/EN) */}
            <div className="field-grid-2">
              <div className="field">
                <label>{t.field_desc} (KR)</label>
                <textarea value={form.desc.ko || ""} onChange={(e) => setDesc("ko", e.target.value)} placeholder="작업에 대한 설명을 자유롭게 적어주세요." />
              </div>
              <div className="field">
                <label>{t.field_desc} (EN)</label>
                <textarea value={form.desc.en || ""} onChange={(e) => setDesc("en", e.target.value)} placeholder="Describe the work." />
              </div>
            </div>

            {/* Tools */}
            <div className="field">
              <label>{t.field_tools}</label>
              <input
                type="text"
                value={(form.tools || []).join(", ")}
                onChange={(e) => setField("tools", e.target.value.split(",").map(s => s.trim()).filter(Boolean))}
                placeholder="Figma, Photoshop, React"
              />
            </div>

            {/* External link */}
            <div className="field">
              <label>{t.field_link}</label>
              <input type="url" value={form.link || ""} onChange={(e) => setField("link", e.target.value)} placeholder="https://" />
            </div>

            {/* Extras */}
            <div className="field">
              <label>{t.field_extras}</label>
              <div className="extras-list">
                {(form.extras || []).map((src, i) => (
                  <div key={i} className="extra-item">
                    <img src={src} alt="" />
                    <button type="button" className="extra-remove" onClick={() => setField("extras", form.extras.filter((_, j) => j !== i))}>✕</button>
                  </div>
                ))}
                <button type="button" className="extra-add" onClick={() => extraInputRef.current.click()}>+</button>
                <input
                  ref={extraInputRef}
                  type="file"
                  accept="image/*"
                  multiple
                  style={{ display: "none" }}
                  onChange={(e) => onExtraFiles(e.target.files)}
                />
              </div>
            </div>
          </div>
          <div className="editor-foot">
            <div className="editor-status">
              {uploading && <span className="editor-status-msg">{t.uploading}</span>}
              {error && <span className="editor-status-msg error">{error}</span>}
            </div>
            <button type="button" className="btn btn-ghost" onClick={onClose} disabled={saving}>{t.cancel}</button>
            <button type="submit" className="btn btn-primary" disabled={saving || uploading}>
              {saving ? "…" : (isNew ? t.add : t.save)}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

/* ---------- Works section ---------- */
function Works({ posts, t, lang, admin, onOpenPost, onNewPost, onEditPost, onDeletePost, density }) {
  const [filter, setFilter] = useState("all");

  const filtered = useMemo(() => {
    if (filter === "all") return posts;
    return posts.filter(p => p.category === filter);
  }, [posts, filter]);

  const counts = useMemo(() => {
    const map = { all: posts.length };
    for (const c of CATEGORIES) map[c.id] = posts.filter(p => p.category === c.id).length;
    return map;
  }, [posts]);

  return (
    <section className="works" id="works">
      <div className="works-head">
        <div>
          <h2>{t.works_title}</h2>
          <div className="sub">{t.works_subtitle}</div>
        </div>
        <div className="works-count">
          <b>{filtered.length}</b> / {posts.length} {t.works_count}
        </div>
      </div>

      <div className="filters">
        <button
          className={"filter-chip" + (filter === "all" ? " active" : "")}
          onClick={() => setFilter("all")}
        >
          {t.all} <span className="count">{counts.all}</span>
        </button>
        {CATEGORIES.map((c) => (
          <button
            key={c.id}
            className={"filter-chip" + (filter === c.id ? " active" : "")}
            onClick={() => setFilter(c.id)}
          >
            {t[c.key]} <span className="count">{counts[c.id] || 0}</span>
          </button>
        ))}
        {admin && (
          <button className="add-btn-toolbar" onClick={onNewPost}>{t.add_btn}</button>
        )}
      </div>

      {filtered.length === 0 ? (
        <div className="empty">
          <h3>{t.empty_title}</h3>
          <p>{t.empty_sub}</p>
        </div>
      ) : (
        <div className={"grid density-" + density}>
          {filtered.map((p) => (
            <Card
              key={p.id}
              post={p}
              t={t}
              lang={lang}
              admin={admin}
              onClick={onOpenPost}
              onEdit={onEditPost}
              onDelete={onDeletePost}
              density={density}
            />
          ))}
        </div>
      )}
    </section>
  );
}

/* ---------- Footer ---------- */
function Footer({ t, lang }) {
  return (
    <footer className="site-footer">
      <div className="site-footer-inner">
        <div>
          <h2 className="big">{lang === "ko" ? "함께 일해요." : "Let's work together."}</h2>
        </div>
        <div>
          <div className="contact-label">Contact</div>
          <a className="contact-val" href="mailto:mjsun0123@gmail.com">mjsun0123@gmail.com</a>
          <span className="contact-val">010-3848-3523</span>
        </div>
      </div>
      <div className="site-footer-inner copy">
        <span>© {t.footer_year} {t.footer_made}</span>
        <span>Built with care · v1.0</span>
      </div>
    </footer>
  );
}

/* ---------- Admin login modal ---------- */
function LoginModal({ t, onClose, onSuccess }) {
  const backdropProps = useBackdropClose(onClose);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const emailRef = useRef(null);

  useEffect(() => { emailRef.current && emailRef.current.focus(); }, []);

  async function submit(e) {
    e.preventDefault();
    if (busy) return;
    setBusy(true); setError("");
    try {
      const session = await window.SB.signIn(email.trim(), password);
      onSuccess(session);
    } catch (err) {
      setError(err.message || t.login_error);
      setBusy(false);
    }
  }

  return (
    <div className="modal-backdrop" {...backdropProps}>
      <div className="modal" style={{ maxWidth: 420 }} onClick={(e) => e.stopPropagation()}>
        <form className="editor" onSubmit={submit}>
          <div className="editor-head">
            <h3>{t.login_title}</h3>
            <button type="button" className="modal-close" style={{ position: "static", width: 30, height: 30 }} onClick={onClose}>✕</button>
          </div>
          <div className="editor-body">
            <div className="field">
              <label>{t.login_email}</label>
              <input
                ref={emailRef}
                type="email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                autoComplete="username"
                required
              />
            </div>
            <div className="field">
              <label>{t.login_password}</label>
              <input
                type="password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                autoComplete="current-password"
                required
              />
            </div>
            {error && <div className="editor-status-msg error" style={{ marginTop: 4 }}>{error}</div>}
          </div>
          <div className="editor-foot">
            <div className="editor-status"></div>
            <button type="button" className="btn btn-ghost" onClick={onClose} disabled={busy}>{t.cancel}</button>
            <button type="submit" className="btn btn-primary" disabled={busy}>
              {busy ? "…" : t.login_submit}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

/* ---------- Tweaks ---------- */
function PortfolioTweaks({ tweaks, setTweak, lang, t, authEmail, onSignOut, onSeed }) {
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection title={lang === "ko" ? "포인트 컬러" : "Accent color"}>
        <TweakColor
          label={lang === "ko" ? "Accent" : "Accent"}
          value={tweaks.accent}
          onChange={(v) => setTweak("accent", v)}
          options={["#5B6CFF", "#0A0A0A", "#D97757", "#7B9E5C", "#E14B8E"]}
        />
      </TweakSection>
      <TweakSection title={lang === "ko" ? "그리드 밀도" : "Grid density"}>
        <TweakRadio
          label={lang === "ko" ? "밀도" : "Density"}
          value={tweaks.density}
          onChange={(v) => setTweak("density", v)}
          options={[
            { value: "airy", label: lang === "ko" ? "여유" : "Airy" },
            { value: "cozy", label: lang === "ko" ? "기본" : "Cozy" },
            { value: "compact", label: lang === "ko" ? "촘촘" : "Compact" },
          ]}
        />
      </TweakSection>
      <TweakSection title={lang === "ko" ? "관리자 / 데이터" : "Admin / data"}>
        {authEmail && (
          <div className="tweak-row" style={{ fontSize: 12, opacity: 0.7, padding: "4px 0" }}>
            {t.signed_in_as}: <b>{authEmail}</b>
          </div>
        )}
        <TweakButton onClick={onSeed}>{t.seed_initial}</TweakButton>
        <TweakButton onClick={onSignOut}>{t.sign_out}</TweakButton>
      </TweakSection>
    </TweaksPanel>
  );
}

/* ---------- App ---------- */
function App() {
  const [authed, setAuthed] = useState(() => {
    try { return sessionStorage.getItem(AUTH_KEY) === "1"; } catch (e) { return false; }
  });
  const [lang, setLang] = useState(() => {
    try { return localStorage.getItem("wenting_lang") || "ko"; } catch (e) { return "ko"; }
  });
  const [admin, setAdminRaw] = useState(false);
  const [authSession, setAuthSession] = useState(null);
  const [loginOpen, setLoginOpen] = useState(false);
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [openPost, setOpenPost] = useState(null);
  const [editPost, setEditPost] = useState(null);
  const [newPostMode, setNewPostMode] = useState(false);

  // tweaks state
  const [tweaks, setTweaksRaw] = useState(() => {
    try {
      const raw = localStorage.getItem(TWEAKS_KEY);
      if (raw) return JSON.parse(raw);
    } catch (e) {}
    return { accent: "#5B6CFF", density: "cozy" };
  });
  function setTweak(key, value) {
    setTweaksRaw((t) => {
      const next = { ...t, [key]: value };
      try { localStorage.setItem(TWEAKS_KEY, JSON.stringify(next)); } catch (e) {}
      return next;
    });
  }

  useEffect(() => {
    document.documentElement.style.setProperty("--accent", tweaks.accent);
    const darker = shade(tweaks.accent, -0.15);
    document.documentElement.style.setProperty("--accent-hover", darker);
    document.documentElement.style.setProperty("--accent-soft", shade(tweaks.accent, 0.85));
  }, [tweaks.accent]);

  useEffect(() => { try { localStorage.setItem("wenting_lang", lang); } catch (e) {} }, [lang]);

  // Initial data load + auth bootstrap
  useEffect(() => {
    let mounted = true;
    (async () => {
      try {
        const [data, session] = await Promise.all([
          window.SB.fetchPosts(),
          window.SB.getSession(),
        ]);
        if (!mounted) return;
        setPosts(data);
        setAuthSession(session);
      } catch (e) {
        console.error("Initial load failed:", e);
      } finally {
        if (mounted) setLoading(false);
      }
    })();

    const sub = window.SB.onAuthChange((session) => {
      setAuthSession(session);
      if (!session) setAdminRaw(false);
    });

    return () => {
      mounted = false;
      try { sub && sub.unsubscribe && sub.unsubscribe(); } catch (e) {}
    };
  }, []);

  const t = I18N[lang];

  function onPass() {
    try { sessionStorage.setItem(AUTH_KEY, "1"); } catch (e) {}
    setAuthed(true);
  }

  // Gate admin toggle behind Supabase auth — opens login modal if not signed in.
  function setAdmin(next) {
    if (authSession) {
      setAdminRaw(next);
    } else if (next) {
      setLoginOpen(true);
    } else {
      setAdminRaw(false);
    }
  }

  async function handleSave(form) {
    const saved = await window.SB.upsertPost(form);
    setPosts((arr) => {
      const idx = arr.findIndex(p => p.id === saved.id);
      if (idx === -1) return [saved, ...arr];
      const next = [...arr];
      next[idx] = saved;
      return next;
    });
    setEditPost(null);
    setNewPostMode(false);
  }

  async function handleDelete(post) {
    if (!confirm(t.delete_confirm)) return;
    try {
      await window.SB.deletePost(post);
      setPosts((arr) => arr.filter(p => p.id !== post.id));
    } catch (e) {
      console.error(e);
      alert(t.delete_error + "\n" + (e.message || ""));
    }
  }

  async function handleSeed() {
    if (!confirm(t.seed_confirm)) return;
    try {
      await window.SB.seedInitial(SEED_POSTS);
      const data = await window.SB.fetchPosts();
      setPosts(data);
      alert(t.seed_done);
    } catch (e) {
      console.error(e);
      alert((e.message || "seed failed"));
    }
  }

  async function handleSignOut() {
    try { await window.SB.signOut(); } catch (e) { console.error(e); }
    setAdminRaw(false);
    try { sessionStorage.removeItem(AUTH_KEY); } catch (e) {}
    setAuthed(false);
  }

  if (!authed) {
    return <PasswordGate onPass={onPass} t={t} />;
  }

  return (
    <>
      <Header t={t} lang={lang} setLang={setLang} admin={admin} setAdmin={setAdmin} />
      <main>
        <Hero t={t} lang={lang} postCount={posts.length} />
        {loading ? (
          <section className="works" id="works">
            <div className="empty"><h3>{t.loading_posts}</h3></div>
          </section>
        ) : (
          <Works
            posts={posts}
            t={t}
            lang={lang}
            admin={admin}
            onOpenPost={setOpenPost}
            onNewPost={() => { setEditPost(null); setNewPostMode(true); }}
            onEditPost={(p) => { setNewPostMode(false); setEditPost(p); }}
            onDeletePost={handleDelete}
            density={tweaks.density}
          />
        )}
      </main>
      <Footer t={t} lang={lang} />
      {openPost && <DetailModal post={openPost} t={t} lang={lang} onClose={() => setOpenPost(null)} />}
      {(editPost || newPostMode) && (
        <EditorModal
          post={editPost}
          t={t}
          lang={lang}
          onSave={handleSave}
          onClose={() => { setEditPost(null); setNewPostMode(false); }}
        />
      )}
      {loginOpen && (
        <LoginModal
          t={t}
          onClose={() => setLoginOpen(false)}
          onSuccess={(session) => {
            setAuthSession(session);
            setAdminRaw(true);
            setLoginOpen(false);
          }}
        />
      )}
      <PortfolioTweaks
        tweaks={tweaks}
        setTweak={setTweak}
        lang={lang}
        t={t}
        authEmail={authSession && authSession.user && authSession.user.email}
        onSignOut={handleSignOut}
        onSeed={handleSeed}
      />
    </>
  );
}

/* ---------- color helper ---------- */
function shade(hex, amt) {
  // amt: -1..1 (negative = darker, positive = mix with white)
  const c = hex.replace("#", "");
  const num = parseInt(c, 16);
  let r = (num >> 16) & 0xff, g = (num >> 8) & 0xff, b = num & 0xff;
  if (amt < 0) {
    r = Math.max(0, r * (1 + amt));
    g = Math.max(0, g * (1 + amt));
    b = Math.max(0, b * (1 + amt));
  } else {
    r = Math.min(255, r + (255 - r) * amt);
    g = Math.min(255, g + (255 - g) * amt);
    b = Math.min(255, b + (255 - b) * amt);
  }
  const to2 = (n) => Math.round(n).toString(16).padStart(2, "0");
  return "#" + to2(r) + to2(g) + to2(b);
}

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