<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<title>ADO2 Hookit — 8초 숏폼 생성기</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
  /* ===== ADO2 Design Tokens (from tokens.css) ===== */
  @import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css");
  :root{
    --ado-purple: rgb(174,114,249);
    --ado-purple-light: rgb(207,171,251);
    --ado-mint: rgb(148,251,224);
    --bg-0: rgb(1,25,26);
    --bg-1: rgb(0,34,36);
    --bg-2: rgb(25,38,40);
    --bg-3: rgb(1,57,59);
    --stroke-1: rgb(50,75,80);
    --stroke-2: rgb(37,57,60);
    --text-white: rgb(255,255,255);
    --text-teal-1: rgb(155,202,204);
    --text-teal-2: rgb(106,176,179);
    --text-teal-3: rgb(101,126,131);
    --text-mute-1: rgb(178,191,193);
    --text-mute-2: rgb(217,223,224);
    --r-md:12px; --r-lg:20px; --r-pill:999px;
    --font:"Pretendard","Apple SD Gothic Neo",-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
    --shadow-card:0 1px 0 rgba(0,0,0,0.4),0 12px 32px -8px rgba(0,0,0,0.45);
  }
  *{box-sizing:border-box;}
  html,body{margin:0;background:var(--bg-1);color:var(--text-white);font-family:var(--font);-webkit-font-smoothing:antialiased;letter-spacing:-0.006em;word-break:keep-all;overflow-wrap:anywhere;}

  /* ===== Layout: centered, no cluttered sidebar ===== */
  .topbar{
    position:sticky;top:0;z-index:10;
    height:72px;display:flex;align-items:center;justify-content:space-between;
    padding:0 32px;background:rgba(0,34,36,0.82);backdrop-filter:blur(12px);
    border-bottom:1px solid var(--stroke-2);
  }
  .wordmark{font:800 22px/1 var(--font);letter-spacing:-0.02em;}
  .wordmark em{font-style:normal;background:linear-gradient(135deg,var(--ado-mint),var(--ado-purple-light));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
  .step-count{font:700 15px/1 var(--font);color:var(--text-teal-2);}
  .step-count b{color:var(--text-white);}

  .wrap{max-width:1000px;margin:0 auto;padding:48px 32px 120px;}

  /* ===== Hero ===== */
  .hero{margin-bottom:48px;}
  .eyebrow{display:inline-flex;align-items:center;gap:10px;font:700 14px/1 var(--font);letter-spacing:0.14em;text-transform:uppercase;color:var(--ado-mint);margin-bottom:24px;}
  .eyebrow::before{content:"";width:9px;height:9px;border-radius:999px;background:var(--ado-mint);box-shadow:0 0 12px var(--ado-mint);}
  h1.hero-title{font:800 clamp(40px,6vw,68px)/1.05 var(--font);letter-spacing:-0.035em;margin:0;}
  h1.hero-title em{font-style:normal;background:linear-gradient(135deg,var(--ado-mint),var(--ado-purple-light));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
  .hero-lede{font:500 20px/1.6 var(--font);color:var(--text-teal-1);margin:28px 0 0;max-width:680px;}

  /* ===== Stepper (4) ===== */
  .stepper{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:48px;}
  .step{display:flex;align-items:center;gap:16px;padding:20px 22px;background:var(--bg-2);border:1px solid var(--stroke-1);border-radius:16px;transition:.15s;}
  .step.active{border-color:var(--ado-purple);background:linear-gradient(180deg,rgba(174,114,249,0.10),rgba(174,114,249,0.02));}
  .step .num{width:42px;height:42px;flex:0 0 42px;border-radius:999px;display:flex;align-items:center;justify-content:center;font:800 18px/1 var(--font);background:rgba(255,255,255,0.06);color:var(--text-teal-1);}
  .step.active .num{background:var(--ado-purple);color:#fff;box-shadow:0 0 0 6px rgba(174,114,249,0.18);}
  .step.done .num{background:var(--ado-mint);color:var(--bg-1);}
  .step b{display:block;font:700 17px/1.2 var(--font);color:var(--text-mute-2);}
  .step.active b{color:#fff;}
  .step span{display:block;font:500 14px/1.2 var(--font);color:var(--text-teal-3);margin-top:5px;}
  @media(max-width:760px){.stepper{grid-template-columns:repeat(2,1fr);} h1.hero-title{font-size:38px;}}

  /* ===== Card ===== */
  .card{background:var(--bg-2);border:1px solid var(--stroke-1);border-radius:24px;padding:44px;box-shadow:var(--shadow-card);}
  .card h2{font:800 32px/1.15 var(--font);margin:0 0 12px;letter-spacing:-0.018em;}
  .card .sub{font:500 17px/1.55 var(--font);color:var(--text-teal-1);margin:0 0 36px;max-width:620px;}

  /* ===== Upload ===== */
  .upload{height:260px;border:2px dashed var(--stroke-1);border-radius:20px;background:var(--bg-1);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;cursor:pointer;transition:.15s;}
  .upload:hover,.upload.drag{border-color:var(--ado-mint);background:rgba(148,251,224,0.04);}
  .upload .ico{width:64px;height:64px;border-radius:999px;background:rgba(148,251,224,0.12);display:flex;align-items:center;justify-content:center;color:var(--ado-mint);}
  .upload b{font:700 20px/1.2 var(--font);}
  .upload span{font:500 15px/1.4 var(--font);color:var(--text-teal-1);}

  .thumbs{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:24px;}
  @media(max-width:640px){.thumbs{grid-template-columns:repeat(2,1fr);}}
  .thumb{position:relative;aspect-ratio:1;border-radius:14px;overflow:hidden;border:1px solid var(--stroke-1);background:var(--bg-3);}
  .thumb img{width:100%;height:100%;object-fit:cover;display:block;}
  .thumb .n{position:absolute;top:8px;left:8px;height:24px;padding:0 10px;border-radius:999px;background:rgba(0,0,0,0.6);backdrop-filter:blur(8px);font:700 13px/24px var(--font);}
  .thumb .x{position:absolute;top:8px;right:8px;width:26px;height:26px;border-radius:999px;background:rgba(0,0,0,0.6);backdrop-filter:blur(8px);border:none;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;}

  /* ===== Fields ===== */
  .fields{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:28px;}
  @media(max-width:640px){.fields{grid-template-columns:1fr;}}
  .field label{display:block;font:700 15px/1.3 var(--font);letter-spacing:0.04em;color:var(--text-mute-2);margin-bottom:12px;}
  .field input{width:100%;height:58px;padding:0 20px;background:var(--bg-1);border:1px solid var(--stroke-1);border-radius:12px;color:#fff;font:500 16px/1 var(--font);outline:none;}
  .field input:focus{border-color:var(--ado-mint);box-shadow:0 0 0 3px rgba(148,251,224,0.14);}
  .field textarea{width:100%;min-height:120px;padding:16px 18px;background:var(--bg-1);border:1px solid var(--stroke-1);border-radius:12px;color:#fff;font:500 16px/1.6 var(--font);outline:none;resize:vertical;}
  .field textarea:focus{border-color:var(--ado-mint);box-shadow:0 0 0 3px rgba(148,251,224,0.14);}
  .field .hint{font:500 14px/1.5 var(--font);color:var(--text-teal-3);margin-top:10px;}
  .form-stack{display:flex;flex-direction:column;gap:24px;}
  .kind-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;}
  @media(max-width:640px){.kind-tiles{grid-template-columns:1fr;}}
  .tile{border:1.5px solid var(--stroke-1);background:var(--bg-1);border-radius:16px;padding:24px;cursor:pointer;transition:.15s;}
  .tile:hover{border-color:rgba(148,251,224,0.4);}
  .tile.sel{border-color:var(--ado-mint);background:rgba(148,251,224,0.05);}
  .tile b{font:800 20px/1.2 var(--font);display:block;}
  .tile span{font:500 14px/1.4 var(--font);color:var(--text-teal-1);margin-top:8px;display:block;}
  .req{color:var(--ado-mint);}
  .opt{color:var(--text-teal-3);font-weight:500;margin-left:6px;text-transform:none;letter-spacing:0;}

  /* ===== Action bar ===== */
  .actionbar{margin-top:28px;display:flex;align-items:center;justify-content:space-between;gap:20px;background:var(--bg-2);border:1px solid var(--stroke-1);border-radius:20px;padding:22px 28px;}
  .actionbar .stat b{font:800 22px/1 var(--font);}
  .actionbar .stat span{display:block;font:600 13px/1 var(--font);color:var(--text-teal-3);letter-spacing:0.06em;text-transform:uppercase;margin-top:6px;}
  .btn{height:58px;padding:0 32px;border-radius:999px;border:none;cursor:pointer;font:700 17px/1 var(--font);display:inline-flex;align-items:center;gap:10px;transition:.15s;}
  .btn-primary{background:linear-gradient(135deg,var(--ado-mint),var(--ado-purple));color:var(--bg-0);}
  .btn-primary:disabled{opacity:0.4;cursor:not-allowed;}
  .btn-ghost{background:transparent;border:1.5px solid var(--stroke-1);color:var(--text-mute-2);}
  .btn-ghost:hover{border-color:var(--ado-mint);color:#fff;}

  /* ===== Analysis ===== */
  .profile-banner{display:flex;align-items:center;gap:24px;padding:28px;border-radius:18px;background:linear-gradient(135deg,rgba(174,114,249,0.16),rgba(148,251,224,0.06));border:1px solid rgba(174,114,249,0.3);margin-bottom:32px;}
  .profile-banner .score{width:88px;height:88px;flex:0 0 88px;border-radius:999px;border:3px solid var(--ado-mint);display:flex;flex-direction:column;align-items:center;justify-content:center;}
  .profile-banner .score b{font:800 30px/1 var(--font);}
  .profile-banner .score span{font:600 11px/1 var(--font);color:var(--text-teal-2);margin-top:4px;letter-spacing:0.08em;}
  .profile-banner .pinfo b{font:800 24px/1.2 var(--font);display:block;}
  .profile-banner .pinfo p{font:500 16px/1.5 var(--font);color:var(--text-teal-1);margin:8px 0 0;}

  .sp-row{display:flex;align-items:center;gap:18px;padding:16px 0;border-top:1px solid var(--stroke-2);}
  .sp-row:first-child{border-top:none;}
  .sp-row .sp-name{flex:0 0 200px;font:700 17px/1.3 var(--font);}
  .sp-row .bar{flex:1;height:8px;border-radius:999px;background:rgba(255,255,255,0.06);overflow:hidden;}
  .sp-row .bar i{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,var(--ado-mint),var(--ado-purple));}
  .sp-row .sp-score{flex:0 0 48px;text-align:right;font:800 18px/1 var(--font);font-variant-numeric:tabular-nums;}

  .chips{display:flex;flex-wrap:wrap;gap:10px;margin-top:24px;}
  .chip{height:38px;padding:0 18px;border-radius:999px;display:inline-flex;align-items:center;font:600 15px/1 var(--font);background:rgba(148,251,224,0.10);border:1px solid rgba(148,251,224,0.3);color:var(--ado-mint);}

  /* ===== Generating ===== */
  .agent{display:grid;grid-template-columns:1fr 220px;gap:28px;align-items:center;padding:26px 0;border-top:1px solid var(--stroke-2);}
  .agent:first-child{border-top:none;}
  .agent .aname b{font:700 19px/1.2 var(--font);display:block;}
  .agent .aname span{font:500 15px/1.3 var(--font);color:var(--text-teal-1);margin-top:6px;display:block;}
  .agent .pbar{height:10px;border-radius:999px;background:rgba(255,255,255,0.06);overflow:hidden;}
  .agent .pbar i{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,var(--ado-mint),var(--ado-purple));transition:width .5s cubic-bezier(.4,0,.2,1);}
  .agent .pct{font:800 16px/1 var(--font);color:var(--text-teal-2);text-align:right;margin-top:8px;font-variant-numeric:tabular-nums;}
  .dot{width:9px;height:9px;border-radius:999px;display:inline-block;margin-right:10px;}
  .dot.run{background:var(--ado-purple);box-shadow:0 0 0 4px rgba(174,114,249,0.18);}
  .dot.done{background:var(--ado-mint);}
  .dot.queue{background:rgba(255,255,255,0.2);}

  /* ===== Result ===== */
  .result-grid{display:grid;grid-template-columns:320px 1fr;gap:48px;align-items:start;}
  @media(max-width:760px){.result-grid{grid-template-columns:1fr;}}
  .phone{width:300px;margin:0 auto;background:#0a1414;border-radius:40px;padding:10px;box-shadow:0 30px 70px -20px rgba(0,0,0,0.6);}
  .phone video{width:100%;border-radius:30px;display:block;background:#000;aspect-ratio:9/16;object-fit:cover;}
  .res-h{font:800 28px/1.2 var(--font);margin:0 0 8px;}
  .res-sub{font:500 16px/1.5 var(--font);color:var(--text-teal-1);margin:0 0 28px;}
  .caption-box{background:var(--bg-1);border:1px solid var(--stroke-1);border-radius:16px;padding:24px;font:500 16px/1.7 var(--font);color:var(--text-mute-2);white-space:pre-wrap;}
  .res-actions{display:flex;gap:14px;margin-top:24px;flex-wrap:wrap;}

  .spinner{width:18px;height:18px;border-radius:999px;border:2.5px solid rgba(148,251,224,0.2);border-top-color:var(--ado-mint);animation:spin .9s linear infinite;}
  @keyframes spin{to{transform:rotate(360deg);}}
  @keyframes pulse{0%,100%{opacity:.5;}50%{opacity:1;}}
  .pulse{animation:pulse 1.6s ease-in-out infinite;}
  .note{font:500 14px/1.6 var(--font);color:var(--text-teal-3);margin-top:20px;}

  /* Brand logo */
  .brand-logo{height:30px;width:auto;display:block;}

  /* ===== Gallery ===== */
  .gallery-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;}
  @media(max-width:760px){.gallery-grid{grid-template-columns:repeat(2,1fr);}}
  @media(max-width:480px){.gallery-grid{grid-template-columns:1fr;}}
  .gallery-card{cursor:pointer;border-radius:20px;overflow:hidden;background:var(--bg-2);border:1px solid var(--stroke-1);transition:.15s;}
  .gallery-card:hover{border-color:var(--ado-mint);transform:translateY(-2px);box-shadow:0 8px 28px rgba(0,0,0,0.35);}
  .gallery-thumb{position:relative;aspect-ratio:9/16;background:var(--bg-3);overflow:hidden;}
  .gallery-play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.28);opacity:0;transition:.15s;color:#fff;}
  .gallery-card:hover .gallery-play{opacity:1;}

  /* Hero selling-point field (emphasized) */
  .field-hero label{color:var(--ado-mint);}
  .field-hero textarea{min-height:64px;border-color:rgba(148,251,224,0.35);}  /* 절반 축소 */

  /* Tone auto-match chip */
  .tone-chip{display:inline-flex;align-items:center;gap:10px;height:40px;padding:0 18px;border-radius:999px;background:rgba(174,114,249,0.16);border:1px solid rgba(174,114,249,0.4);color:var(--ado-purple-light);font:700 15px/1 var(--font);}
  .tone-chip i{width:8px;height:8px;border-radius:999px;background:var(--ado-purple-light);box-shadow:0 0 10px var(--ado-purple-light);}

  /* Result: editable caption + actions */
  .caption-edit{width:100%;min-height:200px;padding:20px 22px;background:var(--bg-1);border:1px solid var(--stroke-1);border-radius:16px;color:var(--text-mute-2);font:500 16px/1.7 var(--font);outline:none;resize:vertical;}
  .caption-edit:focus{border-color:var(--ado-mint);box-shadow:0 0 0 3px rgba(148,251,224,0.14);}
  .cap-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;}
  .cap-head .lbl{font:700 13px/1 var(--font);letter-spacing:0.08em;text-transform:uppercase;color:var(--text-mute-1);}
  .copy-btn{background:transparent;border:none;cursor:pointer;color:var(--ado-mint);font:700 14px/1 var(--font);display:inline-flex;align-items:center;gap:6px;}
  .res-badges{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap;}

  /* 자막 스크립트 생성기 */
  .script-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;}
  .btn-mini{height:42px;padding:0 20px;border-radius:999px;border:none;cursor:pointer;font:700 15px/1 var(--font);background:linear-gradient(135deg,var(--ado-mint),var(--ado-purple));color:var(--bg-0);display:inline-flex;align-items:center;gap:8px;}
  .btn-mini:disabled{opacity:0.4;cursor:not-allowed;}
  .script-card{background:var(--bg-1);border:1px solid var(--stroke-1);border-radius:14px;padding:16px 18px;margin-top:12px;}
  .sc-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;}
  .sc-label{font:700 12px/1 var(--font);letter-spacing:0.08em;text-transform:uppercase;color:var(--ado-mint);}
  .sc-actions{display:flex;gap:8px;}
  .sc-actions button{background:transparent;border:1px solid var(--stroke-1);border-radius:999px;height:30px;padding:0 14px;color:var(--text-mute-2);font:600 13px/1 var(--font);cursor:pointer;}
  .sc-actions button:hover{border-color:var(--ado-mint);color:#fff;}
  .sc-text{font:500 16px/1.6 var(--font);color:var(--text-mute-2);margin:0;white-space:pre-wrap;}
  .script-card textarea{width:100%;min-height:70px;padding:12px 14px;background:var(--bg-0);border:1px solid var(--ado-mint);border-radius:10px;color:#fff;font:500 16px/1.6 var(--font);outline:none;resize:vertical;}
</style>
</head>
<body>
<div id="root"></div>

<script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js"></script>

<script type="text/babel">
const { useState, useRef, useEffect } = React;

/* ===== Minimal icon set (7) — 단순 라인 아이콘만 ===== */
const Icon = {
  Image: (p) => <svg width={p.size||20} height={p.size||20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="1.6"/><path d="M3 17l5-4 4 3 3-2 6 4"/></svg>,
  Check: (p) => <svg width={p.size||20} height={p.size||20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M5 12l5 5 9-11"/></svg>,
  Right: (p) => <svg width={p.size||20} height={p.size||20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>,
  Left: (p) => <svg width={p.size||20} height={p.size||20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 12H5M11 6l-6 6 6 6"/></svg>,
  Play: (p) => <svg width={p.size||20} height={p.size||20} viewBox="0 0 24 24" fill="currentColor"><path d="M7 5l13 7-13 7V5z"/></svg>,
  Close: (p) => <svg width={p.size||16} height={p.size||16} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 5l14 14M19 5L5 19"/></svg>,
  Download: (p) => <svg width={p.size||20} height={p.size||20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>,
};

/* ===== Config ===== */
// 백엔드 주소. 같은 출처(FastAPI가 이 페이지를 서빙)면 "" 로 두면 /api/generate 로 전송.
// 프론트/백엔드를 분리 배포할 땐 빌드/배포 시점에 window.__API_BASE__ 를 head 에 주입
//   예) window.__API_BASE__ = "https://api.example.com";
// 생성 실패/서버 미연결 시 데모 영상은 표시하지 않고 에러 카드만 보여준다.
const API_BASE = (typeof window !== "undefined" && window.__API_BASE__) || "";

const STEPS = [
  { k:"setup", n:"1", b:"사진 업로드", s:"4~5장" },
  { k:"interview", n:"2", b:"정보 입력", s:"몇 가지만 답하기" },
  { k:"generating", n:"3", b:"영상 생성", s:"AI 카메라 + 자막" },
  { k:"result", n:"4", b:"완성", s:"다운로드" },
];

const SCRIPT_BLOCKS = [
  { key:"intro",   label:"인트로" },
  { key:"selling", label:"셀링포인트" },
  { key:"story",   label:"감성 스토리" },
  { key:"cta",     label:"CTA" },
];

function App(){
  const [step, setStep] = useState("setup");
  const [photos, setPhotos] = useState([]); // {url, name}
  const [drag, setDrag] = useState(false);
  // 인터뷰 답변 (사람이 직접 = 인텔리전스)
  const [kind, setKind] = useState("");        // "place" | "product"
  const [bizName, setBizName] = useState("");
  const [addr, setAddr] = useState("");        // 주소 또는 판매 사이트 URL
  const [price, setPrice] = useState("");
  const [selling, setSelling] = useState("");  // 강력한 한방 셀링포인트
  const [prog, setProg] = useState([0,0,0]);
  const [videoUrl, setVideoUrl] = useState(null); // 성공 시에만 채움 (데모 폴백 없음)
  const [errMsg, setErrMsg] = useState("");        // 생성 실패 메시지 (있으면 결과 화면이 에러 카드로 전환)
  const [srvCaption, setSrvCaption] = useState(null);
  const [srvProfile, setSrvProfile] = useState(null);
  const [editedCaption, setEditedCaption] = useState("");
  const [copied, setCopied] = useState(false);
  const [note, setNote] = useState("");
  const [script, setScript] = useState(null);        // {intro,selling,story,cta}
  const [scriptEditing, setScriptEditing] = useState({});
  const [gallery, setGallery] = useState(null);      // null=미로드 / []~[...]=로드됨
  const [galleryOpen, setGalleryOpen] = useState(false);
  const [galleryVideo, setGalleryVideo] = useState(null); // 선택된 영상 {job_id,video_url,...}
  const [scriptLoading, setScriptLoading] = useState(false);
  const fileRef = useRef();
  const pollRef = useRef(null); // 진행 중인 폴링 타이머 (언마운트/리셋 시 정리)

  // 언마운트 시 폴링 타이머 정리
  useEffect(()=>()=>{ if(pollRef.current) clearTimeout(pollRef.current); }, []);

  // 톤 자동 매칭 (인텔리전스 IP를 가볍게 노출 — 유형 기반)
  const tone = kind === "product"
    ? { label: "역동·쇼케이스 톤", profile: "Rhythm Reveal" }
    : kind === "message"
    ? { label: "따뜻한 메시지 톤", profile: "Still Cinema" }
    : { label: "감성·시네마틱 톤", profile: "Still Cinema" };

  // 유형별 입력 placeholder (분기를 한 곳에 모음)
  const KIND_PH = {
    place:   { name: "예: 스테이 머뭄",  addr: "예: 전북 군산시 절골길 18", price: "예: 1박 19만원~",  selling: "예: 옆방 손님 없는 완전한 독채, 개별 정원까지" },
    product: { name: "예: 브랜드명 숄더백", addr: "예: smartstore.naver.com/...", price: "예: 12,900원", selling: "예: 새벽 수확한 감귤만 당일 착즙 — 설탕 무첨가" },
    message: { name: "예: 생일 축하",    addr: "예: 관련 링크 (선택)",      price: "",              selling: "예: 늘 곁에 있어줘서 고마워, 생일 축하해" },
  };
  const ph = KIND_PH[kind] || KIND_PH.place;

  const stepIdx = STEPS.findIndex(s=>s.k===step);
  const canGenerate = kind && bizName.trim() && selling.trim();

  function addFiles(list){
    const imgs = Array.from(list).filter(f=>f.type.startsWith("image/"));
    const next = imgs.map(f=>({ url:URL.createObjectURL(f), name:f.name, file:f }));
    setPhotos(p=>[...p, ...next]);
  }
  function removePhoto(i){ setPhotos(p=>p.filter((_,idx)=>idx!==i)); }

  // 작업 상태(stage_index 0..2)를 3개 진행바 배열로 변환.
  //   이전 단계 = 100%, 현재 단계 = 진행 중(서버 progress 또는 점진 증가), 이후 단계 = 0%
  function barsForStatus(st, prevBars){
    const idx = st.stage_index || 0;
    return [0,1,2].map(i=>{
      if(st.status === "done") return 100;
      if(i < idx) return 100;
      if(i > idx) return 0;
      // 현재 진행 중인 바: 95%까지 조금씩 차오르게(완료 신호는 폴링이 줌)
      const cur = (prevBars && prevBars[i]) || 0;
      return Math.min(95, Math.max(cur + Math.round(Math.random()*8+4), 8));
    });
  }

  function stopPolling(){ if(pollRef.current){ clearTimeout(pollRef.current); pollRef.current = null; } }

  // 작업 완료/실패 시 결과 화면으로
  function finishWith(st){
    if(st.video_url) setVideoUrl((API_BASE||"") + st.video_url);
    if(st.caption){ setSrvCaption(st.caption); setEditedCaption(st.caption); }
    if(st.profile) setSrvProfile(st.profile);
    setProg([100,100,100]);
    setStep("result");
  }

  // job_id 를 주기적으로 폴링하며 진행바를 갱신
  function pollJob(jobId){
    fetch(`${API_BASE}/api/jobs/${jobId}`)
      .then(r=> r.ok ? r.json() : Promise.reject(new Error("HTTP "+r.status)))
      .then(st=>{
        setProg(prev=> barsForStatus(st, prev));
        if(st.caption && !srvCaption){ setSrvCaption(st.caption); setEditedCaption(st.caption); }
        if(st.profile) setSrvProfile(st.profile);
        if(st.status === "done"){ finishWith(st); return; }
        if(st.status === "error"){
          // 실패 시 데모 영상 표시 안 함 — 에러 카드만 보여준다.
          setVideoUrl(null);
          setErrMsg(st.error || "알 수 없는 오류로 생성에 실패했습니다.");
          setProg([100,100,100]); setStep("result");
          return;
        }
        pollRef.current = setTimeout(()=>pollJob(jobId), 2000); // 2초 간격 폴링
      })
      .catch(()=>{ // 폴링 실패 → 잠시 후 재시도 (네트워크 일시 단절 대비)
        pollRef.current = setTimeout(()=>pollJob(jobId), 3000);
      });
  }

  function startGenerate(){
    stopPolling();
    setStep("generating");
    setProg([6,0,0]); setNote(""); setErrMsg(""); setVideoUrl(null); setSrvCaption(null); setCopied(false);
    setSrvProfile(tone.profile);
    setEditedCaption(caption); // 로컬 폴백; 서버 응답 오면 교체

    const fd = new FormData();
    fd.append("kind", kind);
    fd.append("biz_name", bizName);
    fd.append("selling", selling);
    if(addr) fd.append("addr", addr);
    if(price) fd.append("price", price);
    photos.forEach(p=> p.file && fd.append("photos", p.file, p.name));

    // 1) 작업 제출 → job_id 즉시 수신, 2) job_id 폴링
    fetch(`${API_BASE}/api/generate`, { method:"POST", body:fd })
      .then(r=> r.ok ? r.json() : Promise.reject(new Error("HTTP "+r.status)))
      .then(st=>{
        if(!st.job_id) throw new Error("no job_id");
        pollJob(st.job_id);
      })
      .catch(()=>{ // 서버 연결 실패 → 데모 안 띄우고 에러 카드
        setVideoUrl(null);
        setErrMsg("서버에 연결하지 못했습니다. 잠시 후 다시 시도해 주세요.");
        setProg([100,100,100]); setStep("result");
      });
  }
  function reset(){ stopPolling(); setStep("setup"); setPhotos([]); setKind(""); setBizName(""); setAddr(""); setPrice(""); setSelling(""); setProg([0,0,0]); setNote(""); setErrMsg(""); setSrvCaption(null); setSrvProfile(null); setEditedCaption(""); setCopied(false); setVideoUrl(null); setScript(null); setScriptEditing({}); }

  function copyCaption(){
    navigator.clipboard.writeText(editedCaption).then(()=>{ setCopied(true); setTimeout(()=>setCopied(false), 1800); });
  }

  function openGallery(){
    setGalleryOpen(true);
    setGalleryVideo(null);
    fetch(`${API_BASE}/api/outputs`)
      .then(r=> r.ok ? r.json() : Promise.reject())
      .then(list=> setGallery(list))
      .catch(()=> setGallery([]));
  }
  function closeGallery(){ setGalleryOpen(false); setGalleryVideo(null); }

  function fmtDate(ts){
    if(!ts) return "";
    const d = new Date(ts * 1000);
    return `${d.getFullYear()}.${String(d.getMonth()+1).padStart(2,"0")}.${String(d.getDate()).padStart(2,"0")} ${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}`;
  }

  // 백엔드 없을 때 입력값 기반 폴백 (Vercel 정적 배포에서도 동작)
  function localScript(){
    if(kind === "message"){
      const title = bizName || "전하는 마음";
      return {
        intro: title,
        selling: selling || "전하고 싶은 한마디",
        story: "사진 한 장에 마음을 담아, 오래 기억될 순간으로.",
        cta: "마음이 닿기를",
      };
    }
    const place = kind !== "product";
    const name = bizName || (place ? "이 곳" : "이 제품");
    return {
      intro: place ? `${addr ? addr + " " : ""}${name}, 아직 아무도 모르는 곳`
                   : `${name}, 한 번 쓰면 다시 찾게 되는`,
      selling: selling || (place ? "옆방 손님 없는 완전한 독채" : "하나하나 정성껏 만든 단 하나"),
      story: place ? "도착하는 순간, 일상의 속도가 천천히 느려집니다. 오늘 밤은 오롯이 당신만의 시간."
                   : "바쁜 하루의 끝, 작은 사치 하나가 마음을 데웁니다.",
      cta: place ? `지금 프로필 링크에서 ${name} 예약하기`
                 : (addr ? `지금 ${addr}에서 만나보기` : "지금 주문하기"),
    };
  }

  async function generateScript(){
    setScriptLoading(true); setScriptEditing({});
    try {
      const r = await fetch(`${API_BASE}/api/captions`, {
        method:"POST", headers:{ "Content-Type":"application/json" },
        body: JSON.stringify({ kind, biz_name:bizName, addr, price, selling }),
      });
      if(!r.ok) throw new Error("HTTP "+r.status);
      const d = await r.json();
      setScript({ intro:d.intro, selling:d.selling, story:d.story, cta:d.cta });
    } catch {
      setScript(localScript()); // 폴백
    } finally { setScriptLoading(false); }
  }
  function toggleScriptEdit(key){ setScriptEditing(s=>({ ...s, [key]: !s[key] })); }
  function delScriptBlock(key){ setScript(s=>({ ...s, [key]: null })); }

  // 인터뷰 답변 → 결과 캡션
  const caption = `${bizName||"내 가게"}${addr?` · ${addr}`:""}${price?`\n${price}`:""}\n\n${selling||"고객에게 가장 자랑하고 싶은 한 가지"}\n\n${kind==="product"?"#제품추천 #신상 #쇼핑":"#감성장소 #핫플 #여행"} #ADO2\n\n— 실제 사진 기반, AI 카메라 효과를 적용한 영상입니다.`;

  return (
    <div>
      <div className="topbar">
        <img className="brand-logo" src="./assets/ado2-logo-white.png" alt="ADO2.AI" />
        <div style={{display:"flex",alignItems:"center",gap:16}}>
          {!galleryOpen && <div className="step-count"><b>{stepIdx+1}</b> / 4</div>}
          <button className="btn btn-ghost" style={{height:40,padding:"0 18px",fontSize:14}} onClick={galleryOpen ? closeGallery : openGallery}>
            {galleryOpen ? "← 돌아가기" : "생성된 영상"}
          </button>
        </div>
      </div>
      <div className="wrap">

        {/* 갤러리 오버레이 */}
        {galleryOpen && !galleryVideo && (
          <div>
            <div style={{marginBottom:32}}>
              <h2 style={{font:"800 32px/1.15 var(--font)",margin:"0 0 8px",letterSpacing:"-0.018em"}}>생성된 영상</h2>
              <p style={{font:"500 17px/1.55 var(--font)",color:"var(--text-teal-1)",margin:0}}>지금까지 만들어진 영상을 다운로드하거나 다시 볼 수 있습니다.</p>
            </div>
            {gallery === null && (
              <div style={{textAlign:"center",padding:"60px 0",color:"var(--text-teal-3)"}}>
                <div className="spinner" style={{margin:"0 auto 16px"}}></div>불러오는 중…
              </div>
            )}
            {gallery && gallery.length === 0 && (
              <div className="card" style={{textAlign:"center",padding:"60px 0",color:"var(--text-teal-3)"}}>
                아직 완성된 영상이 없습니다.
              </div>
            )}
            {gallery && gallery.length > 0 && (
              <div className="gallery-grid">
                {gallery.map(v=>(
                  <div key={v.job_id} className="gallery-card" onClick={()=>setGalleryVideo(v)}>
                    <div className="gallery-thumb">
                      <video src={(API_BASE||"")+v.video_url} muted playsInline preload="metadata" style={{width:"100%",height:"100%",objectFit:"cover",borderRadius:16,display:"block"}}/>
                      <div className="gallery-play"><Icon.Play size={22}/></div>
                    </div>
                    <div style={{padding:"14px 4px 4px"}}>
                      <div style={{font:"700 16px/1.3 var(--font)",color:"var(--text-mute-2)",marginBottom:4,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>{v.biz_name || v.job_id}</div>
                      <div style={{font:"500 13px/1 var(--font)",color:"var(--text-teal-3)"}}>{fmtDate(v.created_at)}</div>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
        )}

        {/* 갤러리 상세 (영상 선택 시) */}
        {galleryOpen && galleryVideo && (
          <div className="card">
            <button onClick={()=>setGalleryVideo(null)} className="btn btn-ghost" style={{marginBottom:24,height:40,padding:"0 18px",fontSize:14}}>← 목록으로</button>
            <div className="result-grid">
              <div className="phone">
                <video src={(API_BASE||"")+galleryVideo.video_url} controls playsInline autoPlay></video>
              </div>
              <div>
                <h2 className="res-h">{galleryVideo.biz_name || galleryVideo.job_id}</h2>
                <p className="res-sub">{fmtDate(galleryVideo.created_at)}</p>
                {galleryVideo.profile && (
                  <div className="res-badges" style={{marginBottom:20}}>
                    <span className="tone-chip"><i></i>{galleryVideo.profile}</span>
                  </div>
                )}
                {galleryVideo.caption && (
                  <>
                    <div className="cap-head"><span className="lbl">업로드 캡션</span></div>
                    <div className="caption-box">{galleryVideo.caption}</div>
                  </>
                )}
                <div className="res-actions">
                  <a className="btn btn-primary" href={(API_BASE||"")+galleryVideo.video_url} download><Icon.Download/> 다운로드</a>
                </div>
              </div>
            </div>
          </div>
        )}

        {/* 메인 생성 플로우 (갤러리 닫혀있을 때만) */}
        {!galleryOpen && step==="setup" && (
          <div className="hero">
            <div className="eyebrow">ADO2 Hookit · 8초 숏폼</div>
            <h1 className="hero-title">사진 몇 장과 한마디면,<br/><em>8초 홍보 영상</em>이 됩니다</h1>
            <p className="hero-lede">복잡한 분석 없이 — 사진을 올리고 몇 가지만 답하면, AI 카메라 무브와 자막을 입힌 <span style={{whiteSpace:"nowrap"}}>세로형 숏폼</span>이 완성됩니다.</p>
          </div>
        )}

        {!galleryOpen && <div className="stepper">
          {STEPS.map((s,i)=>(
            <div key={s.k} className={"step"+(i===stepIdx?" active":"")+(i<stepIdx?" done":"")}>
              <div className="num">{i<stepIdx ? <Icon.Check size={20}/> : s.n}</div>
              <div><b>{s.b}</b><span>{s.s}</span></div>
            </div>
          ))}
        </div>}

        {/* STEP 1 — UPLOAD */}
        {!galleryOpen && step==="setup" && (
          <div className="card">
            <h2>사진 업로드</h2>
            <p className="sub">홍보할 장소나 물건 사진 4~5장. 전경 1장 · 디테일 2~3장을 섞으면 영상의 서사가 살아납니다.</p>
            <div className={"upload"+(drag?" drag":"")}
              onClick={()=>fileRef.current.click()}
              onDragOver={e=>{e.preventDefault();setDrag(true);}}
              onDragLeave={()=>setDrag(false)}
              onDrop={e=>{e.preventDefault();setDrag(false);addFiles(e.dataTransfer.files);}}>
              <div className="ico"><Icon.Image size={30}/></div>
              <b>여기로 사진을 끌어다 놓거나 클릭</b>
              <span>JPG · PNG · 4장 이상</span>
              <input ref={fileRef} type="file" accept="image/*" multiple style={{display:"none"}}
                onChange={e=>addFiles(e.target.files)} />
            </div>

            {photos.length>0 && (
              <div className="thumbs">
                {photos.map((p,i)=>(
                  <div className="thumb" key={i}>
                    <img src={p.url} alt=""/>
                    <div className="n">{i+1}</div>
                    <button className="x" onClick={()=>removePhoto(i)}><Icon.Close/></button>
                  </div>
                ))}
              </div>
            )}

            <div className="actionbar">
              <div className="stat"><b>{photos.length}장</b><span>업로드됨 {photos.length<4?`· ${4-photos.length}장 더`:"· 준비 완료"}</span></div>
              <button className="btn btn-primary" disabled={photos.length<4} onClick={()=>setStep("interview")}>
                다음: 정보 입력 <Icon.Right/>
              </button>
            </div>
          </div>
        )}

        {/* STEP 2 — INTERVIEW (사람이 직접 답 = 인텔리전스) */}
        {!galleryOpen && step==="interview" && (
          <div className="card">
            <h2>몇 가지만 알려주세요</h2>
            <p className="sub">사장님·마케터가 가장 잘 아는 정보로 카피를 만듭니다.</p>

            <div className="form-stack">
              <div className="field">
                <label>영상의 목적이 무엇인가요? <span className="req">*</span></label>
                <div className="kind-tiles">
                  <div className={"tile"+(kind==="place"?" sel":"")} onClick={()=>setKind("place")}>
                    <b>장소</b><span>펜션 · 카페 · 식당 · 매장 등 공간</span>
                  </div>
                  <div className={"tile"+(kind==="product"?" sel":"")} onClick={()=>setKind("product")}>
                    <b>물건</b><span>제품, 음식 등 정적인 아이템</span>
                  </div>
                  <div className={"tile"+(kind==="message"?" sel":"")} onClick={()=>setKind("message")}>
                    <b>메시지</b><span>축하 · 안부 · 홍보 등</span>
                  </div>
                </div>
                {kind && (
                  <div style={{marginTop:16}}>
                    <span className="tone-chip"><i></i>자동 매칭 톤 · {tone.label}</span>
                  </div>
                )}
              </div>

              <div className="field">
                <label>업체 · 상품 · 메시지 제목 <span className="req">*</span></label>
                <input value={bizName} onChange={e=>setBizName(e.target.value)} placeholder={ph.name}/>
              </div>

              <div className="field field-hero">
                <label>가장 자랑하고 싶은 한 가지 <span className="req">*</span></label>
                <textarea value={selling} onChange={e=>setSelling(e.target.value)} placeholder={ph.selling}></textarea>
                <div className="hint">이 한마디가 영상의 후킹 타이틀·핵심 메시지가 됩니다. 구체적일수록 좋아요.</div>
              </div>

              {/* AI 자막 스크립트 생성기 */}
              <div className="field">
                <div className="script-head">
                  <label style={{margin:0}}>영상 자막 · 스크립트</label>
                  <button className="btn-mini" onClick={generateScript} disabled={scriptLoading || !selling.trim()}>
                    {scriptLoading ? "생성 중…" : (script ? "다시 생성" : "자막 생성하기")}
                  </button>
                </div>
                {!script && (
                  <div className="hint">위 한 가지를 적은 뒤 누르면, AI가 <b>인트로 · 셀링포인트 · 감성 스토리 · CTA</b> 4가지를 만들어줍니다. 각 블록은 수정·삭제할 수 있어요.</div>
                )}
                {script && SCRIPT_BLOCKS.map(b => script[b.key] != null && (
                  <div className="script-card" key={b.key}>
                    <div className="sc-top">
                      <span className="sc-label">{b.label}</span>
                      <span className="sc-actions">
                        <button onClick={()=>toggleScriptEdit(b.key)}>{scriptEditing[b.key] ? "완료" : "수정"}</button>
                        <button onClick={()=>delScriptBlock(b.key)}>삭제</button>
                      </span>
                    </div>
                    {scriptEditing[b.key]
                      ? <textarea value={script[b.key]} onChange={e=>setScript({ ...script, [b.key]: e.target.value })} />
                      : <p className="sc-text">{script[b.key]}</p>}
                  </div>
                ))}
              </div>

              <div className="field">
                <label>주소 또는 판매 사이트 URL<span className="opt">(선택)</span></label>
                <input value={addr} onChange={e=>setAddr(e.target.value)} placeholder={ph.addr}/>
                <div className="hint">영상 엔드카드·해시태그에 활용됩니다.</div>
              </div>

              <div className="field">
                <label>가격 정보<span className="opt">(선택)</span></label>
                <input value={price} onChange={e=>setPrice(e.target.value)} placeholder={ph.price}/>
              </div>
            </div>

            <div className="actionbar">
              <button className="btn btn-ghost" onClick={()=>setStep("setup")}><Icon.Left/> 사진 다시</button>
              <button className="btn btn-primary" disabled={!canGenerate} onClick={startGenerate}>영상 생성 <Icon.Right/></button>
            </div>
          </div>
        )}

        {/* STEP 3 — GENERATING */}
        {!galleryOpen && step==="generating" && (
          <div className="card">
            <h2>영상 생성 중</h2>
            <p className="sub">한 번의 생성으로 트랜지션·자막·사운드까지 완성됩니다.</p>
            {[
              {b:"카피·구성 설계", s:"입력한 셀링포인트 → 후킹 타이틀·자막 구성"},
              {b:"AI 영상 생성", s:"사진 → 8초 시네마틱 (AI 카메라 효과)"},
              {b:"자막·합성", s:"후킹 타이틀 · 셀링 배지 · 엔드카드"},
            ].map((a,i)=>{
              const done = prog[i]>=100, run = prog[i]>0 && prog[i]<100;
              return (
                <div className="agent" key={i}>
                  <div className="aname">
                    <b><span className={"dot "+(done?"done":run?"run":"queue")}></span>{a.b}</b>
                    <span>{a.s}</span>
                  </div>
                  <div>
                    <div className="pbar"><i style={{width:prog[i]+"%"}}></i></div>
                    <div className="pct">{done?"완료":prog[i]+"%"}</div>
                  </div>
                </div>
              );
            })}
          </div>
        )}

        {/* STEP 4 — RESULT (성공: 영상 / 실패: 에러 카드) */}
        {!galleryOpen && step==="result" && errMsg && (
          <div className="card">
            <h2 className="res-h">영상 생성에 실패했습니다</h2>
            <p className="res-sub">아래 사유를 확인하고 다시 시도해 주세요. (데모 영상은 표시하지 않습니다)</p>
            <div className="caption-box" style={{borderColor:"rgba(255,120,120,0.4)"}}>{errMsg}</div>
            <div className="res-actions">
              <button className="btn btn-primary" onClick={startGenerate}>다시 시도</button>
              <button className="btn btn-ghost" onClick={reset}>처음으로</button>
            </div>
          </div>
        )}
        {!galleryOpen && step==="result" && !errMsg && videoUrl && (
          <div className="card">
            <div className="result-grid">
              <div className="phone">
                <video src={videoUrl} controls playsInline poster=""></video>
              </div>
              <div>
                <h2 className="res-h">완성됐습니다</h2>
                <p className="res-sub">9:16 · 8초 · 사운드 포함 — 바로 업로드할 수 있는 완성본입니다.</p>
                <div className="res-badges">
                  <span className="chip">9:16 세로형</span>
                  <span className="chip">8초</span>
                  {srvProfile && <span className="tone-chip"><i></i>{srvProfile}</span>}
                </div>
                <div className="cap-head">
                  <span className="lbl">업로드 캡션 · 수정 가능</span>
                  <button className="copy-btn" onClick={copyCaption}>{copied ? "복사됨" : "복사"}</button>
                </div>
                <textarea className="caption-edit" value={editedCaption} onChange={e=>setEditedCaption(e.target.value)}></textarea>
                <div className="res-actions">
                  <a className="btn btn-primary" href={videoUrl} download><Icon.Download/> 영상 다운로드</a>
                  <button className="btn btn-ghost" onClick={reset}>새 영상 만들기</button>
                </div>
                {note && <p className="note">※ {note}</p>}
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

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