diff --git a/ui/builder.html b/ui/builder.html
index 61032cc..6c0c772 100644
--- a/ui/builder.html
+++ b/ui/builder.html
@@ -194,6 +194,31 @@ input,textarea,button,select{font-family:inherit}
.detail-hdr-lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);padding:7px 12px 6px;border-bottom:1px solid var(--border);background:var(--surface-alt);flex-shrink:0}
.detail-node-head{display:flex;align-items:center;gap:5px;padding:8px 10px;border-bottom:1px solid var(--border);background:var(--surface-alt);flex-shrink:0}
.detail-body{padding:10px;display:flex;flex-direction:column;gap:8px}
+
+/* ── Tab 2: 내용 채우기 ──────────────────── */
+#panel-filler{flex-direction:column}
+.filler-toolbar{background:var(--surface);border-bottom:1px solid var(--border);padding:8px 12px;display:flex;align-items:center;gap:8px;flex-shrink:0}
+.filler-title{font-size:13px;font-weight:600;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0 4px}
+.filler-main{flex:1;display:flex;overflow:hidden}
+.filler-form-wrap{flex:1;overflow-y:auto;padding:18px 20px;background:var(--bg)}
+.filler-form-wrap::-webkit-scrollbar{width:5px}
+.filler-form-wrap::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
+.filler-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:8px;color:var(--muted);text-align:center;line-height:1.7}
+.filler-preview{width:400px;min-width:260px;display:flex;flex-direction:column;border-left:1px solid var(--border)}
+/* 섹션 카드 */
+.fsec{margin-bottom:10px}
+.fsec-head{display:flex;align-items:center;gap:7px;padding:7px 10px;background:var(--surface);border:1.5px solid var(--border);border-radius:var(--r) var(--r) 0 0}
+.fsec-head.alone{border-radius:var(--r)}
+.fsec-body{padding:8px;background:var(--surface);border:1.5px solid var(--border);border-top:none;border-radius:0 0 var(--r) var(--r);display:flex;flex-direction:column;gap:6px}
+.fsec-body>.fsec{margin-bottom:0}
+.fsec-body>.fsec .fsec-head{border-left:3px solid var(--accent);padding-left:8px}
+.fsec-mark{font-size:15px;flex-shrink:0;line-height:1}
+.fsec-name{font-size:13px;font-weight:500}
+/* 값 입력 */
+.fval-row{display:flex;flex-direction:column;gap:3px}
+.fval-lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
+.fval-inp{width:100%;font-size:13px;border:1.5px solid var(--border);border-radius:6px;padding:6px 8px;outline:none;font-family:inherit;background:var(--surface-alt);resize:vertical;line-height:1.5}
+.fval-inp:focus{border-color:var(--border-focus);background:#fff}
@@ -257,10 +282,48 @@ input,textarea,button,select{font-family:inherit}
-
-
📋
-
내용 채우기
-
Step 3에서 구현 예정
+
+
+ 📋
+ 템플릿 없음
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📂 템플릿 JSON 불러오기
+
Builder의 JSON slim 또는 JSON full 출력을 붙여넣으세요.
+
+
+
+
+
@@ -1046,6 +1109,167 @@ function newDoc(){
S={titleMark:'✅',title:'',nodes:[]}; refresh();
}
+// ════════════════════════════════════════════════════════
+// Tab 2: 내용 채우기
+// ════════════════════════════════════════════════════════
+let FT = null; // filler template (parsed JSON root)
+let FV = {}; // filler values: nodeId → string
+let fillerTab = 'text';
+
+function openFillerImport(){ document.getElementById('fillerImportModal').classList.add('open') }
+function closeFillerImport(){ document.getElementById('fillerImportModal').classList.remove('open') }
+
+function doLoadFillerTemplate(){
+ const raw=document.getElementById('fillerImportTxt').value.trim(); if(!raw) return;
+ let obj;
+ try{ obj=JSON.parse(raw); }catch(e){ alert('JSON 파싱 오류:\n'+e.message); return; }
+ if(obj.type!=='ROOT'){ alert('최상위 타입이 ROOT가 아닙니다.'); return; }
+ FT=obj; FV={};
+ initFillerValues(FT);
+ closeFillerImport();
+ document.getElementById('fillerImportTxt').value='';
+ renderFiller();
+}
+
+function initFillerValues(node){
+ for(const child of node.children??[]){
+ if(child.type==='VALUE'){
+ if(!child.id) child.id='fv_'+Math.random().toString(36).slice(2);
+ FV[child.id]=String(child.value??'');
+ } else if(child.type==='SECTION'){
+ if(!child.id) child.id='fs_'+Math.random().toString(36).slice(2);
+ initFillerValues(child);
+ }
+ }
+}
+
+function clearFiller(){
+ if(!confirm('템플릿을 지우고 처음부터 시작할까요?')) return;
+ FT=null; FV={};
+ renderFiller();
+}
+
+function renderFiller(){
+ const form=document.getElementById('fillerForm');
+ const titleEl=document.getElementById('fillerTitle');
+ const outEl=document.getElementById('fillerOut');
+ if(!FT){
+ titleEl.textContent='템플릿 없음';
+ form.innerHTML=`
+
📋
+
템플릿이 없습니다
+
위의 📂 JSON 불러오기 버튼으로
Builder에서 만든 양식 JSON을 가져오세요
+
`;
+ outEl.className='preview-text dim'; outEl.textContent='내용을 채우면 미리보기가 표시됩니다';
+ return;
+ }
+ const title=FT.attributes?.title??'(제목 없음)';
+ const mark=FT.attributes?.mark??'';
+ titleEl.textContent=(mark?mark+' ':'')+title;
+ let html='';
+ for(const child of FT.children??[]) if(child.type==='SECTION') html+=renderFillerSection(child);
+ form.innerHTML=html||'
';
+ updateFillerPreview();
+}
+
+function renderFillerSection(sec){
+ const mark=sec.attributes?.mark??'';
+ const name=sec.content??'';
+ let body='';
+ for(const child of sec.children??[]){
+ if(child.type==='VALUE'){
+ const type=child.data_type??'string';
+ const id=child.id;
+ const val=FV[id]??'';
+ body+=`
+ ${esc(type)}
+ ${type==='string'
+ ?``
+ :``}
+
`;
+ } else if(child.type==='SECTION'){
+ body+=renderFillerSection(child);
+ }
+ }
+ return `
+
+ ${esc(mark||'·')}
+ ${esc(name||'이름 없음')}
+
+ ${body?`
${body}
`:''}
+
`;
+}
+
+function updateFillerPreview(){
+ const el=document.getElementById('fillerOut'); if(!FT) return;
+ if(fillerTab==='text'){
+ const t=fillerToText();
+ el.className=t.trim()?'preview-text':'preview-text dim';
+ el.textContent=t.trim()||'내용을 채우면 미리보기가 표시됩니다';
+ } else {
+ el.className='preview-text';
+ el.innerHTML=highlight(fillerToJSON());
+ }
+}
+
+function setFillerTab(t){
+ fillerTab=t;
+ document.querySelectorAll('.ftab').forEach(el=>el.classList.remove('active'));
+ document.getElementById('ftab-'+t).classList.add('active');
+ updateFillerPreview();
+}
+
+function copyFillerOutput(){
+ const txt=fillerTab==='text'?fillerToText():fillerToJSON();
+ navigator.clipboard.writeText(txt).then(()=>{
+ const btn=document.getElementById('fCopyBtn');
+ btn.textContent='✅ 복사됨'; btn.classList.add('ok');
+ setTimeout(()=>{ btn.innerHTML='📋 복사'; btn.classList.remove('ok'); },2000);
+ });
+}
+
+function fillerToText(){
+ if(!FT) return '';
+ function rSec(sec){
+ const mark=sec.attributes?.mark??'';
+ const content=sec.content??'';
+ const prefix=sec.prefix??'';
+ const suffix=sec.suffix??'';
+ const nl=sec.nameNewline!==false?'\n':'';
+ let t=prefix+mark+content;
+ let first=true;
+ for(const child of sec.children??[]){
+ if(child.type==='SECTION'){
+ t+=rSec(child); first=false;
+ } else if(child.type==='VALUE'){
+ const val=FV[child.id]??'';
+ if(val.trim()){ t+=(first?nl:'\n')+val; first=false; }
+ }
+ }
+ t+=suffix;
+ return t;
+ }
+ const title=FT.attributes?.title??'';
+ const titleMark=FT.attributes?.mark??'';
+ let out=title?`${titleMark}${title}\n`:'';
+ for(const child of FT.children??[]) if(child.type==='SECTION') out+=rSec(child);
+ return out.replace(/\n{3,}/g,'\n\n').trimEnd()+'\n';
+}
+
+function fillerToJSON(){
+ if(!FT) return '{}';
+ function fill(node){
+ const r={...node};
+ r.children=(node.children??[]).map(child=>{
+ if(child.type==='VALUE') return {...child, value:FV[child.id]??child.value??null};
+ if(child.type==='SECTION') return fill(child);
+ return child;
+ });
+ return r;
+ }
+ return JSON.stringify(fill(FT),null,2);
+}
+
// ════════════════════════════════════════════════════════
// 탭 전환
// ════════════════════════════════════════════════════════
@@ -1063,7 +1287,8 @@ function switchTab(name){
load();
refresh();
document.getElementById('importModal').addEventListener('click',e=>{ if(e.target===e.currentTarget) closeImport(); });
-document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeImport(); });
+document.getElementById('fillerImportModal').addEventListener('click',e=>{ if(e.target===e.currentTarget) closeFillerImport(); });
+document.addEventListener('keydown',e=>{ if(e.key==='Escape'){ closeImport(); closeFillerImport(); } });