// App — composes the three columns. Hydrates from /api/state and routes
// every flow (upload, analyze, distill, brainstorm, export) through the API.

class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { error: null }; }
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, info) { console.error('ErrorBoundary caught:', error, info); }
  render() {
    if (!this.state.error) return this.props.children;
    return (
      <div style={{minHeight:'100vh', display:'flex', alignItems:'center', justifyContent:'center', background:'var(--paper-2)', padding:24, fontFamily:'var(--sans)'}}>
        <div style={{maxWidth:520, background:'var(--paper)', borderRadius:14, padding:'32px 36px', boxShadow:'0 24px 60px rgba(0,0,0,.08)'}}>
          <div style={{fontFamily:'var(--serif)', fontSize:24, marginBottom:8}}>잠시 문제가 생겼어요</div>
          <div style={{fontSize:14, color:'var(--ink-3)', marginBottom:18}}>
            화면을 그리는 중 오류가 났어요. 새로고침으로 보통 해결됩니다.
          </div>
          <pre style={{fontSize:11, color:'var(--ink-4)', background:'var(--paper-2)', padding:12, borderRadius:8, overflow:'auto', maxHeight:200}}>
            {String(this.state.error?.stack || this.state.error)}
          </pre>
          <div style={{display:'flex', gap:8, marginTop:18}}>
            <button onClick={() => location.reload()} style={{padding:'10px 20px', borderRadius:999, border:'1px solid var(--ink)', background:'var(--ink)', color:'var(--paper)', cursor:'pointer', fontSize:13}}>새로고침</button>
            <button onClick={() => this.setState({error: null})} style={{padding:'10px 20px', borderRadius:999, border:'1px solid var(--line-2)', background:'var(--paper)', color:'var(--ink-2)', cursor:'pointer', fontSize:13}}>다시 시도</button>
          </div>
        </div>
      </div>
    );
  }
}

const appStyles = {
  shell: (dataCol, analysisCol) => ({
    display:'grid',
    gridTemplateColumns: `${dataCol} ${analysisCol} 1fr`,
    height:'calc(100vh - 64px)', minHeight:600,
    transition:'grid-template-columns .24s ease',
  }),
  collapsed: {
    width:'100%', background:'var(--paper-2)', borderRight:'1px solid var(--line)',
    display:'flex', flexDirection:'column', alignItems:'center', padding:'14px 0', gap:14,
    cursor:'pointer',
  },
  collapsedLabel: {
    writingMode:'vertical-rl', transform:'rotate(180deg)', letterSpacing:'.18em',
    fontFamily:'var(--mono)', fontSize:10, textTransform:'uppercase', color:'var(--ink-3)',
    marginTop:6,
  },
  expandBtn: {
    width:30, height:30, borderRadius:'50%', border:'1px solid var(--line-2)',
    background:'var(--paper)', color:'var(--ink-2)', fontSize:13, cursor:'pointer',
    display:'flex', alignItems:'center', justifyContent:'center',
  },
  collapseBtn: {
    position:'absolute', top:10, right:10, width:24, height:24, borderRadius:'50%',
    border:'1px solid var(--line-2)', background:'var(--paper)', color:'var(--ink-3)',
    fontSize:12, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', zIndex:5,
  },
  bootSplash: {
    position:'fixed', inset:0, display:'flex', alignItems:'center', justifyContent:'center',
    background:'var(--paper)', zIndex:100, fontFamily:'var(--serif)', fontSize:20, color:'var(--ink-3)',
  },
  toast: {
    position:'fixed', bottom:18, left:'50%', transform:'translateX(-50%)',
    padding:'10px 16px', background:'var(--ink)', color:'var(--paper)', borderRadius:999,
    fontSize:12, fontFamily:'var(--sans)', zIndex:60, boxShadow:'0 10px 30px rgba(0,0,0,.12)',
  },
  jobBar: {
    position:'fixed', top:64, left:0, right:0, height:3, zIndex:30,
    background:'var(--paper-2)',
  },
  jobFill: (pct) => ({
    height:'100%', width: (pct*100).toFixed(1)+'%',
    background:'linear-gradient(90deg, var(--accent), var(--accent-3))',
    transition:'width .4s',
  }),
};

function App() {
  const [authChecked, setAuthChecked] = React.useState(false);
  const [user, setUser] = React.useState(null);
  const [session, setSession] = React.useState(null);
  const [loaded, setLoaded] = React.useState(false);
  const [projects, setProjects] = React.useState([]);
  const [analyses, setAnalyses] = React.useState({});
  const [boards, setBoards] = React.useState({});
  const [brainstorms, setBrainstorms] = React.useState({});
  const [distilled, setDistilled] = React.useState({});
  const [activeProjectId, setActiveProjectId] = React.useState(null);
  const [selectedFileId, setSelectedFileId] = React.useState(null);
  const [step, setStep] = React.useState(1);
  const [toast, setToast] = React.useState(null);
  const [newProjectOpen, setNewProjectOpen] = React.useState(false);
  const [analyzing, setAnalyzing] = React.useState({});
  const [job, setJob] = React.useState(null); // active batch job
  const [references, setReferences] = React.useState({}); // {pid: {saved, last_results, last_query}}
  const [dataCollapsed, setDataCollapsed] = React.useState(false);
  const [analysisCollapsed, setAnalysisCollapsed] = React.useState(false);
  const pollRef = React.useRef(null);

  const flash = (msg) => {
    setToast(msg);
    setTimeout(() => setToast(null), 2400);
  };

  const hydrate = React.useCallback(async () => {
    const s = await API.state();
    setProjects(s.projects || []);
    setAnalyses(s.analyses || {});
    setBoards(s.boards || {});
    setBrainstorms(s.brainstorms || {});
    setDistilled(s.distilled || {});
    setReferences(s.references || {});
    if (!activeProjectId && s.projects?.[0]) setActiveProjectId(s.projects[0].id);
    setLoaded(true);
    return s;
  }, [activeProjectId]);

  React.useEffect(() => {
    // Wait for Supabase session, then hydrate. onChange handles token refresh.
    let unsub = null;
    (async () => {
      if (!window.palette_auth) { setAuthChecked(true); return; }
      const s = await window.palette_auth.waitForSession();
      setSession(s);
      setAuthChecked(true);
      unsub = window.palette_auth.onChange((next) => {
        setSession(next);
        if (!next) { setUser(null); setLoaded(false); }
      });
    })();
    const onUnauth = () => { setSession(null); setUser(null); };
    window.addEventListener('palette:unauth', onUnauth);
    return () => { if (unsub) unsub(); window.removeEventListener('palette:unauth', onUnauth); };
  }, []);

  React.useEffect(() => {
    if (!session) return;
    (async () => {
      try {
        const me = await API.me();
        setUser(me);
        await hydrate();
      } catch(e) { console.error(e); flash('세션을 불러올 수 없어요 — 다시 로그인해주세요'); }
    })();
  }, [session, hydrate]);

  const onSignOut = async () => {
    try { await window.palette_auth.signOut(); } catch(e) {}
    setSession(null); setUser(null); setProjects([]); setAnalyses({}); setBoards({});
  };

  // File-id → file-name map for chip provenance.
  const fileNameById = React.useMemo(() => {
    const m = {};
    for (const p of projects) for (const f of p.folders||[]) for (const fl of f.files||[]) m[fl.id] = fl.name;
    return m;
  }, [projects]);

  const project = projects.find(p => p.id === activeProjectId);
  const board = boards[activeProjectId] || { pinned:[], watching:[] };
  const messages = brainstorms[activeProjectId] || [];
  const distilledKws = distilled[activeProjectId] || [];

  const onSwitchProject = (pid) => {
    if (pid === activeProjectId) return;
    setActiveProjectId(pid);
    setSelectedFileId(null);
  };

  const setBoard = async (b) => {
    setBoards(prev => ({...prev, [activeProjectId]: b}));
    try { await API.saveBoard(activeProjectId, b); } catch(e) { flash('Save failed'); }
  };

  const setMessages = async (next) => {
    setBrainstorms(prev => ({...prev, [activeProjectId]: next}));
    try { await API.saveBrainstorm(activeProjectId, next); } catch(e) { /* silent */ }
  };

  const addKw = (k) => {
    const exists = board.pinned.some(x => x.w === k.w && x.ko === k.ko);
    if (exists) { flash('Already pinned'); return; }
    const next = {
      ...board,
      pinned: [...board.pinned, {...k, color: ['accent','accent-2','accent-3','ink'][board.pinned.length%4]}],
    };
    setBoard(next);
  };

  const savedRefs = (references[activeProjectId] || {}).saved || [];

  const onSaveRef = async (candidate) => {
    try {
      const res = await API.archiveReference(activeProjectId, candidate);
      if (res.already) { flash('이미 저장된 레퍼런스'); return; }
      setReferences(r => {
        const cur = r[activeProjectId] || {};
        return {...r, [activeProjectId]: {...cur, saved: [...(cur.saved || []), res.ref]}};
      });
      flash('레퍼런스 저장됨');
    } catch(e) { flash('저장 실패'); }
  };

  const onDownloadRef = async ({ ref_id, candidate, folder_id }) => {
    if (!activeProjectId) return;
    flash('자료실로 다운로드 중…');
    try {
      const res = await API.downloadReference(activeProjectId, { ref_id, candidate, folder_id });
      // Refresh projects so the new file shows up in the dataroom.
      const s = await API.state();
      setProjects(s.projects || []);
      if (res.note) {
        flash(`⚠ ${res.note} · 썸네일 저장됨`);
      } else {
        flash(`자료실 저장 완료 · ${res.file?.name || 'file'}`);
      }
      return res;
    } catch(e) {
      console.error(e);
      flash('다운로드 실패 — ' + (e.message || ''));
    }
  };

  const onRemoveRef = async (rid) => {
    try {
      await API.removeReference(activeProjectId, rid);
      setReferences(r => {
        const cur = r[activeProjectId] || {};
        return {...r, [activeProjectId]: {...cur, saved: (cur.saved || []).filter(s => s.id !== rid)}};
      });
    } catch(e) { flash('삭제 실패'); }
  };

  const onSearchedRefs = (_candidates) => {};

  const onPatchBlock = async (file_id, block_id, patch) => {
    try {
      const res = await API.patchBlock(file_id, block_id, patch);
      setAnalyses(prev => {
        const ana = prev[file_id];
        if (!ana) return prev;
        const blocks = (ana.blocks || []).map(b => b.id === block_id ? res.block : b);
        return {...prev, [file_id]: {...ana, blocks, keywords: res.keywords || ana.keywords}};
      });
    } catch(e) { console.error(e); flash('Edit failed'); }
  };

  const onCreateProject = async ({client, name, brief}) => {
    const p = await API.createProject({client, name, brief});
    setProjects(ps => [...ps, p]);
    setBoards(bs => ({...bs, [p.id]:{pinned:[], watching:[]}}));
    setBrainstorms(bs => ({...bs, [p.id]:[]}));
    onSwitchProject(p.id);
    setNewProjectOpen(false);
    flash('Project created');
  };

  const onUpload = async (folderId, file) => {
    if (!project) return;
    flash(`Uploading ${file.name}…`);
    const entry = await API.uploadFile(project.id, folderId, file);
    setProjects(ps => ps.map(p => p.id !== project.id ? p : ({
      ...p,
      folders: p.folders.map(f => f.id !== folderId ? f : ({...f, files:[...f.files, entry]}))
    })));
    setSelectedFileId(entry.id);
    flash(`Uploaded ${file.name}`);
  };

  const applyAnalysis = (file_id, ana) => {
    setAnalyses(a => ({...a, [file_id]: ana}));
    setProjects(ps => ps.map(p => ({
      ...p,
      folders: p.folders.map(f => ({
        ...f,
        files: f.files.map(fl => fl.id !== file_id ? fl : ({
          ...fl,
          status: ana?.error ? 'idle' : 'analyzed',
        })),
      })),
    })));
  };

  const onAnalyzeFile = (file_id) => {
    // Stream analyze: block placeholders appear immediately, each card fills
    // in as that block's ideation finishes. Much lower perceived latency.
    setAnalyzing(a => ({...a, [file_id]: {phase:'parse', completed:0, total:0}}));
    setSelectedFileId(file_id);
    flash('분석 시작 · reading file…');

    let fellBack = false;
    const close = API.streamAnalyze(file_id, {
      onParsed: (p) => {
        // Seed analysis with empty block skeletons so cards appear at once.
        const skeletons = (p.blocks || []).map(b => ({
          id: b.id, title: b.title, page: b.page,
          summary: '', keywords: [], scenes: [], _pending: true,
        }));
        setAnalyses(a => ({...a, [file_id]: {blocks: skeletons, keywords: [], block_count: p.block_count, _streaming: true}}));
        setAnalyzing(a => ({...a, [file_id]: {phase:'ideate', completed:0, total: p.block_count || 0}}));
      },
      onBlock: (p) => {
        setAnalyses(a => {
          const prev = a[file_id] || {blocks: [], keywords: []};
          const blocks = (prev.blocks || []).map(b =>
            b.id === p.block.id ? {...p.block, _pending:false} : b
          );
          return {...a, [file_id]: {...prev, blocks}};
        });
        setAnalyzing(a => ({...a, [file_id]: {phase:'ideate', completed: p.completed, total: p.total}}));
      },
      onDone: (full) => {
        applyAnalysis(file_id, full);
        setAnalyzing(a => { const x={...a}; delete x[file_id]; return x; });
        // Refresh distilled panel since backend auto-updates it on stream done.
        API.state().then(s => setDistilled(s.distilled || {})).catch(()=>{});
        flash(full?.low_signal ? '분석 완료 · low-signal doc' : '분석 완료');
      },
      onError: async (err) => {
        if (fellBack) return;
        fellBack = true;
        console.warn('stream failed, falling back to POST', err);
        // Graceful fallback for environments that block SSE.
        try {
          const res = await API.analyzeFile(file_id);
          applyAnalysis(file_id, res.analysis);
          flash(res.analysis?.low_signal ? '분석 완료 · low-signal doc' : '분석 완료');
        } catch(e) {
          flash('분석 실패 · ' + (err?.error || e.message || ''));
        } finally {
          setAnalyzing(a => { const x={...a}; delete x[file_id]; return x; });
        }
      },
    });
    // Close SSE if user navigates away.
    return close;
  };

  const stopPolling = () => {
    if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; }
  };

  const onAnalyzeAll = async () => {
    if (!project) return;
    if (job?.status === 'running') { flash('Already analyzing'); return; }
    const todo = project.folders.flatMap(f => f.files.filter(fl => fl.status !== 'analyzed')).map(fl => fl.id);
    if (!todo.length) { flash('Nothing to analyze'); return; }
    flash(`Analyzing ${todo.length} files…`);
    setAnalyzing(a => todo.reduce((m,id) => ({...m, [id]: true}), a));
    let res;
    try { res = await API.analyzeProject(project.id); }
    catch(e) { flash('Batch analyze failed'); setAnalyzing({}); return; }
    if (!res.job_id) { flash(res.message || 'Nothing to analyze'); setAnalyzing({}); return; }

    setJob({id: res.job_id, total: res.todo, completed: 0, status:'running'});
    stopPolling();
    let lastCompleted = 0;
    pollRef.current = setInterval(async () => {
      try {
        const j = await API.getJob(res.job_id);
        setJob({id: j.id, total: j.total, completed: j.completed, status: j.status});
        if (j.completed > lastCompleted) {
          // Re-hydrate just the analyses we now have.
          const fresh = await API.state();
          setAnalyses(fresh.analyses || {});
          setProjects(fresh.projects || []);
          lastCompleted = j.completed;
        }
        if (j.status !== 'running') {
          stopPolling();
          setAnalyzing({});
          flash(`Done — ${j.completed}/${j.total} analyzed`);
          setTimeout(() => setJob(null), 1500);
        }
      } catch(e) { /* keep polling */ }
    }, 1500);
  };

  const onDeleteFile = async (file_id) => {
    await API.deleteFile(file_id);
    setProjects(ps => ps.map(p => ({
      ...p,
      folders: p.folders.map(f => ({...f, files: f.files.filter(fl => fl.id !== file_id)}))
    })));
    setAnalyses(a => { const x={...a}; delete x[file_id]; return x; });
    if (selectedFileId === file_id) setSelectedFileId(null);
  };

  const onDeleteProject = async (pid) => {
    if (!confirm('Delete this project and all its files?')) return;
    await API.deleteProject(pid);
    setProjects(ps => ps.filter(p => p.id !== pid));
    setBoards(bs => { const x={...bs}; delete x[pid]; return x; });
    setBrainstorms(bs => { const x={...bs}; delete x[pid]; return x; });
    if (activeProjectId === pid) {
      const next = projects.find(p => p.id !== pid);
      setActiveProjectId(next?.id || null);
      setSelectedFileId(null);
    }
    flash('Project deleted');
  };

  const onDistill = async () => {
    if (!project) return;
    flash('Distilling cross-file keywords…');
    try {
      const res = await API.distill(project.id);
      setDistilled(d => ({...d, [project.id]: res.keywords}));
      flash(`Distilled ${res.count} keywords`);
    } catch(e) { flash('Distill failed'); }
  };

  const onExport = async () => {
    if (!project) return;
    try {
      const res = await API.exportProject(project.id);
      const blob = new Blob([res.markdown], {type:'text/markdown;charset=utf-8'});
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = (project.name.replace(/[^\w\-]+/g,'_') || 'project') + '.md';
      a.click();
      URL.revokeObjectURL(url);
      flash('저장됨 · .data/exports/ 에 복사본');
    } catch(e) { flash('내보내기 실패'); }
  };

  const onExportDeck = async () => {
    if (!project) return;
    const url = `/api/projects/${encodeURIComponent(project.id)}/deck.html`;
    window.open(url, '_blank', 'noopener,noreferrer');
  };

  if (!authChecked) {
    return <div style={appStyles.bootSplash}>Palette · Keyword Agent — loading…</div>;
  }
  if (!session) {
    // Invite token may be in the URL as /invite/<token> or ?invite=<token>
    const path = window.location.pathname || '';
    const m = path.match(/\/invite\/([\w\-]+)/);
    const inviteToken = (m && m[1]) || new URLSearchParams(window.location.search).get('invite') || null;
    return <LoginScreen inviteToken={inviteToken} onReady={() => { /* onAuthStateChange handles */ }}/>;
  }
  if (!loaded) {
    return <div style={appStyles.bootSplash}>Palette · Keyword Agent — loading…</div>;
  }
  if (!project) {
    return (
      <div>
        <TopBar project={{name:'Palette', client:''}} step={step} setStep={setStep}
                user={user} onSignOut={onSignOut}
                onNewProject={() => setNewProjectOpen(true)} onAnalyzeAll={()=>{}}/>
        <div style={{padding:60, textAlign:'center', color:'var(--ink-3)', fontFamily:'var(--serif)', fontSize:20}}>
          <div style={{fontSize:36, marginBottom:8}}>✨</div>
          <div style={{fontSize:22, color:'var(--ink)', marginBottom:6}}>
            {user?.name || user?.email || '팀원'}님, 환영해요!
          </div>
          <div style={{fontSize:14, color:'var(--ink-3)', marginBottom:20}}>
            아직 프로젝트가 없어요. 첫 RFP 를 업로드할 프로젝트를 만들어볼까요?
          </div>
          <button style={{border:'1px solid var(--ink)', padding:'10px 20px', borderRadius:999, background:'var(--ink)', color:'var(--paper)', cursor:'pointer', fontSize:13}} onClick={() => setNewProjectOpen(true)}>＋ 새 프로젝트 시작 · Create your first project</button>
        </div>
        {newProjectOpen && <NewProjectModal onClose={() => setNewProjectOpen(false)} onCreate={onCreateProject}/>}
      </div>
    );
  }

  return (
    <div>
      <TopBar project={project} step={step} setStep={setStep}
              user={user}
              onSignOut={onSignOut}
              onNewProject={() => setNewProjectOpen(true)}
              onAnalyzeAll={onAnalyzeAll}
              onExport={onExport}/>
      {job && job.status === 'running' && (
        <div style={appStyles.jobBar}>
          <div style={appStyles.jobFill(job.total ? job.completed / job.total : 0)}/>
        </div>
      )}
      <div style={appStyles.shell(
        dataCollapsed ? '44px' : '320px',
        analysisCollapsed ? '44px' : 'minmax(420px, 1fr)',
      )} id="palette-shell">
        {dataCollapsed ? (
          <div style={appStyles.collapsed} onClick={() => setDataCollapsed(false)} title="자료실 열기">
            <button style={appStyles.expandBtn} onClick={e => { e.stopPropagation(); setDataCollapsed(false); }}>›</button>
            <div style={appStyles.collapsedLabel}>01 · Dataroom · 자료실</div>
          </div>
        ) : (
          <div style={{position:'relative', minHeight:0, display:'flex', flexDirection:'column', height:'100%', overflow:'hidden'}}>
            <button style={appStyles.collapseBtn} onClick={() => setDataCollapsed(true)} title="자료실 접기 · collapse">‹</button>
            <div style={{flex:1, minHeight:0, display:'flex', flexDirection:'column'}}>
            <Dataroom
              projects={projects}
              activeProjectId={activeProjectId}
              setActiveProjectId={onSwitchProject}
              selectedFileId={selectedFileId}
              setSelectedFileId={setSelectedFileId}
              onNewProject={() => setNewProjectOpen(true)}
              onUpload={onUpload}
              onAnalyzeAll={onAnalyzeAll}
              onAnalyzeFile={onAnalyzeFile}
              onDeleteFile={onDeleteFile}
              onDeleteProject={onDeleteProject}
              analyzing={analyzing}
              job={job}
            />
            </div>
          </div>
        )}
        {analysisCollapsed ? (
          <div style={appStyles.collapsed} onClick={() => setAnalysisCollapsed(false)} title="분석 열기">
            <button style={appStyles.expandBtn} onClick={e => { e.stopPropagation(); setAnalysisCollapsed(false); }}>›</button>
            <div style={appStyles.collapsedLabel}>02 · Analysis · 분석</div>
          </div>
        ) : (
          <div style={{position:'relative', minHeight:0, display:'flex', flexDirection:'column', height:'100%', overflow:'hidden'}}>
            <button style={appStyles.collapseBtn} onClick={() => setAnalysisCollapsed(true)} title="분석 접기 · collapse">‹</button>
            <div style={{flex:1, minHeight:0, display:'flex', flexDirection:'column'}}>
            <AnalysisCol
              project={project}
              selectedFileId={selectedFileId}
              analyses={analyses}
              onAddKw={addKw}
              onAnalyzeFile={onAnalyzeFile}
              analyzing={analyzing}
              onPatchBlock={onPatchBlock}
            />
            </div>
          </div>
        )}
        <BoardCol
          project={project}
          board={board}
          setBoard={setBoard}
          messages={messages}
          setMessages={setMessages}
          distilled={distilledKws}
          onDistill={onDistill}
          onExport={onExport}
          onExportDeck={onExportDeck}
          fileNameById={fileNameById}
          onAddKw={addKw}
          savedRefs={savedRefs}
          onSaveRef={onSaveRef}
          onRemoveRef={onRemoveRef}
          onDownloadRef={onDownloadRef}
          onSearchedRefs={onSearchedRefs}
          step={step}
        />
      </div>
      {newProjectOpen && <NewProjectModal onClose={() => setNewProjectOpen(false)} onCreate={onCreateProject}/>}
      {toast && <div style={appStyles.toast}>{toast}</div>}
    </div>
  );
}

function NewProjectModal({ onClose, onCreate }) {
  const [client, setClient] = React.useState('');
  const [name, setName] = React.useState('');
  const [brief, setBrief] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const submit = async () => {
    if (!name.trim()) return;
    setBusy(true);
    try { await onCreate({client, name, brief}); } finally { setBusy(false); }
  };
  const overlay = { position:'fixed', inset:0, background:'rgba(0,0,0,.4)', zIndex:50, display:'flex', alignItems:'center', justifyContent:'center' };
  const modal = { width:460, background:'var(--paper)', borderRadius:12, padding:24, boxShadow:'0 30px 60px rgba(0,0,0,.2)', fontFamily:'var(--sans)' };
  const label = { fontFamily:'var(--mono)', fontSize:10, letterSpacing:'.14em', color:'var(--ink-3)', textTransform:'uppercase', marginBottom:6 };
  const input = { width:'100%', border:'1px solid var(--line-2)', borderRadius:6, padding:'8px 10px', fontSize:13, background:'var(--paper-2)', outline:'none', marginBottom:16, fontFamily:'var(--sans)', color:'var(--ink)' };
  const textarea = {...input, minHeight:90, resize:'vertical', fontFamily:'var(--serif)', fontSize:14, lineHeight:1.5};
  return (
    <div style={overlay} onClick={onClose}>
      <div style={modal} onClick={e => e.stopPropagation()}>
        <div style={{fontFamily:'var(--serif)', fontSize:22, marginBottom:4}}>New project</div>
        <div style={{fontSize:12, color:'var(--ink-3)', marginBottom:18}}>Creates 01·Brief / 02·References / 03·Ideation / 04·Deck folders.</div>
        <div style={label}>Client</div>
        <input style={input} placeholder="e.g. LG전자" value={client} onChange={e=>setClient(e.target.value)}/>
        <div style={label}>Project name</div>
        <input style={input} placeholder="e.g. LG gram · Global 2026" value={name} onChange={e=>setName(e.target.value)}/>
        <div style={label}>Brief (short)</div>
        <textarea style={textarea} placeholder="One or two sentences on positioning, tone, deliverable…" value={brief} onChange={e=>setBrief(e.target.value)}/>
        <div style={{display:'flex', justifyContent:'flex-end', gap:8, marginTop:6}}>
          <button style={{height:32, padding:'0 14px', border:'1px solid var(--line-2)', background:'var(--paper)', borderRadius:999, cursor:'pointer', fontSize:12}} onClick={onClose}>Cancel</button>
          <button disabled={busy || !name.trim()} style={{height:32, padding:'0 14px', border:'1px solid var(--ink)', background:'var(--ink)', color:'var(--paper)', borderRadius:999, cursor:'pointer', fontSize:12, opacity: busy || !name.trim() ? .6 : 1}} onClick={submit}>{busy ? 'Creating…' : 'Create project'}</button>
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('app')).render(
  <ErrorBoundary><App/></ErrorBoundary>
);
