{"id":22,"date":"2026-04-22T11:18:23","date_gmt":"2026-04-22T11:18:23","guid":{"rendered":"https:\/\/twainstudio.dev\/?page_id=22"},"modified":"2026-04-22T11:18:23","modified_gmt":"2026-04-22T11:18:23","slug":"the-forge-cover-creations","status":"publish","type":"page","link":"https:\/\/twainstudio.dev\/?page_id=22","title":{"rendered":"The Forge Cover Creations"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>T.W.A.I.N. Forge \u2014 Rebellion Command<\/title>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Cinzel:wght@400;600;700;900&#038;family=Oswald:wght@300;400;500;600;700&#038;family=Crimson+Text:ital,wght@0,400;0,600;1,400&#038;display=swap\" rel=\"stylesheet\">\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jszip\/3.10.1\/jszip.min.js\"><\/script>\n<script src=\"https:\/\/docspace-to7moo.onlyoffice.com\/static\/scripts\/sdk\/1.0.0\/api.js\"><\/script>\n<style>\n*{box-sizing:border-box;margin:0;padding:0;}\n:root{\n  --navy:#060d18;--navy2:#0a1628;--navy3:#0d1e35;--navy4:#080f1e;\n  --gold:#c9a84c;--gold2:#e8c97a;--gold-dim:rgba(201,168,76,0.1);\n  --orange:#FF4500;--orange-dim:rgba(255,69,0,0.1);--orange-glow:rgba(255,69,0,0.22);\n  --cream:#f0e6c8;--slate:#4a5d7a;--slate2:#8a9ab5;\n  --green:#1D9E75;--green-dark:#0F6E56;--green-dim:rgba(29,158,117,0.1);\n  --purple:#7c3aed;--purple-dim:rgba(124,58,237,0.12);\n  --paper:#f5f0e8;--paper2:#ede8da;--ink:#18120a;--ink2:#2c2318;\n}\nhtml,body{height:100%;margin:0;padding:0;}\nbody{background:var(--navy);color:var(--cream);font-family:'Crimson Text',serif;display:flex;flex-direction:column;overflow:hidden;}\n::-webkit-scrollbar{width:4px;height:4px;}\n::-webkit-scrollbar-thumb{background:rgba(201,168,76,0.25);border-radius:2px;}\n\n\/* TOP BAR *\/\n.topbar{flex-shrink:0;height:50px;background:var(--navy4);border-bottom:1px solid rgba(201,168,76,0.3);display:flex;align-items:center;justify-content:space-between;padding:0 16px;position:relative;z-index:100;}\n.topbar::after{content:'';position:absolute;bottom:-1px;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--orange) 30%,var(--gold) 50%,var(--orange) 70%,transparent);}\n.tb-brand{font-family:'Cinzel',serif;font-weight:900;font-size:13px;letter-spacing:.12em;color:var(--cream);}\n.tb-brand span{color:var(--gold);}\n.tb-brand em{color:var(--orange);font-style:normal;}\n.tb-phase{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.25em;text-transform:uppercase;color:var(--orange);background:var(--orange-dim);border:1px solid rgba(255,69,0,0.3);padding:3px 10px;border-radius:20px;}\n.tb-center{display:flex;align-items:center;gap:14px;}\n.tb-stat{font-family:'Oswald',sans-serif;font-size:10px;letter-spacing:.08em;color:var(--slate2);}\n.tb-stat span{color:var(--gold);font-weight:600;}\n.tb-right{display:flex;align-items:center;gap:6px;}\n.tb-btn{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.1em;text-transform:uppercase;background:transparent;border:1px solid rgba(201,168,76,0.2);color:var(--slate2);padding:5px 11px;border-radius:2px;cursor:pointer;transition:all .15s;white-space:nowrap;}\n.tb-btn:hover{color:var(--cream);border-color:rgba(201,168,76,0.5);}\n.tb-btn.gold{color:var(--gold);background:var(--gold-dim);border-color:rgba(201,168,76,0.35);}\n.tb-btn.gold:hover{background:rgba(201,168,76,0.18);}\n.tb-btn.orange{color:var(--orange);border-color:rgba(255,69,0,0.3);}\n.tb-btn.orange:hover{background:var(--orange-dim);}\n.tb-btn.green{color:var(--green);border-color:rgba(29,158,117,0.3);background:var(--green-dim);}\n.tb-btn.purple{color:#a78bfa;border-color:rgba(124,58,237,0.35);background:var(--purple-dim);}\n\n\/* MODULE NAV *\/\n.module-nav{flex-shrink:0;height:32px;background:var(--navy4);border-bottom:1px solid rgba(201,168,76,0.1);display:flex;align-items:center;padding:0 14px;gap:2px;}\n.mod-link{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.1em;text-transform:uppercase;padding:4px 10px;border-radius:2px;color:var(--slate);border:1px solid transparent;transition:all .15s;text-decoration:none;display:flex;align-items:center;gap:4px;cursor:pointer;}\n.mod-link:hover{color:var(--cream);}\n.mod-link.active{color:var(--gold);border-color:rgba(201,168,76,0.2);background:var(--gold-dim);}\n.mod-num{color:var(--orange);font-size:8px;font-weight:700;}\n.mod-arrow{color:rgba(201,168,76,0.2);margin:0 2px;}\n.mod-spacer{flex:1;}\n\n\/* MAIN LAYOUT *\/\n.forge-layout{flex:1;display:flex;min-height:0;overflow:hidden;}\n\n\/* LEFT PANEL *\/\n.ctrl-panel{width:240px;min-width:240px;background:var(--navy3);display:flex;flex-direction:column;border-right:1px solid rgba(201,168,76,0.08);overflow:hidden;}\n.ctrl-body{flex:1;overflow-y:auto;padding:12px;}\n.ctrl-section{margin-bottom:18px;}\n.ctrl-sec-title{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.2em;text-transform:uppercase;color:var(--orange);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid rgba(255,69,0,0.12);}\n.ctrl-field{margin-bottom:8px;}\n.ctrl-lbl{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.12em;text-transform:uppercase;color:var(--slate2);margin-bottom:3px;display:block;}\n.ctrl-inp{width:100%;background:rgba(255,255,255,0.03);border:1px solid rgba(201,168,76,0.15);border-radius:2px;color:var(--cream);font-family:'Oswald',sans-serif;font-size:11px;padding:6px 8px;outline:none;}\n.ctrl-inp:focus{border-color:rgba(201,168,76,0.4);}\n.ctrl-inp::placeholder{color:rgba(74,93,122,0.4);font-style:italic;}\n.ctrl-select{width:100%;background:rgba(255,255,255,0.03);border:1px solid rgba(201,168,76,0.15);border-radius:2px;color:var(--cream);font-family:'Oswald',sans-serif;font-size:11px;padding:6px 8px;outline:none;cursor:pointer;}\n.ctrl-textarea{width:100%;background:rgba(255,255,255,0.03);border:1px solid rgba(201,168,76,0.15);border-radius:2px;color:var(--cream);font-family:'Crimson Text',serif;font-size:12px;padding:6px 8px;outline:none;resize:none;min-height:52px;line-height:1.6;}\n.ctrl-textarea::placeholder{color:rgba(74,93,122,0.4);font-style:italic;}\n.ctrl-btn{width:100%;padding:8px;border-radius:2px;font-family:'Oswald',sans-serif;font-size:10px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;cursor:pointer;border:none;transition:all .15s;margin-bottom:5px;}\n.ctrl-btn.primary{background:var(--orange);color:#fff;box-shadow:0 3px 12px var(--orange-glow);}\n.ctrl-btn.primary:hover{background:#ff5a1a;}\n.ctrl-btn.ai-build{background:linear-gradient(135deg,var(--purple),#5b21b6);color:#fff;box-shadow:0 3px 14px rgba(124,58,237,0.35);}\n.ctrl-btn.ai-build:hover{background:linear-gradient(135deg,#8b5cf6,var(--purple));}\n.ctrl-btn.secondary{background:transparent;color:var(--gold);border:1px solid rgba(201,168,76,0.3);}\n.ctrl-btn.secondary:hover{background:var(--gold-dim);}\n.ctrl-btn.ghost{background:transparent;color:var(--slate2);border:1px solid rgba(74,93,122,0.25);}\n.ctrl-btn.ghost:hover{color:var(--cream);}\n.ctrl-btn.green{background:var(--green-dark);color:#b5e8d8;}\n.ctrl-btn.green:hover{background:var(--green);}\n.ctrl-btn:disabled{opacity:.4;cursor:not-allowed;}\n.status-bar{flex-shrink:0;padding:8px 12px;border-top:1px solid rgba(201,168,76,0.08);}\n.status-msg{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:var(--slate);line-height:1.5;}\n.status-msg.ok{color:var(--green);}\n.status-msg.err{color:var(--orange);}\n.status-msg.info{color:var(--gold);}\n.status-msg.ai{color:#a78bfa;}\n\n\/* Chapter list *\/\n.ch-list{display:flex;flex-direction:column;gap:3px;max-height:160px;overflow-y:auto;}\n.ch-item{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.04em;color:var(--slate2);padding:4px 8px;border-radius:2px;cursor:pointer;border:1px solid transparent;display:flex;justify-content:space-between;align-items:center;}\n.ch-item:hover{background:rgba(201,168,76,0.06);border-color:rgba(201,168,76,0.15);color:var(--cream);}\n.ch-item.reviewed{border-color:rgba(29,158,117,0.25);}\n.ch-item .ch-wc{font-size:7px;color:var(--slate);opacity:.7;}\n\n\/* KEY STRIP *\/\n.key-strip{padding:10px 12px;border-bottom:1px solid rgba(201,168,76,0.08);background:rgba(6,13,24,0.5);flex-shrink:0;}\n.key-lbl{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.12em;text-transform:uppercase;color:rgba(201,168,76,0.4);margin-bottom:3px;}\n.key-inp{width:100%;background:rgba(255,255,255,0.03);border:1px solid rgba(201,168,76,0.15);border-radius:2px;color:var(--cream);font-family:'Oswald',sans-serif;font-size:10px;padding:5px 8px;outline:none;}\n.key-inp:focus{border-color:rgba(201,168,76,0.4);}\n.key-inp::placeholder{color:rgba(74,93,122,0.4);}\n\n\/* CENTER PREVIEW *\/\n.preview-pane{flex:1;background:var(--navy);display:flex;flex-direction:column;min-width:0;overflow:hidden;}\n.preview-tabs{flex-shrink:0;height:34px;background:var(--navy2);border-bottom:1px solid var(--gold-dim);display:flex;align-items:center;padding:0 12px;gap:2px;}\n.ptab{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.1em;text-transform:uppercase;padding:4px 10px;border-radius:2px;color:var(--slate);border:1px solid transparent;cursor:pointer;background:transparent;transition:all .15s;}\n.ptab:hover{color:var(--cream);}\n.ptab.active{color:var(--gold);border-color:rgba(201,168,76,0.2);background:var(--gold-dim);}\n.ptab.purple-ptab{color:#a78bfa;}\n.ptab.purple-ptab.active{border-color:rgba(124,58,237,0.3);background:var(--purple-dim);}\n.preview-content{flex:1;min-height:0;overflow:hidden;position:relative;}\n#onlyoffice-editor{width:100%;height:100%;border:none;}\n#onlyoffice-editor iframe{width:100%!important;height:100%!important;border:none;}\n#panel-raw{display:none;height:100%;}\n#ms-raw{width:100%;height:100%;background:rgba(255,255,255,0.02);border:none;color:var(--cream);font-family:'Crimson Text',serif;font-size:13px;padding:20px;resize:none;outline:none;line-height:1.7;}\n#panel-build{display:none;height:100%;overflow-y:auto;}\n#panel-review{display:none;height:100%;overflow-y:auto;}\n\n\/* AI BUILD PANEL *\/\n.ai-build-panel{padding:16px;display:flex;flex-direction:column;gap:10px;background:var(--navy2);}\n.ai-step-card{background:var(--navy3);border:1px solid var(--gold-dim);border-radius:3px;padding:12px;position:relative;}\n.ai-step-card.running{border-color:rgba(167,139,250,0.5);background:var(--purple-dim);}\n.ai-step-card.done{border-color:rgba(29,158,117,0.4);background:rgba(29,158,117,0.05);}\n.ai-step-card.failed{border-color:rgba(255,69,0,0.4);}\n.step-header{display:flex;align-items:center;gap:8px;margin-bottom:6px;}\n.step-num{width:20px;height:20px;border-radius:50%;background:var(--gold-dim);border:1px solid rgba(201,168,76,0.3);display:flex;align-items:center;justify-content:center;font-family:'Oswald',sans-serif;font-size:9px;font-weight:700;color:var(--gold);flex-shrink:0;}\n.step-title{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--gold);flex:1;}\n.step-status{font-family:'Oswald',sans-serif;font-size:11px;color:var(--slate2);}\n.step-output{font-family:'Crimson Text',serif;font-size:12px;color:var(--cream);line-height:1.6;min-height:22px;max-height:80px;overflow:hidden;margin-bottom:8px;font-style:italic;}\n.build-progress{display:none;margin-bottom:14px;padding:12px;background:var(--navy3);border:1px solid var(--purple-dim);border-radius:3px;}\n.build-track{height:4px;background:rgba(124,58,237,0.15);border-radius:2px;overflow:hidden;}\n.build-fill{height:100%;background:linear-gradient(90deg,var(--purple),#8b5cf6);border-radius:2px;width:0%;transition:width .5s ease;}\n.build-lbl{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.08em;text-transform:uppercase;color:#a78bfa;margin-top:4px;}\n.build-all-btn{width:100%;padding:10px;border-radius:2px;font-family:'Oswald',sans-serif;font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;cursor:pointer;border:none;background:linear-gradient(135deg,var(--purple),#5b21b6);color:#fff;box-shadow:0 3px 14px rgba(124,58,237,0.35);margin-bottom:14px;}\n.build-all-btn:hover{background:linear-gradient(135deg,#8b5cf6,var(--purple));}\n.build-all-btn:disabled{opacity:.4;cursor:not-allowed;}\n\n\/* REVIEW PANEL *\/\n.review-panel{padding:16px;background:var(--navy2);}\n.review-header{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:var(--gold);margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--gold-dim);}\n.review-start-btn{width:100%;padding:9px;border-radius:2px;font-family:'Oswald',sans-serif;font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;cursor:pointer;border:none;background:linear-gradient(135deg,var(--green-dark),var(--green));color:#fff;box-shadow:0 3px 12px rgba(29,158,117,0.35);margin-bottom:14px;}\n.review-start-btn:hover{background:linear-gradient(135deg,var(--green),#22c694);}\n.review-start-btn:disabled{opacity:.4;cursor:not-allowed;}\n.review-progress{display:none;margin-bottom:12px;}\n.review-track{height:4px;background:rgba(29,158,117,0.15);border-radius:2px;overflow:hidden;}\n.review-fill{height:100%;background:linear-gradient(90deg,var(--green-dark),var(--green));border-radius:2px;width:0%;transition:width .5s ease;}\n.review-lbl{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.08em;text-transform:uppercase;color:var(--green);margin-top:4px;}\n.ch-review-card{background:var(--navy3);border:1px solid var(--gold-dim);border-radius:3px;padding:12px;margin-bottom:10px;}\n.ch-review-card.reviewing{border-color:rgba(29,158,117,0.4);background:rgba(29,158,117,0.04);}\n.ch-review-header{display:flex;align-items:center;gap:8px;margin-bottom:8px;}\n.ch-review-num{font-family:'Oswald',sans-serif;font-size:8px;font-weight:700;color:var(--green);background:var(--green-dim);border:1px solid rgba(29,158,117,0.3);padding:2px 7px;border-radius:20px;letter-spacing:.06em;}\n.ch-review-title{font-family:'Cinzel',serif;font-size:11px;font-weight:700;color:var(--cream);flex:1;}\n.ch-review-wc{font-family:'Oswald',sans-serif;font-size:8px;color:var(--slate);letter-spacing:.04em;}\n.ch-review-body{font-family:'Oswald',sans-serif;font-size:9.5px;line-height:1.7;color:var(--slate2);}\n.ch-review-placeholder{color:rgba(138,154,181,0.5);font-style:italic;}\n.rating-row{display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap;}\n.rating-badge{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.06em;text-transform:uppercase;padding:2px 8px;border-radius:20px;border:1px solid;}\n.rating-badge.good{color:var(--green);border-color:rgba(29,158,117,0.3);background:var(--green-dim);}\n.rating-badge.warn{color:var(--orange);border-color:rgba(255,69,0,0.3);background:var(--orange-dim);}\n.rating-badge.neutral{color:var(--slate2);border-color:rgba(138,154,181,0.3);}\n.suggestion{display:flex;gap:6px;margin-bottom:6px;padding:6px 8px;background:rgba(201,168,76,0.06);border-left:3px solid var(--gold);border-radius:1px;}\n.suggestion-icon{flex-shrink:0;}\n\n\/* RIGHT PANEL \u2014 AA BRAIN *\/\n.aa-panel{width:280px;min-width:280px;background:var(--navy3);border-left:1px solid rgba(201,168,76,0.08);display:flex;flex-direction:column;overflow:hidden;}\n.aa-header{flex-shrink:0;padding:10px 12px;border-bottom:1px solid rgba(201,168,76,0.08);display:flex;align-items:center;gap:8px;}\n.eng-light{width:7px;height:7px;border-radius:50%;background:#1a2a1a;transition:all .3s;flex-shrink:0;}\n.eng-light.on{background:var(--green);box-shadow:0 0 8px var(--green);}\n.eng-light.busy{background:#a78bfa;box-shadow:0 0 8px #a78bfa;animation:epulse .7s ease-in-out infinite;}\n.eng-light.error{background:#ff3333;box-shadow:0 0 6px #ff3333;}\n@keyframes epulse{0%,100%{opacity:.5;}50%{opacity:1;}}\n@keyframes tp{0%,100%{opacity:.2;}50%{opacity:.8;}}\n.eng-name{font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.1em;color:var(--slate2);text-transform:uppercase;}\n.eng-status{font-family:'Oswald',sans-serif;font-size:8px;color:var(--slate);flex:1;text-align:right;}\n.brain-model-row{flex-shrink:0;padding:6px 12px;border-bottom:1px solid rgba(201,168,76,0.08);display:flex;align-items:center;gap:6px;}\n.brain-model-lbl{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.12em;text-transform:uppercase;color:var(--slate);}\n.brain-model-sel{flex:1;background:rgba(255,255,255,0.03);border:1px solid rgba(201,168,76,0.15);border-radius:2px;color:var(--cream);font-family:'Oswald',sans-serif;font-size:10px;padding:4px 7px;outline:none;cursor:pointer;}\n.quick-actions{flex-shrink:0;padding:6px 10px;border-bottom:1px solid rgba(201,168,76,0.08);display:flex;gap:4px;flex-wrap:wrap;}\n.qa-btn{font-family:'Oswald',sans-serif;font-size:7.5px;letter-spacing:.06em;text-transform:uppercase;padding:3px 7px;border-radius:2px;cursor:pointer;border:1px solid rgba(201,168,76,0.2);color:var(--slate2);background:transparent;transition:all .1s;}\n.qa-btn:hover{color:var(--cream);border-color:rgba(201,168,76,0.4);}\n.chat{flex:1;min-height:0;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:7px;}\n.msg-aa{background:rgba(255,255,255,0.03);border-left:3px solid var(--gold);padding:9px 11px;font-family:'Oswald',sans-serif;font-size:10px;line-height:1.65;color:var(--cream);}\n.msg-aa.purple-msg{border-left-color:#7c3aed;}\n.msg-user{background:var(--orange-dim);border-right:3px solid var(--orange);padding:8px 11px;font-family:'Oswald',sans-serif;font-size:10px;color:var(--cream);text-align:right;font-style:italic;}\n.msg-think{display:flex;gap:3px;padding:8px 11px;border-left:3px solid rgba(201,168,76,0.2);}\n.msg-think span{width:5px;height:5px;border-radius:50%;background:var(--green);opacity:.3;animation:tp .9s ease-in-out infinite;}\n.msg-think span:nth-child(2){animation-delay:.2s;}\n.msg-think span:nth-child(3){animation-delay:.4s;}\n.chat-input-row{flex-shrink:0;padding:8px 10px;border-top:1px solid rgba(201,168,76,0.1);display:flex;gap:5px;background:rgba(6,13,24,0.5);}\n.chat-inp{flex:1;background:rgba(255,255,255,0.03);border:1px solid rgba(201,168,76,0.15);border-radius:2px;color:var(--cream);font-family:'Crimson Text',serif;font-size:12px;padding:7px 9px;outline:none;resize:none;min-height:36px;max-height:80px;line-height:1.5;}\n.chat-inp:focus{border-color:rgba(201,168,76,0.4);}\n.chat-inp::placeholder{color:rgba(74,93,122,0.4);font-style:italic;}\n.chat-send{background:var(--orange);border:none;border-radius:2px;padding:7px 12px;font-family:'Oswald',sans-serif;font-size:11px;font-weight:700;color:#fff;cursor:pointer;align-self:flex-end;}\n.chat-send:hover{background:#ff5a1a;}\n\n\/* Progress bar (global) *\/\n#progress-wrap{display:none;padding:6px 12px;background:var(--navy4);border-bottom:1px solid rgba(201,168,76,0.08);}\n#progress-fill{height:3px;background:linear-gradient(90deg,var(--orange),var(--gold));border-radius:2px;width:0%;transition:width .4s ease;}\n#progress-lbl{font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.08em;text-transform:uppercase;color:var(--gold);margin-top:3px;}\n\n\/* Dropzone *\/\n#forge-dropzone.drag-over{border-color:rgba(201,168,76,0.6)!important;background:rgba(201,168,76,0.05)!important;}\n<\/style>\n<\/head>\n<body>\n\n<!-- TOP BAR -->\n<div class=\"topbar\">\n  <div class=\"tb-brand\"><span>T.W.A.I.N.<\/span> <em>FORGE<\/em><\/div>\n  <div class=\"tb-phase\">Manuscript Studio<\/div>\n  <div class=\"tb-center\">\n    <div class=\"tb-stat\">Words <span id=\"stat-words\">0<\/span><\/div>\n    <div class=\"tb-stat\">Chapters <span id=\"stat-chapters\">0<\/span><\/div>\n    <div class=\"tb-stat\">Project <span id=\"stat-project\">None<\/span><\/div>\n  <\/div>\n  <div class=\"tb-right\">\n    <button class=\"tb-btn\" onclick=\"loadFromGenesis()\">Load from Genesis<\/button>\n    <button class=\"tb-btn purple\" onclick=\"buildAllFrontMatter()\">\u26a1 AI Build All<\/button>\n    <button class=\"tb-btn gold\" onclick=\"exportHTML()\">Export HTML<\/button>\n    <button class=\"tb-btn green\" onclick=\"sendToPulse()\">Send to Pulse<\/button>\n    <button class=\"tb-btn orange\" onclick=\"saveForge()\">Save<\/button>\n  <\/div>\n<\/div>\n\n<!-- MODULE NAV -->\n<div class=\"module-nav\">\n  <a class=\"mod-link\" href=\"twain-genesis.html\"><span class=\"mod-num\">01<\/span> Genesis<\/a>\n  <span class=\"mod-arrow\">\u203a<\/span>\n  <a class=\"mod-link active\"><span class=\"mod-num\">02<\/span> Forge<\/a>\n  <span class=\"mod-arrow\">\u203a<\/span>\n  <a class=\"mod-link\" href=\"twain-pulse.html\"><span class=\"mod-num\">03<\/span> Pulse<\/a>\n  <span class=\"mod-arrow\">\u203a<\/span>\n  <a class=\"mod-link\" href=\"twain-cover-studio.html\"><span class=\"mod-num\">04<\/span> Cover Studio<\/a>\n  <span class=\"mod-arrow\">\u203a<\/span>\n  <a class=\"mod-link\" href=\"twain-command-center.html\"><span class=\"mod-num\">05<\/span> Command<\/a>\n  <span class=\"mod-arrow\">\u203a<\/span>\n  <a class=\"mod-link\" href=\"twain-deploy.html\"><span class=\"mod-num\">06<\/span> Deploy<\/a>\n  <div class=\"mod-spacer\"><\/div>\n  <span style=\"font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.12em;text-transform:uppercase;color:var(--slate);\">Rebellion Command Ecosystem<\/span>\n<\/div>\n\n<!-- GLOBAL PROGRESS -->\n<div id=\"progress-wrap\">\n  <div id=\"progress-fill\"><\/div>\n  <div id=\"progress-lbl\"><\/div>\n<\/div>\n\n<!-- FORGE LAYOUT -->\n<div class=\"forge-layout\">\n\n  <!-- LEFT CONTROLS -->\n  <div class=\"ctrl-panel\">\n    <div class=\"key-strip\">\n      <div class=\"key-lbl\">Groq API Key<\/div>\n      <input type=\"password\" class=\"key-inp\" id=\"groq-key\" placeholder=\"gsk_...\" oninput=\"saveKeys()\">\n      <div style=\"font-family:'Oswald',sans-serif;font-size:7px;color:rgba(201,168,76,0.4);margin-top:3px;letter-spacing:.06em;\">\n        Powers: AI Build All \u00b7 Chapter Review \u00b7 A.A. Brain\n      <\/div>\n      <button onclick=\"testGroqConnection()\" style=\"margin-top:5px;width:100%;padding:4px;background:transparent;border:1px solid rgba(29,158,117,0.3);color:var(--green);font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;border-radius:2px;\">Test Connection<\/button>\n    <\/div>\n\n    <div class=\"ctrl-body\">\n\n      <div class=\"ctrl-section\">\n        <div class=\"ctrl-sec-title\">Book Metadata<\/div>\n        <div class=\"ctrl-field\"><label class=\"ctrl-lbl\">Title<\/label>\n          <input class=\"ctrl-inp\" id=\"book-title\" placeholder=\"Book Title\" oninput=\"updateStats()\"><\/div>\n        <div class=\"ctrl-field\"><label class=\"ctrl-lbl\">Author<\/label>\n          <input class=\"ctrl-inp\" id=\"book-author\" placeholder=\"Author Name\"><\/div>\n        <div class=\"ctrl-field\"><label class=\"ctrl-lbl\">ISBN (optional)<\/label>\n          <input class=\"ctrl-inp\" id=\"book-isbn\" placeholder=\"Pending\"><\/div>\n        <div class=\"ctrl-field\"><label class=\"ctrl-lbl\">Dedication seed<\/label>\n          <textarea class=\"ctrl-textarea\" id=\"book-dedication\" placeholder=\"To... (AI will craft from this)\"><\/textarea><\/div>\n        <div class=\"ctrl-field\"><label class=\"ctrl-lbl\">Genre<\/label>\n          <select class=\"ctrl-select\" id=\"book-genre\">\n            <option value=\"\">Select genre&#8230;<\/option>\n            <option value=\"Non-Fiction \/ Self-Help\">Non-Fiction \/ Self-Help<\/option>\n            <option value=\"Business \/ Entrepreneurship\">Business \/ Entrepreneurship<\/option>\n            <option value=\"Memoir \/ Biography\">Memoir \/ Biography<\/option>\n            <option value=\"Thriller \/ Suspense\">Thriller \/ Suspense<\/option>\n            <option value=\"Romance\">Romance<\/option>\n            <option value=\"Fantasy \/ Sci-Fi\">Fantasy \/ Sci-Fi<\/option>\n            <option value=\"Horror\">Horror<\/option>\n            <option value=\"Literary Fiction\">Literary Fiction<\/option>\n          <\/select><\/div>\n      <\/div>\n\n      <div class=\"ctrl-section\">\n        <div class=\"ctrl-sec-title\">Upload Manuscript<\/div>\n        <div id=\"forge-dropzone\" onclick=\"document.getElementById('ms-upload').click()\" style=\"border:2px dashed rgba(201,168,76,0.25);border-radius:3px;padding:14px 10px;text-align:center;cursor:pointer;transition:all .2s;margin-bottom:6px;background:rgba(255,255,255,0.02);\">\n          <div style=\"font-size:22px;margin-bottom:5px;opacity:.5;\">\ud83d\udcc4<\/div>\n          <div style=\"font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--slate2);margin-bottom:3px;\">Drop or click to browse<\/div>\n          <div style=\"font-family:'Oswald',sans-serif;font-size:7px;letter-spacing:.08em;text-transform:uppercase;color:var(--slate);\">.txt \u00b7 .md \u00b7 .docx<\/div>\n        <\/div>\n        <input type=\"file\" id=\"ms-upload\" accept=\".txt,.md,.doc,.docx\" style=\"display:none\" onchange=\"ingestFile(this)\">\n        <div id=\"upload-progress\" style=\"display:none;margin-bottom:6px;\">\n          <div style=\"height:3px;background:rgba(255,255,255,0.05);border-radius:2px;overflow:hidden;\">\n            <div id=\"upload-bar\" style=\"height:100%;background:var(--orange);width:0%;transition:width .3s;\"><\/div>\n          <\/div>\n          <div id=\"upload-label\" style=\"font-family:'Oswald',sans-serif;font-size:8px;letter-spacing:.08em;color:var(--orange);margin-top:3px;\">Reading&#8230;<\/div>\n        <\/div>\n        <button class=\"ctrl-btn ghost\" onclick=\"loadFromGenesis()\">Pull from Genesis<\/button>\n      <\/div>\n\n      <div class=\"ctrl-section\">\n        <div class=\"ctrl-sec-title\">Forge Actions<\/div>\n        <button class=\"ctrl-btn primary\" onclick=\"runForge()\">\u2460 Forge Manuscript<\/button>\n        <button class=\"ctrl-btn ai-build\" id=\"btn-build-all\" onclick=\"buildAllFrontMatter()\" disabled>\u26a1 \u2461 AI Build All Front Matter<\/button>\n        <button class=\"ctrl-btn green\" id=\"btn-review\" onclick=\"startFullReview()\" disabled>\u2726 \u2462 Full Chapter Review<\/button>\n        <button class=\"ctrl-btn ghost\" onclick=\"clearForge()\">Clear All<\/button>\n      <\/div>\n\n      <div class=\"ctrl-section\">\n        <div class=\"ctrl-sec-title\">Chapter Map<\/div>\n        <div class=\"ch-list\" id=\"ch-list\">\n          <div style=\"font-family:'Oswald',sans-serif;font-size:9px;color:var(--slate);letter-spacing:.06em;\">Forge manuscript to see chapters<\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"ctrl-section\">\n        <div class=\"ctrl-sec-title\">Export<\/div>\n        <button class=\"ctrl-btn secondary\" onclick=\"exportHTML()\">Export HTML \u2192 PDF<\/button>\n        <button class=\"ctrl-btn ghost\" onclick=\"exportEpubBundle()\">Export ePub Bundle<\/button>\n      <\/div>\n\n    <\/div>\n    <div class=\"status-bar\">\n      <div class=\"status-msg\" id=\"status-msg\">Load a manuscript to begin forging.<\/div>\n    <\/div>\n  <\/div>\n\n  <!-- CENTER: ONLYOFFICE CANVAS + PANELS -->\n  <div class=\"preview-pane\">\n    <div class=\"preview-tabs\">\n      <button class=\"ptab active\" id=\"tab-preview\" onclick=\"showTab('preview')\">Canvas<\/button>\n      <button class=\"ptab\" id=\"tab-raw\" onclick=\"showTab('raw')\">Raw Text<\/button>\n      <button class=\"ptab purple-ptab\" id=\"tab-build\" onclick=\"showTab('build')\">AI Build<\/button>\n      <button class=\"ptab purple-ptab\" id=\"tab-review\" onclick=\"showTab('review')\">Chapter Review<\/button>\n    <\/div>\n    <div class=\"preview-content\">\n\n      <!-- CANVAS (OnlyOffice) -->\n      <div id=\"panel-preview\" style=\"height:100%;\">\n        <div id=\"onlyoffice-editor\" style=\"width:100%;height:100%;\"><\/div>\n      <\/div>\n\n      <!-- RAW TEXT -->\n      <div id=\"panel-raw\" style=\"display:none;height:100%;\">\n        <textarea id=\"ms-raw\" placeholder=\"Paste raw manuscript here, or upload a file.&#10;&#10;The Forge will detect chapters, generate front matter, and produce a print-ready document.\"><\/textarea>\n      <\/div>\n\n      <!-- AI BUILD PANEL -->\n      <div id=\"panel-build\" style=\"display:none;height:100%;overflow-y:auto;\">\n        <div class=\"ai-build-panel\">\n          <button class=\"build-all-btn\" id=\"build-all-btn\" onclick=\"buildAllFrontMatter()\">\u26a1 Run AI Build All \u2014 5 Steps<\/button>\n          <div class=\"build-progress\" id=\"build-progress\">\n            <div class=\"build-track\"><div class=\"build-fill\" id=\"build-fill\"><\/div><\/div>\n            <div class=\"build-lbl\" id=\"build-lbl\">Starting&#8230;<\/div>\n          <\/div>\n          <!-- Step cards -->\n          <div class=\"ai-step-card\" id=\"step-toc\">\n            <div class=\"step-header\">\n              <div class=\"step-num\">1<\/div>\n              <div class=\"step-title\">Table of Contents<\/div>\n              <div class=\"step-status\" id=\"step-toc-status\">\u25cb<\/div>\n            <\/div>\n            <div class=\"step-output\" id=\"out-toc\">Waiting&#8230;<\/div>\n            <button class=\"ctrl-btn ghost\" style=\"margin:0;font-size:8px;padding:5px;\" onclick=\"buildTOCWithAI()\">Run Step<\/button>\n          <\/div>\n          <div class=\"ai-step-card\" id=\"step-copy\">\n            <div class=\"step-header\">\n              <div class=\"step-num\">2<\/div>\n              <div class=\"step-title\">Copyright Page<\/div>\n              <div class=\"step-status\" id=\"step-copy-status\">\u25cb<\/div>\n            <\/div>\n            <div class=\"step-output\" id=\"out-copy\">Waiting&#8230;<\/div>\n            <button class=\"ctrl-btn ghost\" style=\"margin:0;font-size:8px;padding:5px;\" onclick=\"buildCopyrightWithAI()\">Run Step<\/button>\n          <\/div>\n          <div class=\"ai-step-card\" id=\"step-ded\">\n            <div class=\"step-header\">\n              <div class=\"step-num\">3<\/div>\n              <div class=\"step-title\">Dedication<\/div>\n              <div class=\"step-status\" id=\"step-ded-status\">\u25cb<\/div>\n            <\/div>\n            <div class=\"step-output\" id=\"out-ded\">Waiting&#8230;<\/div>\n            <button class=\"ctrl-btn ghost\" style=\"margin:0;font-size:8px;padding:5px;\" onclick=\"buildDedicationWithAI()\">Run Step<\/button>\n          <\/div>\n          <div class=\"ai-step-card\" id=\"step-fore\">\n            <div class=\"step-header\">\n              <div class=\"step-num\">4<\/div>\n              <div class=\"step-title\">Foreword<\/div>\n              <div class=\"step-status\" id=\"step-fore-status\">\u25cb<\/div>\n            <\/div>\n            <div class=\"step-output\" id=\"out-fore\">Waiting&#8230;<\/div>\n            <div style=\"margin-bottom:6px;\">\n              <label style=\"font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--slate2);display:block;margin-bottom:3px;\">Custom Foreword Text (optional \u2014 leave blank to use embedded foreword)<\/label>\n              <textarea id=\"book-foreword-custom\" style=\"width:100%;background:rgba(255,255,255,0.03);border:1px solid rgba(201,168,76,0.15);border-radius:2px;color:var(--cream);font-family:Crimson Text,serif;font-size:11px;padding:6px 8px;outline:none;resize:vertical;min-height:60px;line-height:1.6;\" placeholder=\"Paste custom foreword text here, or leave blank to use the default foreword...\"><\/textarea>\n            <\/div>\n            <button class=\"ctrl-btn ghost\" style=\"margin:0;font-size:8px;padding:5px;\" onclick=\"buildForewordWithAI()\">Run Step<\/button>\n          <\/div>\n          <div class=\"ai-step-card\" id=\"step-ch\">\n            <div class=\"step-header\">\n              <div class=\"step-num\">5<\/div>\n              <div class=\"step-title\">Chapter Titles<\/div>\n              <div class=\"step-status\" id=\"step-ch-status\">\u25cb<\/div>\n            <\/div>\n            <div class=\"step-output\" id=\"out-ch\">Waiting&#8230;<\/div>\n            <button class=\"ctrl-btn ghost\" style=\"margin:0;font-size:8px;padding:5px;\" onclick=\"buildChapterTitlesWithAI()\">Run Step<\/button>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <!-- REVIEW PANEL -->\n      <div id=\"panel-review\" style=\"display:none;height:100%;overflow-y:auto;\">\n        <div class=\"review-panel\">\n          <div class=\"review-header\">Full Chapter Review \u2014 Groq Analysis<\/div>\n          <button class=\"review-start-btn\" id=\"review-start-btn\" onclick=\"startFullReview()\">\u2726 Start Full Chapter Review<\/button>\n          <div class=\"review-progress\" id=\"review-progress\">\n            <div class=\"review-track\"><div class=\"review-fill\" id=\"review-fill\"><\/div><\/div>\n            <div class=\"review-lbl\" id=\"review-lbl\">Starting review&#8230;<\/div>\n          <\/div>\n          <div id=\"review-cards\">\n            <div style=\"text-align:center;padding:40px 20px;font-family:'Oswald',sans-serif;font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:rgba(138,154,181,0.3);\">Forge your manuscript first<\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n  <\/div>\n\n  <!-- RIGHT PANEL: A.A. BRAIN -->\n  <div class=\"aa-panel\">\n    <div class=\"aa-header\">\n      <div class=\"eng-light on\" id=\"eng-light\"><\/div>\n      <div class=\"eng-name\" id=\"eng-name\">A.A. Brain<\/div>\n      <div class=\"eng-status\" id=\"eng-status\">Ready<\/div>\n    <\/div>\n    <div class=\"brain-model-row\">\n      <div class=\"brain-model-lbl\">Model<\/div>\n      <select class=\"brain-model-sel\" id=\"brain-model\" onchange=\"updateModel()\">\n        <option value=\"openai\/gpt-oss-120b\" selected>openai\/gpt-oss-120b (default)<\/option>\n        <option value=\"llama-3.1-8b-instant\">llama-3.1-8b-instant<\/option>\n        <option value=\"meta-llama\/llama-4-scout-17b-16e-instruct\">meta-llama\/llama-4-scout-17b-16e-instruct(fast)<\/option>\n        <option value=\"qwen\/qwen3-32b\">qwen\/qwen3-32b<\/option>\n        <option value=\"openai\/gpt-oss-20b\">openai\/gpt-oss-20b<\/option>\n      <\/select>\n    <\/div>\n    <div class=\"quick-actions\">\n      <button class=\"qa-btn\" onclick=\"analyzeStructure()\">Structure<\/button>\n      <button class=\"qa-btn\" onclick=\"suggestTitle()\">Titles<\/button>\n      <button class=\"qa-btn\" onclick=\"checkPacing()\">Pacing<\/button>\n      <button class=\"qa-btn\" onclick=\"hookAnalysis()\">Hook<\/button>\n      <button class=\"qa-btn\" onclick=\"audienceCheck()\">Audience<\/button>\n      <button class=\"qa-btn\" onclick=\"genSummary()\">Back Cover<\/button>\n    <\/div>\n    <div class=\"chat\" id=\"chat\">\n      <div class=\"msg-aa\">T.W.A.I.N. Forge is ready. Load your manuscript, hit Forge, then ask me anything about your book \u2014 structure, pacing, titles, characters, commercial potential.<\/div>\n    <\/div>\n    <div class=\"chat-input-row\">\n      <textarea class=\"chat-inp\" id=\"chat-inp\" placeholder=\"Ask the A.A. Brain...\" rows=\"2\"\n        onkeydown=\"if(event.key==='Enter'&#038;&#038;!event.shiftKey){event.preventDefault();sendMsg();}\"><\/textarea>\n      <button class=\"chat-send\" onclick=\"sendMsg()\">\u25b6<\/button>\n    <\/div>\n  <\/div>\n\n<\/div>\n\n<script>\n\/\/ ================================================================\n\/\/ STATE\n\/\/ ================================================================\nvar F = {\n  rawText: '',\n  formattedHTML: '',\n  chapters: [],\n  title: '',\n  author: '',\n  activeModel: 'openai\/gpt-oss-20b',  \/\/ must match dropdown default\n  conversation: [],\n  bible: {},\n  buildRunning: false,\n  reviewRunning: false\n};\n\nvar docEditor = null;\nvar OO = { ready: false };\n\n\/\/ ================================================================\n\/\/ INIT\n\/\/ ================================================================\nwindow.addEventListener('load', function() {\n  var gk = localStorage.getItem('twain_groq_key');\n  if (gk) document.getElementById('groq-key').value = gk;\n  loadFromGenesis();\n  setLight('on', 'A.A. Brain');\n  setupForgeDropZone();\n  updateEngineStatus();\n  \/\/ Attempt OnlyOffice handshake with a blank placeholder doc\n  initiateHandshake(null, 'TW-FORGE-' + Date.now(), 'New Manuscript');\n});\n\n\/\/ ================================================================\n\/\/ ONLYOFFICE HANDSHAKE\n\/\/ ================================================================\nfunction initiateHandshake(fileUrl, fileId, docTitle) {\n  var container = document.getElementById('onlyoffice-editor');\n  if (!container) return;\n\n  \/\/ If no fileUrl, show the placeholder iframe immediately\n  if (!fileUrl) {\n    renderPlaceholderDocument('<div style=\"text-align:center;padding:80px 40px;font-family:Georgia,serif;color:#888;\"><h2 style=\"font-family:Georgia,serif;color:#aaa;margin-bottom:16px;\">T.W.A.I.N. Forge<\/h2><p>Upload or pull from Genesis \u2014 then hit Forge Manuscript<\/p><\/div>');\n    return;\n  }\n\n  var config = {\n    document: {\n      fileType: 'docx',\n      key: fileId || ('TW-FORGE-' + Date.now()),\n      title: docTitle || 'New Manuscript',\n      url: fileUrl\n    },\n    documentType: 'word',\n    editorConfig: {\n      mode: 'edit',\n      lang: 'en',\n      user: { id: 'jerald-wright', name: 'Jerald Wright' },\n      customization: {\n        uiTheme: 'theme-dark',\n        forcesave: true,\n        customer: {\n          name: 'Rebellion Command',\n          address: 'Ocala, Florida',\n          mail: 'support@rebellioncommand.com',\n          www: 'https:\/\/twainstudio.dev',\n          info: 'Author Strategist Platform'\n        }\n      }\n    },\n    height: '100%',\n    width: '100%'\n  };\n\n  try {\n    docEditor = new DocsAPI.DocEditor('onlyoffice-editor', config);\n    OO.ready = true;\n    setStatus('Canvas ready.', 'ok');\n  } catch(e) {\n    console.error('OnlyOffice init failed:', e);\n    setStatus('Canvas using preview mode.', 'info');\n  }\n}\n\n\/\/ ================================================================\n\/\/ CANVAS RENDERING (placeholder iframe fallback)\n\/\/ ================================================================\nfunction escapeAttr(str) {\n  return String(str)\n    .replace(\/&\/g, '&amp;')\n    .replace(\/\"\/g, '&quot;')\n    .replace(\/'\/g, '&#39;')\n    .replace(\/<\/g, '&lt;')\n    .replace(\/>\/g, '&gt;');\n}\n\nvar _canvasBlobUrl = null;\n\nfunction renderPlaceholderDocument(html) {\n  var host = document.getElementById('onlyoffice-editor');\n  if (!host) return;\n  var css = [\n    '@import url(\"https:\/\/fonts.googleapis.com\/css2?family=Cinzel:wght@700;900&family=Crimson+Text:ital,wght@0,400;0,600;1,400&display=swap\");',\n    'body{font-family:\"Crimson Text\",Georgia,serif;font-size:12pt;line-height:1.75;color:#18120a;background:#f5f0e8;max-width:680px;margin:0 auto;padding:48px 56px;}',\n    'h1{font-family:\"Cinzel\",Georgia,serif;font-size:20pt;font-weight:700;text-align:center;margin:0 0 32px 0;letter-spacing:.04em;}',\n    'h2{font-family:\"Cinzel\",Georgia,serif;font-size:14pt;font-weight:700;text-align:center;margin:0 0 20px 0;letter-spacing:.06em;}',\n    'p{margin:0 0 1em 1.5em;}',\n    'p:first-of-type{margin-left:0;}',\n    '.title-page{text-align:center;padding:80px 0 60px;border-bottom:2px solid #c9a84c;margin-bottom:60px;}',\n    '.title-page h1{font-size:26pt;margin-bottom:16px;}',\n    '.byline{font-family:\"Crimson Text\",serif;font-size:14pt;font-style:italic;color:#5a4a2a;}',\n    '.copyright-page{font-size:10pt;line-height:1.6;padding:40px 0;border-bottom:1px solid rgba(0,0,0,.1);margin-bottom:60px;color:#555;}',\n    '.dedication{text-align:center;padding:80px 0 60px;font-style:italic;font-size:14pt;border-bottom:1px solid rgba(0,0,0,.1);margin-bottom:60px;}',\n    '.toc-section{padding:0 0 48px;border-bottom:1px solid rgba(0,0,0,.1);margin-bottom:60px;}',\n    '.foreword{padding:0 0 48px;border-bottom:1px solid rgba(0,0,0,.1);margin-bottom:60px;}',\n    '.chapter{padding-top:60px;margin-bottom:60px;border-top:3px solid #c9a84c;}',\n    '.toc-entry{display:flex;justify-content:space-between;align-items:baseline;border-bottom:1px dotted rgba(0,0,0,.2);padding:6px 0;font-size:11pt;}',\n    '.toc-entry a{color:#2a1a08;text-decoration:none;}',\n    '.rebellion-brand{background:#f0ece4;border-left:3px solid #c9a84c;padding:10px 14px;margin:14px 0;font-size:10pt;}',\n    'a{color:inherit;text-decoration:none;}'\n  ].join('');\n\n  var docHTML = '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><style>' + css + '<\/style><\/head><body>' + html + '<\/body><\/html>';\n\n  \/\/ Primary: document.write directly into iframe \u2014 works in all environments including file:\/\/\n  var iframe = document.createElement('iframe');\n  iframe.style.cssText = 'width:100%;height:100%;border:none;background:#f5f0e8;';\n  host.innerHTML = '';\n  host.appendChild(iframe);\n  try {\n    iframe.contentDocument.open();\n    iframe.contentDocument.write(docHTML);\n    iframe.contentDocument.close();\n    return;\n  } catch(e) {}\n  \/\/ Fallback: Blob URL\n  if (_canvasBlobUrl) { try { URL.revokeObjectURL(_canvasBlobUrl); } catch(e2) {} }\n  try {\n    var blob = new Blob([docHTML], { type: 'text\/html' });\n    _canvasBlobUrl = URL.createObjectURL(blob);\n    iframe.src = _canvasBlobUrl;\n  } catch(e3) {\n    \/\/ Last resort: srcdoc (note: only safe because docHTML has no double-quotes in attributes)\n    iframe.srcdoc = docHTML;\n  }\n}\n\nfunction pushHTMLToCanvas(html) {\n  F.formattedHTML = html || '';\n  \/\/ Always use the placeholder iframe \u2014 OnlyOffice live sync requires a server-side document URL\n  \/\/ Once twainstudio.dev backend is set up, swap this for the docEditor.executeMethod path\n  renderPlaceholderDocument(F.formattedHTML);\n}\n\n\/\/ ================================================================\n\/\/ KEY MANAGEMENT\n\/\/ ================================================================\nfunction saveKeys() {\n  var key = document.getElementById('groq-key').value.trim();\n  if (key) localStorage.setItem('twain_groq_key', key);\n  else localStorage.removeItem('twain_groq_key');\n  updateEngineStatus();\n}\n\nfunction getGroqKey() {\n  return document.getElementById('groq-key').value.trim();\n}\n\nfunction getClaudioKey() { return getGroqKey(); }\n\nfunction updateEngineStatus() {\n  var key = getGroqKey();\n  if (key) {\n    setLight('on', 'Groq Ready');\n    document.getElementById('eng-status').textContent = 'Full power';\n  } else {\n    setLight('error', 'No Key');\n    document.getElementById('eng-status').textContent = 'Add Groq key';\n  }\n}\n\nfunction updateModel() {\n  F.activeModel = document.getElementById('brain-model').value;\n  F.conversation = [];\n}\n\n\/\/ ================================================================\n\/\/ GROQ API\n\/\/ ================================================================\nasync function callGroq(messages, maxTokens) {\n  var key = getGroqKey();\n  if (!key) throw new Error('No Groq key \u2014 paste your gsk_ key in the key panel.');\n  var res;\n  try {\n    res = await fetch('https:\/\/api.groq.com\/openai\/v1\/chat\/completions', {\n      method: 'POST',\n      headers: { 'Authorization': 'Bearer ' + key, 'Content-Type': 'application\/json' },\n      body: JSON.stringify({\n        model: F.activeModel,\n        max_tokens: maxTokens || 800,\n        temperature: 0.7,\n        messages: messages\n      })\n    });\n  } catch(netErr) {\n    throw new Error('Network error reaching Groq: ' + netErr.message + '. Check your internet connection.');\n  }\n  var data;\n  try { data = await res.json(); } catch(e) { throw new Error('Groq returned non-JSON response (status ' + res.status + ')'); }\n  if (!res.ok) {\n    var msg = (data.error && data.error.message) ? data.error.message : JSON.stringify(data);\n    throw new Error('Groq API error ' + res.status + ': ' + msg);\n  }\n  if (!data.choices || !data.choices[0]) throw new Error('Groq returned empty choices array.');\n  return data.choices[0].message.content;\n}\n\nasync function testGroqConnection() {\n  setStatus('Testing Groq connection...', 'info');\n  setLight('busy', 'Testing...');\n  try {\n    var result = await callAI(\n      'You are a test assistant.',\n      'Reply with exactly: TWAIN_FORGE_CONNECTED',\n      20\n    );\n    if (result && result.indexOf('TWAIN_FORGE_CONNECTED') !== -1) {\n      setStatus('Groq connected. Model: ' + F.activeModel, 'ok');\n      setLight('on', 'Groq Ready');\n      addMsg('\u2713 Groq connection confirmed. Model: <strong>' + F.activeModel + '<\/strong>. AI Build All and Chapter Review are ready.', false);\n    } else {\n      setStatus('Groq responded but result unexpected: ' + result.substring(0, 60), 'info');\n      setLight('on', 'Groq Ready');\n      addMsg('Groq responded. Connection working \u2014 model: ' + F.activeModel, false);\n    }\n  } catch(e) {\n    setStatus('Connection failed: ' + e.message, 'err');\n    setLight('error', 'Error');\n    addMsg('\u2717 Groq connection failed: ' + e.message + '<br><br>Check: 1) Key is correct (starts with gsk_) 2) Key has not expired 3) Model is available on your plan.', false);\n  }\n}\n\nasync function callAI(systemPrompt, userPrompt, maxTokens) {\n  return callGroq([\n    { role: 'system', content: systemPrompt },\n    { role: 'user', content: userPrompt }\n  ], maxTokens || 800);\n}\n\n\/\/ ================================================================\n\/\/ DROP ZONE\n\/\/ ================================================================\nfunction setupForgeDropZone() {\n  var dz = document.getElementById('forge-dropzone');\n  if (!dz) return;\n  dz.addEventListener('dragover', function(e) { e.preventDefault(); e.stopPropagation(); dz.classList.add('drag-over'); });\n  dz.addEventListener('dragleave', function(e) { e.stopPropagation(); dz.classList.remove('drag-over'); });\n  dz.addEventListener('drop', function(e) {\n    e.preventDefault(); e.stopPropagation(); dz.classList.remove('drag-over');\n    var f = e.dataTransfer.files[0]; if (f) processUploadedFile(f);\n  });\n  document.addEventListener('dragover', function(e) { e.preventDefault(); });\n  document.addEventListener('drop', function(e) { e.preventDefault(); });\n}\n\n\/\/ ================================================================\n\/\/ FILE INGESTION\n\/\/ ================================================================\nfunction loadFromGenesis() {\n  try {\n    var raw = localStorage.getItem('twain_active_manuscript');\n    if (!raw) return;\n    var data = JSON.parse(raw);\n    F.title   = data.title   || '';\n    F.rawText = data.rawText || '';\n    F.bible   = data.bible   || {};\n    if (F.title)   document.getElementById('book-title').value  = F.title;\n    if (data.author) document.getElementById('book-author').value = data.author;\n    if (F.rawText) {\n      document.getElementById('ms-raw').value = F.rawText;\n      updateStats();\n      setStatus('Loaded from Genesis \u2014 ' + F.rawText.split(\/\\s+\/).length.toLocaleString() + ' words. Hit Forge Manuscript.', 'info');\n    }\n    document.getElementById('stat-project').textContent = (F.title || 'Untitled').substring(0, 14);\n  } catch(e) {}\n}\n\nfunction ingestFile(inp) { var f = inp.files[0]; if (f) processUploadedFile(f); }\n\nfunction processUploadedFile(file) {\n  var ext = file.name.split('.').pop().toLowerCase();\n  if (['txt','md','docx','doc'].indexOf(ext) === -1) { setStatus('Use .txt, .md, or .docx', 'err'); return; }\n  var prog = document.getElementById('upload-progress');\n  var bar  = document.getElementById('upload-bar');\n  var lbl  = document.getElementById('upload-label');\n  prog.style.display = 'block'; bar.style.width = '20%'; lbl.textContent = 'Reading ' + file.name + '...';\n  var reader = new FileReader();\n  if (ext === 'docx' || ext === 'doc') {\n    reader.onload = function(e) {\n      bar.style.width = '60%'; lbl.textContent = 'Extracting DOCX...';\n      try {\n        extractDocxText(e.target.result, file.name, function(text) {\n          bar.style.width = '100%'; lbl.textContent = 'Done.';\n          setTimeout(function() { prog.style.display = 'none'; bar.style.width = '0%'; }, 1200);\n          finalizeIngest(text, file.name);\n        });\n      } catch(err) { prog.style.display = 'none'; setStatus('DOCX failed: ' + err.message, 'err'); }\n    };\n    reader.readAsArrayBuffer(file);\n  } else {\n    reader.onload = function(e) {\n      bar.style.width = '100%'; lbl.textContent = 'Done.';\n      setTimeout(function() { prog.style.display = 'none'; bar.style.width = '0%'; }, 800);\n      finalizeIngest(e.target.result, file.name);\n    };\n    reader.readAsText(file);\n  }\n}\n\nfunction extractDocxText(buf, filename, callback) {\n  JSZip.loadAsync(buf).then(function(zip) {\n    var docFile = zip.file('word\/document.xml');\n    if (!docFile) { callback('[DOCX extraction failed \u2014 word\/document.xml not found.]'); return Promise.resolve(null); }\n    return docFile.async('string');\n  }).then(function(xmlContent) {\n    if (!xmlContent) { callback('[DOCX extraction failed \u2014 empty document.]'); return; }\n    var text = '';\n    var parser = new DOMParser();\n    var doc = parser.parseFromString(xmlContent, 'text\/xml');\n    var paragraphs = doc.getElementsByTagNameNS('http:\/\/schemas.openxmlformats.org\/wordprocessingml\/2006\/main', 'p');\n    for (var i = 0; i < paragraphs.length; i++) {\n      var paraText = '';\n      var runs = paragraphs[i].getElementsByTagNameNS('http:\/\/schemas.openxmlformats.org\/wordprocessingml\/2006\/main', 't');\n      for (var j = 0; j < runs.length; j++) paraText += runs[j].textContent;\n      paraText = paraText.trim();\n      text += paraText.length > 0 ? paraText + '\\n' : '\\n';\n    }\n    text = text.replace(\/\\r\\n\/g, '\\n').replace(\/\\n{4,}\/g, '\\n\\n\\n').trim();\n    callback(text || '[No readable text found in DOCX.]');\n  }).catch(function(err) { callback('[DOCX extraction failed: ' + err.message + ']'); });\n}\n\nfunction finalizeIngest(text, filename) {\n  text = text\n    .replace(\/file:[^\\s)>]*\/g, '')\n    .replace(\/https?:[^\\s)>]*\/g, '')\n    .replace(\/[(][)]\/g, '')\n    .replace(\/[(]\\s*[)]\/g, '')\n    .replace(\/\\n{4,}\/g, '\\n\\n\\n')\n    .trim();\n  F.rawText = text;\n  document.getElementById('ms-raw').value = F.rawText;\n  updateStats();\n  var words = F.rawText.split(\/\\s+\/).filter(function(w) { return w.length > 0; }).length;\n  setStatus('Loaded: ' + filename + ' \u2014 ' + words.toLocaleString() + ' words. Hit Forge Manuscript.', 'ok');\n  addMsg('<strong>' + filename + '<\/strong> loaded \u2014 ' + words.toLocaleString() + ' words. Hit <strong>Forge Manuscript<\/strong> to structure and format it.', false);\n  showTab('raw');\n  var firstLine = text.split('\\n')[0].replace(\/^#+\\s*\/, '').trim();\n  if (firstLine && firstLine.length < 80 &#038;&#038; !document.getElementById('book-title').value) {\n    document.getElementById('book-title').value = firstLine;\n    F.title = firstLine;\n    document.getElementById('stat-project').textContent = firstLine.substring(0, 14);\n  }\n}\n\n\/\/ ================================================================\n\/\/ FORGE ENGINE \u2014 detectChapters + runForge\n\/\/ ================================================================\nfunction detectChapters(text) {\n  text = (text || '').replace(\/\\r\\n\/g, '\\n').trim();\n\n  var chapterRegex = \/(?:^|\\n)\\s*((?:chapter|ch\\.?)\\s+(?:\\d+|[ivxlcdm]+)(?:\\s*[:\\-\u2014]\\s*.*|[ \\t]+.*)?)\\s*(?=\\n)\/gim;\n\n  var matches = [];\n  var match;\n\n  while ((match = chapterRegex.exec(text)) !== null) {\n    matches.push({\n      title: match[1].trim(),\n      index: match.index + match[0].indexOf(match[1])\n    });\n  }\n\n  if (matches.length === 0) {\n    var parts = text.split(\/\\n{3,}\/);\n    if (parts.length > 1) {\n      return parts.map(function(part, i) {\n        var trimmed = part.trim();\n        var lines = trimmed.split('\\n').filter(Boolean);\n        var firstLine = lines[0] || ('Section ' + (i + 1));\n        var body = lines.slice(1).join('\\n').trim();\n\n        return {\n          title: firstLine,\n          body: body || trimmed\n        };\n      }).filter(function(c) {\n        return c.body && c.body.length > 100;\n      });\n    }\n\n    return [{\n      title: document.getElementById('book-title').value || 'Manuscript',\n      body: text\n    }];\n  }\n\n  var chapters = [];\n  for (var i = 0; i < matches.length; i++) {\n    var title = matches[i].title;\n    var start = matches[i].index + title.length;\n    var end = (i + 1 < matches.length) ? matches[i + 1].index : text.length;\n    var body = text.substring(start, end).trim();\n\n    chapters.push({\n      title: title,\n      body: body\n    });\n  }\n\n  return chapters.filter(function(c) {\n    return c.body &#038;&#038; c.body.length > 0;\n  });\n}\n\nfunction buildTitlePage(title, author) {\n  return '<div class=\"title-page\" style=\"page-break-after:always;\">' +\n    '<h1>' + escHtml(title) + '<\/h1>' +\n    '<div class=\"byline\">by ' + escHtml(author) + '<\/div><\/div>';\n}\n\nfunction buildCopyrightPage(title, author, isbn) {\n  var year  = new Date().getFullYear();\n  var month = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(new Date());\n  return '<div class=\"copyright-page\" style=\"page-break-after:always;font-size:10pt;line-height:1.6;\">' +\n    '<p><strong>' + escHtml(title.toUpperCase()) + '<\/strong><\/p>' +\n    '<p>Copyright &copy; ' + year + ' by ' + escHtml(author) + '.<\/p>' +\n    '<div class=\"rebellion-brand\"><strong>Published through T.W.A.I.N. Studio<\/strong><br><em>The Writers A.I. Nexus \u2014 twainstudio.dev<\/em><\/div>' +\n    '<p>All rights reserved. No part of this publication may be reproduced, distributed, or transmitted in any form or by any means without the prior written permission of the publisher.<\/p>' +\n    '<p>First Edition: ' + month + ' ' + year + '<br>ISBN: ' + escHtml(isbn || 'Pending') + '<br>Printed in the United States of America.<\/p>' +\n    '<\/div>';\n}\n\nfunction buildTOCHTML(chapters) {\n  var items = chapters.map(function(c, i) {\n    return '<div class=\"toc-entry\"><a href=\"#ch' + i + '\">' + escHtml(c.title) + '<\/a><span>' + (i + 1) + '<\/span><\/div>';\n  }).join('');\n  return '<div class=\"toc-section\" style=\"page-break-after:always;\"><h2>Table of Contents<\/h2>' + items + '<\/div>';\n}\n\nfunction buildDedicationHTML(text) {\n  return '<div class=\"dedication\" style=\"page-break-after:always;text-align:center;padding-top:2in;font-style:italic;font-size:13pt;\">' + escHtml(text) + '<\/div>';\n}\n\nfunction chaptersToHTML(chapters) {\n  return chapters.map(function(c, i) {\n    var paras = (c.body || '').split(\/\\n{2,}\/).map(function(p) {\n      var trimmed = p.trim();\n      return trimmed ? '<p>' + escHtml(trimmed) + '<\/p>' : '';\n    }).filter(Boolean).join('\\n');\n    return '<div class=\"chapter\" id=\"ch' + i + '\" style=\"page-break-before:always;page-break-after:always;\">' +\n      '<h1>' + escHtml(c.title) + '<\/h1>' +\n      paras + '<\/div>';\n  }).join('\\n');\n}\n\nfunction runForge() {\n  var raw = document.getElementById('ms-raw').value || F.rawText;\n  if (!raw.trim()) {\n    setStatus('Upload or paste a manuscript first.', 'err');\n    addMsg('No manuscript found. Upload a .docx or .txt file, or paste raw text in the Raw Text tab.', false);\n    return;\n  }\n  F.rawText = raw;\n  setStatus('Forging...', 'info');\n  showProgress(true);\n  setProgress(10, 'Detecting chapters...');\n\n  try {\n    F.chapters = detectChapters(F.rawText);\n    console.log('Detected chapters:', F.chapters.map(function(c, i) {\n  return {\n    chapter: i + 1,\n    title: c.title,\n    words: c.body ? c.body.split(\/\\s+\/).filter(Boolean).length : 0,\n    preview: (c.body || '').substring(0, 120)\n  };\n}));\n    setProgress(40, 'Building front matter...');\n\n    var title  = document.getElementById('book-title').value  || F.title || 'Untitled';\n    var author = document.getElementById('book-author').value || 'Author';\n    var isbn   = document.getElementById('book-isbn').value   || '';\n    var dedSeed = document.getElementById('book-dedication').value || '';\n\n    var titlePage   = buildTitlePage(title, author);\n    var copyPage    = buildCopyrightPage(title, author, isbn);\n    var dedPage     = dedSeed ? buildDedicationHTML(dedSeed) : '';\n    var tocHTML     = buildDualTOCHTML(F.chapters);\n    var chaptersHTML = chaptersToHTML(F.chapters);\n\n    setProgress(75, 'Assembling document...');\n\n    var html = titlePage + copyPage + (dedPage || '') + tocHTML + chaptersHTML;\n    F.formattedHTML = html;\n\n    setProgress(90, 'Rendering canvas...');\n    pushHTMLToCanvas(html);\n\n    \/\/ Build chapter list\n    var chList = document.getElementById('ch-list');\n    chList.innerHTML = '';\n    F.chapters.forEach(function(c, i) {\n      var wc = c.body ? c.body.split(\/\\s+\/).filter(Boolean).length : 0;\n      var item = document.createElement('div');\n      item.className = 'ch-item';\n      item.innerHTML = '<span>' + escHtml(c.title.substring(0, 28)) + '<\/span><span class=\"ch-wc\">' + wc + 'w<\/span>';\n      item.onclick = function() { scrollToChapter(i); };\n      chList.appendChild(item);\n    });\n\n    setProgress(100, 'Forge complete.');\n    setTimeout(function() { showProgress(false); }, 1500);\n\n    document.getElementById('btn-build-all').disabled = false;\n    document.getElementById('btn-review').disabled    = false;\n    document.getElementById('build-all-btn').disabled = false;\n    document.getElementById('review-start-btn').disabled = false;\n\n    updateStats();\n    setStatus('Forged \u2014 ' + F.chapters.length + ' chapters detected. Run AI Build All for front matter.', 'ok');\n    addMsg('Manuscript forged \u2014 <strong>' + F.chapters.length + ' chapters<\/strong> detected. Front matter (title, copyright, TOC) added.<br><br>Run <strong>\u26a1 AI Build All<\/strong> to have Groq write the TOC, copyright, dedication, foreword, and chapter titles. Then run <strong>\u2726 Full Chapter Review<\/strong>.', false);\n    showTab('preview');\n\n  } catch(e) {\n    setStatus('Forge failed: ' + e.message, 'err');\n    showProgress(false);\n  }\n}\n\n\/\/ ================================================================\n\/\/ SECTION INJECTION\n\/\/ ================================================================\nfunction replaceOrInsertSection(html, selectorName) {\n  var parser = new DOMParser();\n  var doc = parser.parseFromString('<div id=\"root\">' + (F.formattedHTML || '') + '<\/div>', 'text\/html');\n  var root = doc.getElementById('root');\n  var temp = doc.createElement('div');\n  temp.innerHTML = html.trim();\n  var newNode = temp.firstElementChild;\n  if (!newNode) return;\n  var className = selectorName.replace('.', '');\n  var existing = root.querySelector(selectorName);\n  if (existing) {\n    existing.replaceWith(newNode);\n  } else {\n    if (className === 'copyright-page') {\n      var tp = root.querySelector('.title-page');\n      if (tp && tp.nextSibling) root.insertBefore(newNode, tp.nextSibling);\n      else root.appendChild(newNode);\n    } else if (className === 'dedication') {\n      var toc = root.querySelector('.toc-section');\n      if (toc) root.insertBefore(newNode, toc);\n      else root.appendChild(newNode);\n    } else if (className === 'toc-section') {\n      var fc = root.querySelector('.chapter');\n      if (fc) root.insertBefore(newNode, fc);\n      else root.appendChild(newNode);\n    } else if (className === 'foreword') {\n      var fc2 = root.querySelector('.chapter');\n      if (fc2) root.insertBefore(newNode, fc2);\n      else root.appendChild(newNode);\n    } else {\n      root.appendChild(newNode);\n    }\n  }\n  F.formattedHTML = root.innerHTML;\n  pushHTMLToCanvas(F.formattedHTML);\n}\n\nfunction injectSection(html, selector) { replaceOrInsertSection(html, selector); }\n\n\/\/ ================================================================\n\/\/ AI BUILD ALL\n\/\/ ================================================================\nasync function buildAllFrontMatter() {\n  if (F.buildRunning) { setStatus('Build already running...', 'info'); return; }\n  if (F.chapters.length === 0) { setStatus('Forge manuscript first.', 'err'); return; }\n  var key = getGroqKey();\n  if (!key) {\n    setStatus('Groq API key required.', 'err');\n    addMsg('Add your Groq API key to run AI Build All.', false);\n    showTab('build'); return;\n  }\n  F.buildRunning = true;\n  showTab('build');\n  document.getElementById('build-progress').style.display = 'block';\n  document.getElementById('build-all-btn').disabled = true;\n  addMsg('\u26a1 AI Build All started \u2014 Groq is writing your front matter.', false, 'purple');\n  try {\n    setBuildProgress(10, 'Step 1\/5 \u2014 Table of Contents...');\n    await buildTOCWithAI();\n    setBuildProgress(30, 'Step 2\/5 \u2014 Copyright Page...');\n    await buildCopyrightWithAI();\n    setBuildProgress(50, 'Step 3\/5 \u2014 Dedication...');\n    await buildDedicationWithAI();\n    setBuildProgress(70, 'Step 4\/5 \u2014 Foreword...');\n    await buildForewordWithAI();\n    setBuildProgress(90, 'Step 5\/5 \u2014 Chapter Titles...');\n    await buildChapterTitlesWithAI();\n    setBuildProgress(100, 'AI Build Complete \u2713');\n    setTimeout(function() { document.getElementById('build-progress').style.display = 'none'; }, 2500);\n    setStatus('AI Build complete \u2014 all front matter generated.', 'ok');\n    addMsg('\u26a1 AI Build All complete. TOC, Copyright, Dedication, and Foreword written by Groq and injected into the canvas.<br><br>Now run <strong>\u2726 Full Chapter Review<\/strong>.', false, 'purple');\n    showTab('preview');\n  } catch(e) {\n    setStatus('AI Build failed: ' + e.message, 'err');\n    addMsg('\u2717 AI Build failed at step: ' + e.message + '<br><br>Click <strong>Test Connection<\/strong> in the key panel to verify your Groq key is working.', false);\n    document.getElementById('build-progress').style.display = 'none';\n  }\n  F.buildRunning = false;\n  document.getElementById('build-all-btn').disabled = false;\n}\n\nfunction setBuildProgress(pct, lbl) {\n  document.getElementById('build-fill').style.width = pct + '%';\n  document.getElementById('build-lbl').textContent  = lbl;\n}\n\nfunction setStepState(stepId, state) {\n  var card   = document.getElementById('step-' + stepId);\n  var status = document.getElementById('step-' + stepId + '-status');\n  if (card)   card.className   = 'ai-step-card' + ({ running: ' running', done: ' done', failed: ' failed' }[state] || '');\n  if (status) status.textContent = { running: '\u27f3', done: '\u2713', failed: '\u2717' }[state] || '\u25cb';\n}\n\n\/\/ \u2500\u2500 TOC \u2500\u2500\nasync function buildTOCWithAI() {\n  if (F.chapters.length === 0) {\n    setStatus('Forge manuscript first.', 'err');\n    return;\n  }\n\n  setStepState('toc', 'running');\n  document.getElementById('out-toc').textContent = 'Building TOC from chapter list...';\n\n  try {\n    console.log('TOC chapters:', F.chapters.map(function(c, i) {\n      return {\n        chapter: i + 1,\n        title: c.title,\n        words: c.body ? c.body.split(\/\\s+\/).filter(Boolean).length : 0\n      };\n    }));\n\n    var tocHTML = buildTOCHTML(F.chapters);\n    injectSection(tocHTML, '.toc-section');\n\n    document.getElementById('out-toc').textContent =\n      'TOC generated \u2014 ' + F.chapters.length + ' entries.';\n    setStepState('toc', 'done');\n  } catch (e) {\n    document.getElementById('out-toc').textContent =\n      'TOC failed (' + e.message + ')';\n    setStepState('toc', 'failed');\n  }\n}\n\n\/\/ \u2500\u2500 COPYRIGHT \u2500\u2500\nasync function buildCopyrightWithAI() {\n  setStepState('copy', 'running');\n  document.getElementById('out-copy').textContent = 'Groq writing copyright page...';\n  var title  = document.getElementById('book-title').value  || 'Untitled';\n  var author = document.getElementById('book-author').value || 'Author';\n  var isbn   = document.getElementById('book-isbn').value   || 'Pending';\n  var year   = new Date().getFullYear();\n  try {\n    var result = await callAI(\n      'You are a professional book formatter. Output only HTML \u2014 no explanation, no markdown.',\n      'Write a professional copyright page HTML for:\\nTitle: ' + title + '\\nAuthor: ' + author + '\\nISBN: ' + isbn + '\\nYear: ' + year + '\\nPublisher: T.W.A.I.N. Studio \u2014 The Writers A.I. Nexus\\n\\n' +\n      'Output a <div class=\"copyright-page\" style=\"page-break-after:always;font-size:10pt;line-height:1.6;\"> with proper copyright notice, all rights reserved, publisher credit, and ISBN. Output ONLY the HTML.',\n      500\n    );\n    injectSection(result, '.copyright-page');\n    document.getElementById('out-copy').textContent = 'Copyright page written.';\n    setStepState('copy', 'done');\n  } catch(e) {\n    injectSection(buildCopyrightPage(title, author, isbn), '.copyright-page');\n    document.getElementById('out-copy').textContent = 'Static copyright used (' + e.message + ')';\n    setStepState('copy', 'done');\n  }\n}\n\n\/\/ \u2500\u2500 DEDICATION \u2500\u2500\nasync function buildDedicationWithAI() {\n  setStepState('ded', 'running');\n  document.getElementById('out-ded').textContent = 'Groq writing dedication...';\n  var seed   = document.getElementById('book-dedication').value || '';\n  var title  = document.getElementById('book-title').value  || 'Untitled';\n  var author = document.getElementById('book-author').value || 'Author';\n  try {\n    var prompt = seed\n      ? 'Write a brief, elegant book dedication based on this seed: \"' + seed + '\". One to three lines, poetic but simple.'\n      : 'Write a brief, elegant dedication for \"' + title + '\" by ' + author + '. One to three lines, poetic but simple.';\n    var result = await callAI(\n      'You are a literary editor. Output only the dedication text \u2014 no HTML tags, no explanation.',\n      prompt, 200\n    );\n    var dedHTML = '<div class=\"dedication\" style=\"page-break-after:always;text-align:center;padding-top:2in;font-style:italic;font-size:13pt;\">' + escHtml(result.trim()) + '<\/div>';\n    injectSection(dedHTML, '.dedication');\n    document.getElementById('out-ded').textContent = result.trim().substring(0, 80) + '...';\n    setStepState('ded', 'done');\n  } catch(e) {\n    if (seed) injectSection(buildDedicationHTML(seed), '.dedication');\n    document.getElementById('out-ded').textContent = 'Dedication skipped (' + e.message + ')';\n    setStepState('ded', seed ? 'done' : 'failed');\n  }\n}\n\n\/\/ \u2500\u2500 FOREWORD \u2500\u2500\nvar CUSTOM_FOREWORD_TEXT = [\n  \"Most books try to impress you in the first few pages. This one doesn\u2019t. It pulls you in quietly, almost without permission, and before you realize it, you\u2019re not just reading about the city\u2014you\u2019re inside it. Jerald Wright writes with control, not excess. Every scene has weight. Every line earns its place. That kind of discipline is rare.\",\n  \n  \"Smoky Blues is not about noise. It\u2019s about what lingers after the noise fades\u2014the conversations that stay with you, the choices you can\u2019t undo, and the truth people avoid until it costs them something. Wright understands that tension, and he doesn\u2019t rush it. He lets it build, then lets it land.\",\n  \n  \"What makes this work stand out is not just the atmosphere, but the honesty behind it. There\u2019s no attempt to dress things up or soften the edges. The result is a story that feels lived-in, not performed. By the time you reach the final page, you won\u2019t just remember what happened\u2014you\u2019ll remember how it felt to be there.\"\n];\n\nfunction buildForewordHTML(paragraphs) {\n  var title  = document.getElementById('book-title').value  || 'Untitled';\n  var author = document.getElementById('book-author').value || 'Author';\n  var paras  = paragraphs.map(function(p) {\n    return '<p style=\"text-indent:0;margin-bottom:1.1em;font-size:12pt;line-height:1.7;\">' + escHtml(p) + '<\/p>';\n  }).join('');\n  return '<div class=\"foreword\" style=\"page-break-after:always;\">' +\n    '<h2 style=\"font-family:Georgia,serif;font-size:16pt;font-weight:700;text-align:center;margin:0 0 0.4in 0;\">Foreword<\/h2>' +\n    paras +\n    '<p style=\"margin-top:1.5em;font-style:italic;color:#555;\">\\u2014 T.W.A.I.N. Editorial<\/p>' +\n    '<\/div>';\n}\n\n\/\/ \u2500\u2500 FOREWORD (fixed: actually calls AI now) \u2500\u2500\nasync function buildForewordWithAI() {\n  setStepState('fore', 'running');\n  document.getElementById('out-fore').textContent = 'Groq writing foreword...';\n  var customFore = document.getElementById('book-foreword-custom')\n    ? document.getElementById('book-foreword-custom').value.trim()\n    : '';\n  var title  = document.getElementById('book-title').value  || 'Untitled';\n  var author = document.getElementById('book-author').value || 'Author';\n\n  \/\/ If the user typed their own foreword, use it directly \u2014 no AI needed\n  if (customFore) {\n    var paragraphs = customFore.split(\/\\n{2,}\/).map(function(p) { return p.trim(); }).filter(Boolean);\n    try {\n      injectSection(buildForewordHTML(paragraphs), '.foreword');\n      document.getElementById('out-fore').textContent = 'Custom foreword injected \u2014 ' + paragraphs.length + ' paragraphs.';\n      setStepState('fore', 'done');\n    } catch(e) {\n      document.getElementById('out-fore').textContent = 'Foreword failed (' + e.message + ')';\n      setStepState('fore', 'failed');\n    }\n    return;\n  }\n\n  \/\/ No custom text \u2014 ask Groq\n  try {\n    var result = await callAI(\n      'You are a literary editor writing a foreword for a book. Write in elegant, literary prose. No HTML tags, no markdown, just plain paragraphs separated by blank lines.',\n      'Write a compelling 2-paragraph foreword for \"' + title + '\" by ' + author + '. ' +\n      'The foreword should praise the author\\'s craft and describe what makes this book significant. ' +\n      'Keep each paragraph 4\u20136 sentences. Output ONLY the two paragraphs, separated by a blank line.',\n      400\n    );\n    var paragraphs = result.trim().split(\/\\n{2,}\/).map(function(p) { return p.trim(); }).filter(Boolean);\n    if (paragraphs.length === 0) throw new Error('Empty response from Groq');\n    injectSection(buildForewordHTML(paragraphs), '.foreword');\n    document.getElementById('out-fore').textContent = 'Foreword written by Groq \u2014 ' + paragraphs.length + ' paragraphs.';\n    setStepState('fore', 'done');\n  } catch(e) {\n    \/\/ Fall back to the embedded custom foreword text\n    injectSection(buildForewordHTML(CUSTOM_FOREWORD_TEXT), '.foreword');\n    document.getElementById('out-fore').textContent = 'Static foreword used (' + e.message + ')';\n    setStepState('fore', 'done'); \/\/ still \"done\" \u2014 content was injected\n  }\n}\n\n\/\/ \u2500\u2500 CHAPTER TITLES (fixed: robust JSON parsing + cleaned response) \u2500\u2500\nasync function buildChapterTitlesWithAI() {\n  if (F.chapters.length === 0) { setStepState('ch', 'failed'); return; }\n  setStepState('ch', 'running');\n  document.getElementById('out-ch').textContent = 'Groq suggesting chapter titles...';\n  var title = document.getElementById('book-title').value || 'Untitled';\n  var chSummaries = F.chapters.map(function(c, i) {\n    return (i + 1) + '. Current: \"' + c.title + '\" \u2014 opening: ' + (c.body || '').substring(0, 200);\n  }).join('\\n\\n');\n\n  try {\n    var result = await callAI(\n      'You are a literary editor. Suggest evocative chapter titles. Be specific to the content. Output ONLY raw JSON \u2014 no markdown fences, no explanation, no preamble.',\n      'Suggest chapter titles for \"' + title + '\". For each chapter provide 3 options:\\n\\n' + chSummaries +\n      '\\n\\nOutput a raw JSON array (no code block, no backticks): [{\"chapter\":1,\"options\":[\"Title A\",\"Title B\",\"Title C\"]}, ...]. ONLY the JSON array.',\n      1200\n    );\n\n    \/\/ FIX: strip all markdown fences and surrounding whitespace robustly\n    var cleaned = result\n      .replace(\/^```[\\w]*\\s*\/m, '')   \/\/ opening fence + optional language tag\n      .replace(\/\\s*```\\s*$\/m, '')     \/\/ closing fence\n      .trim();\n\n    \/\/ FIX: if the model still wrapped it, find the first [ and last ]\n    var startIdx = cleaned.indexOf('[');\n    var endIdx   = cleaned.lastIndexOf(']');\n    if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n      cleaned = cleaned.substring(startIdx, endIdx + 1);\n    }\n\n    var suggestions = JSON.parse(cleaned);\n    if (!Array.isArray(suggestions)) throw new Error('Response was not a JSON array');\n\n    suggestions.forEach(function(s) {\n      var idx = s.chapter - 1;\n      if (F.chapters[idx] && s.options && s.options.length > 0) {\n        F.chapters[idx].suggestedTitles = s.options;\n        F.chapters[idx].chosenTitle = null;\n      }\n    });\n\n    rebuildChapterList();\n    renderTitleChooserUI();\n    document.getElementById('out-ch').textContent = suggestions.length + ' chapters \u2014 choose titles below.';\n    setStepState('ch', 'done');\n    addMsg('\u2726 Chapter title suggestions ready. Use the Title Chooser in the AI Build tab to accept titles one by one, or Apply All \/ Apply None.', false, 'purple');\n    showTab('build');\n  } catch(e) {\n    document.getElementById('out-ch').textContent = 'Title suggestions failed (' + e.message + ')';\n    setStepState('ch', 'failed');\n  }\n}\n\n\/\/ \u2500\u2500 TITLE CHOOSER UI (fixed: \"Keep Original\" always visible) \u2500\u2500\nfunction renderTitleChooserUI() {\n  var old = document.getElementById('title-chooser-panel');\n  if (old) old.remove();\n\n  var panel = document.createElement('div');\n  panel.id = 'title-chooser-panel';\n  panel.style.cssText = 'background:var(--navy3);border:1px solid rgba(201,168,76,0.25);border-radius:3px;padding:14px;margin-top:8px;';\n\n  var header = '<div style=\"font-family:Oswald,sans-serif;font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--gold-dim);\">\u2726 Chapter Title Chooser<\/div>';\n\n  var bulkBtns = '<div style=\"display:flex;gap:6px;margin-bottom:12px;\">' +\n    '<button onclick=\"applyAllSuggestedTitles()\" style=\"flex:1;padding:6px;background:var(--green-dark);color:#b5e8d8;border:none;border-radius:2px;font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;\">\u2713 Apply All<\/button>' +\n    '<button onclick=\"applyNoneSuggestedTitles()\" style=\"flex:1;padding:6px;background:transparent;color:var(--slate2);border:1px solid rgba(138,154,181,0.25);border-radius:2px;font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;\">\u2717 Keep Originals<\/button>' +\n    '<button onclick=\"applyChosenTitles()\" style=\"flex:1;padding:6px;background:var(--orange);color:#fff;border:none;border-radius:2px;font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;\">\u26a1 Apply Chosen<\/button>' +\n    '<\/div>';\n\n  var rows = F.chapters.map(function(c, i) {\n    if (!c.suggestedTitles) return '';\n    var opts = c.suggestedTitles.map(function(opt, oi) {\n      var isChosen = c.chosenTitle === opt;\n      return '<button onclick=\"chooseTitleOption(' + i + ',' + oi + ')\" id=\"title-opt-' + i + '-' + oi + '\" style=\"display:block;width:100%;text-align:left;padding:5px 8px;margin-bottom:3px;background:' +\n        (isChosen ? 'rgba(29,158,117,0.15)' : 'rgba(255,255,255,0.02)') +\n        ';border:1px solid ' + (isChosen ? 'rgba(29,158,117,0.5)' : 'rgba(201,168,76,0.12)') +\n        ';border-radius:2px;color:' + (isChosen ? '#b5e8d8' : 'var(--cream)') +\n        ';font-family:Crimson Text,serif;font-size:12px;cursor:pointer;\">' +\n        (isChosen ? '\u2713 ' : '') + escHtml(opt) + '<\/button>';\n    }).join('');\n\n    \/\/ FIX: \"Keep Original\" is ALWAYS shown (not conditional on !c.chosenTitle)\n    \/\/ It acts as a deselect \u2014 clicking it while another is chosen will unset the choice\n    var isKeepChosen = (c.chosenTitle === null || c.chosenTitle === undefined);\n    var keepBtn = '<button onclick=\"chooseTitleOption(' + i + ',-1)\" id=\"title-opt-' + i + '-keep\" style=\"display:block;width:100%;text-align:left;padding:4px 8px;background:' +\n      (isKeepChosen ? 'rgba(29,158,117,0.08)' : 'rgba(255,255,255,0.02)') +\n      ';border:1px solid ' + (isKeepChosen ? 'rgba(29,158,117,0.3)' : 'rgba(138,154,181,0.15)') +\n      ';border-radius:2px;color:var(--slate2);font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.06em;cursor:pointer;margin-bottom:2px;\">' +\n      (isKeepChosen ? '\u2713 ' : '') + 'KEEP ORIGINAL: ' + escHtml(c.title.substring(0, 40)) + '<\/button>';\n\n    return '<div id=\"title-row-' + i + '\" style=\"margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid rgba(201,168,76,0.07);\">' +\n      '<div style=\"font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--slate);margin-bottom:5px;\">CH ' +\n      String(i + 1).padStart(2, '0') + ' \u2014 Original: <em style=\"color:var(--slate2);\">' + escHtml(c.title.substring(0, 35)) + '<\/em><\/div>' +\n      opts + keepBtn + '<\/div>';\n  }).join('');\n\n  panel.innerHTML = header + bulkBtns + '<div id=\"title-chooser-rows\">' + rows + '<\/div>';\n\n  var stepCh = document.getElementById('step-ch');\n  if (stepCh && stepCh.parentNode) {\n    stepCh.parentNode.insertBefore(panel, stepCh.nextSibling);\n  } else {\n    document.querySelector('.ai-build-panel').appendChild(panel);\n  }\n}\n\n\/\/ \u2500\u2500 chooseTitleOption (fixed: updates Keep Original button highlight too) \u2500\u2500\nfunction chooseTitleOption(chIdx, optIdx) {\n  var c = F.chapters[chIdx];\n  if (!c) return;\n\n  c.chosenTitle = (optIdx === -1) ? null : c.suggestedTitles[optIdx];\n\n  \/\/ Refresh suggested-title buttons\n  (c.suggestedTitles || []).forEach(function(opt, oi) {\n    var btn = document.getElementById('title-opt-' + chIdx + '-' + oi);\n    if (!btn) return;\n    var isChosen = c.chosenTitle === opt;\n    btn.style.background   = isChosen ? 'rgba(29,158,117,0.15)' : 'rgba(255,255,255,0.02)';\n    btn.style.borderColor  = isChosen ? 'rgba(29,158,117,0.5)'  : 'rgba(201,168,76,0.12)';\n    btn.style.color        = isChosen ? '#b5e8d8' : 'var(--cream)';\n    btn.textContent        = (isChosen ? '\u2713 ' : '') + opt;\n  });\n\n  \/\/ FIX: also update the Keep Original button state\n  var keepBtn = document.getElementById('title-opt-' + chIdx + '-keep');\n  if (keepBtn) {\n    var isKeep = (c.chosenTitle === null || c.chosenTitle === undefined);\n    keepBtn.style.background  = isKeep ? 'rgba(29,158,117,0.08)' : 'rgba(255,255,255,0.02)';\n    keepBtn.style.borderColor = isKeep ? 'rgba(29,158,117,0.3)'  : 'rgba(138,154,181,0.15)';\n    keepBtn.textContent       = (isKeep ? '\u2713 ' : '') + 'KEEP ORIGINAL: ' + escHtml(c.title.substring(0, 40));\n  }\n\n  rebuildChapterList();\n}\n\n\/\/ \u2500\u2500 applyChosenTitles (fixed: preserves AI-generated foreword from canvas) \u2500\u2500\nfunction applyChosenTitles() {\n  var changed = 0;\n  F.chapters.forEach(function(c) {\n    if (c.chosenTitle) { c.title = c.chosenTitle; c.chosenTitle = null; changed++; }\n  });\n  if (changed === 0) { setStatus('No titles selected to apply.', 'info'); return; }\n\n  var title   = document.getElementById('book-title').value  || F.title || 'Untitled';\n  var author  = document.getElementById('book-author').value || 'Author';\n  var isbn    = document.getElementById('book-isbn').value   || '';\n  var dedSeed = document.getElementById('book-dedication').value || '';\n\n  \/\/ Preserve ALL existing front-matter sections from the live canvas\n  var parser      = new DOMParser();\n  var existingDoc = parser.parseFromString('<div id=\"root\">' + (F.formattedHTML || '') + '<\/div>', 'text\/html');\n  var root        = existingDoc.getElementById('root');\n\n  \/\/ FIX: preserve each section independently, fall back gracefully if missing\n  var forewordEl   = root ? root.querySelector('.foreword')        : null;\n  var copyrightEl  = root ? root.querySelector('.copyright-page')  : null;\n  var dedicationEl = root ? root.querySelector('.dedication')      : null;\n\n  var forewordHTML   = forewordEl   ? forewordEl.outerHTML   : '';\n  var copyrightHTML  = copyrightEl  ? copyrightEl.outerHTML  : buildCopyrightPage(title, author, isbn);\n  var dedicationHTML = dedicationEl ? dedicationEl.outerHTML : (dedSeed ? buildDedicationHTML(dedSeed) : '');\n\n  var titlePage    = buildTitlePage(title, author);\n  var tocHTML      = buildDualTOCHTML(F.chapters);\n  var chaptersHTML = chaptersToHTML(F.chapters);\n\n  F.formattedHTML = titlePage + copyrightHTML + dedicationHTML + tocHTML + forewordHTML + chaptersHTML;\n  pushHTMLToCanvas(F.formattedHTML);\n  rebuildChapterList();\n  renderTitleChooserUI();\n  setStatus(changed + ' chapter title(s) applied. TOC updated.', 'ok');\n  addMsg('\u2726 ' + changed + ' title(s) applied. Canvas and TOC refreshed.', false, 'purple');\n}\n\n\/\/ \u2500\u2500 CHAPTER TITLES \u2500\u2500\nasync function buildChapterTitlesWithAI() {\n  if (F.chapters.length === 0) { setStepState('ch', 'failed'); return; }\n  setStepState('ch', 'running');\n  document.getElementById('out-ch').textContent = 'Groq suggesting chapter titles...';\n  var title = document.getElementById('book-title').value || 'Untitled';\n  var chSummaries = F.chapters.map(function(c, i) {\n    return (i + 1) + '. Current: \"' + c.title + '\" \u2014 opening: ' + (c.body || '').substring(0, 200);\n  }).join('\\n\\n');\n  try {\n    var result = await callAI(\n      'You are a literary editor. Suggest evocative chapter titles. Be specific to the content. Output only JSON.',\n      'Suggest chapter titles for \"' + title + '\". For each chapter provide 3 options:\\n\\n' + chSummaries +\n      '\\n\\nOutput JSON array: [{\"chapter\":1,\"options\":[\"Title A\",\"Title B\",\"Title C\"]}, ...]. Output ONLY the JSON.',\n      1200\n    );\n    var cleaned = result.replace(\/```json|```\/g, '').trim();\n    var suggestions = JSON.parse(cleaned);\n    suggestions.forEach(function(s) {\n      var idx = s.chapter - 1;\n      if (F.chapters[idx] && s.options && s.options[0]) {\n        F.chapters[idx].suggestedTitles = s.options;\n        F.chapters[idx].chosenTitle = null; \/\/ null = no change chosen yet\n      }\n    });\n    \/\/ Rebuild chapter list sidebar\n    rebuildChapterList();\n    \/\/ Show the title chooser UI in the build panel\n    renderTitleChooserUI();\n    document.getElementById('out-ch').textContent = suggestions.length + ' chapters \u2014 choose titles below.';\n    setStepState('ch', 'done');\n    addMsg('\u2726 Chapter title suggestions ready. Use the Title Chooser in the AI Build tab to accept titles one by one, or Apply All \/ Apply None.', false, 'purple');\n    showTab('build');\n  } catch(e) {\n    document.getElementById('out-ch').textContent = 'Title suggestions failed (' + e.message + ')';\n    setStepState('ch', 'failed');\n  }\n}\n\nfunction rebuildChapterList() {\n  var chList = document.getElementById('ch-list');\n  chList.innerHTML = '';\n  F.chapters.forEach(function(c, i) {\n    var item = document.createElement('div');\n    item.className = 'ch-item';\n    var displayTitle = c.chosenTitle ? '\u2713 ' + c.chosenTitle : (c.suggestedTitles ? '\u2726 ' + c.title : c.title);\n    var wc = c.body ? c.body.split(\/\\s+\/).length : 0;\n    item.innerHTML = '<span>' + escHtml(displayTitle.substring(0, 28)) + '<\/span><span class=\"ch-wc\">' + wc + 'w<\/span>';\n    item.title = c.suggestedTitles ? 'Options: ' + c.suggestedTitles.join(' | ') : c.title;\n    item.onclick = function() { scrollToChapter(i); };\n    if (c.chosenTitle) item.classList.add('reviewed');\n    chList.appendChild(item);\n  });\n}\n\nfunction renderTitleChooserUI() {\n  \/\/ Remove any existing chooser\n  var old = document.getElementById('title-chooser-panel');\n  if (old) old.remove();\n\n  var panel = document.createElement('div');\n  panel.id = 'title-chooser-panel';\n  panel.style.cssText = 'background:var(--navy3);border:1px solid rgba(201,168,76,0.25);border-radius:3px;padding:14px;margin-top:8px;';\n\n  var header = '<div style=\"font-family:Oswald,sans-serif;font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--gold-dim);\">\u2726 Chapter Title Chooser<\/div>';\n\n  var bulkBtns = '<div style=\"display:flex;gap:6px;margin-bottom:12px;\">' +\n    '<button onclick=\"applyAllSuggestedTitles()\" style=\"flex:1;padding:6px;background:var(--green-dark);color:#b5e8d8;border:none;border-radius:2px;font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;\">\u2713 Apply All<\/button>' +\n    '<button onclick=\"applyNoneSuggestedTitles()\" style=\"flex:1;padding:6px;background:transparent;color:var(--slate2);border:1px solid rgba(138,154,181,0.25);border-radius:2px;font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;\">\u2717 Keep Originals<\/button>' +\n    '<button onclick=\"applyChosenTitles()\" style=\"flex:1;padding:6px;background:var(--orange);color:#fff;border:none;border-radius:2px;font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;\">\u26a1 Apply Chosen<\/button>' +\n    '<\/div>';\n\n  var rows = F.chapters.map(function(c, i) {\n    if (!c.suggestedTitles) return '';\n    var opts = c.suggestedTitles.map(function(opt, oi) {\n      var isChosen = c.chosenTitle === opt;\n      return '<button onclick=\"chooseTitleOption(' + i + ',' + oi + ')\" id=\"title-opt-' + i + '-' + oi + '\" style=\"display:block;width:100%;text-align:left;padding:5px 8px;margin-bottom:3px;background:' + (isChosen ? 'rgba(29,158,117,0.15)' : 'rgba(255,255,255,0.02)') + ';border:1px solid ' + (isChosen ? 'rgba(29,158,117,0.5)' : 'rgba(201,168,76,0.12)') + ';border-radius:2px;color:' + (isChosen ? '#b5e8d8' : 'var(--cream)') + ';font-family:Crimson Text,serif;font-size:12px;cursor:pointer;\">' +\n        (isChosen ? '\u2713 ' : '') + escHtml(opt) + '<\/button>';\n    }).join('');\n    var keepBtn = !c.chosenTitle ? '<button onclick=\"chooseTitleOption(' + i + ',-1)\" style=\"display:block;width:100%;text-align:left;padding:4px 8px;background:rgba(255,255,255,0.02);border:1px solid rgba(138,154,181,0.15);border-radius:2px;color:var(--slate2);font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.06em;cursor:pointer;margin-bottom:2px;\">KEEP ORIGINAL: ' + escHtml(c.title.substring(0,40)) + '<\/button>' : '';\n    return '<div id=\"title-row-' + i + '\" style=\"margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid rgba(201,168,76,0.07);\">' +\n      '<div style=\"font-family:Oswald,sans-serif;font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--slate);margin-bottom:5px;\">CH ' + String(i+1).padStart(2,'0') + ' \u2014 Original: <em style=\"color:var(--slate2);\">' + escHtml(c.title.substring(0,35)) + '<\/em><\/div>' +\n      opts + keepBtn + '<\/div>';\n  }).join('');\n\n  panel.innerHTML = header + bulkBtns + '<div id=\"title-chooser-rows\">' + rows + '<\/div>';\n\n  \/\/ Insert after step-ch card\n  var stepCh = document.getElementById('step-ch');\n  if (stepCh && stepCh.parentNode) {\n    stepCh.parentNode.insertBefore(panel, stepCh.nextSibling);\n  } else {\n    document.querySelector('.ai-build-panel').appendChild(panel);\n  }\n}\n\nfunction chooseTitleOption(chIdx, optIdx) {\n  var c = F.chapters[chIdx];\n  if (!c) return;\n  if (optIdx === -1) {\n    c.chosenTitle = null; \/\/ keep original\n  } else {\n    c.chosenTitle = c.suggestedTitles[optIdx];\n  }\n  \/\/ Refresh button styles for that row\n  (c.suggestedTitles || []).forEach(function(opt, oi) {\n    var btn = document.getElementById('title-opt-' + chIdx + '-' + oi);\n    if (!btn) return;\n    var isChosen = c.chosenTitle === opt;\n    btn.style.background = isChosen ? 'rgba(29,158,117,0.15)' : 'rgba(255,255,255,0.02)';\n    btn.style.borderColor = isChosen ? 'rgba(29,158,117,0.5)' : 'rgba(201,168,76,0.12)';\n    btn.style.color = isChosen ? '#b5e8d8' : 'var(--cream)';\n    btn.textContent = (isChosen ? '\u2713 ' : '') + opt;\n  });\n  rebuildChapterList();\n}\n\nfunction applyAllSuggestedTitles() {\n  F.chapters.forEach(function(c) {\n    if (c.suggestedTitles && c.suggestedTitles[0]) c.chosenTitle = c.suggestedTitles[0];\n  });\n  applyChosenTitles();\n  renderTitleChooserUI();\n  addMsg('\u2713 All suggested titles applied. TOC and chapter headings updated.', false, 'purple');\n}\n\nfunction applyNoneSuggestedTitles() {\n  F.chapters.forEach(function(c) { c.chosenTitle = null; });\n  renderTitleChooserUI();\n  rebuildChapterList();\n  addMsg('Original chapter titles restored. No changes applied.', false);\n}\n\nfunction applyChosenTitles() {\n  \/\/ Apply chosen titles to chapters, rebuild HTML\n  var changed = 0;\n  F.chapters.forEach(function(c) {\n    if (c.chosenTitle) { c.title = c.chosenTitle; c.chosenTitle = null; changed++; }\n  });\n  if (changed === 0) { setStatus('No titles selected to apply.', 'info'); return; }\n  \/\/ Rebuild the full document with new titles\n  var title   = document.getElementById('book-title').value  || F.title || 'Untitled';\n  var author  = document.getElementById('book-author').value || 'Author';\n  var isbn    = document.getElementById('book-isbn').value   || '';\n  var dedSeed = document.getElementById('book-dedication').value || '';\n\n  \/\/ Re-extract the foreword and other front-matter from existing formattedHTML if present\n  var parser = new DOMParser();\n  var existingDoc = parser.parseFromString('<div id=\"root\">' + (F.formattedHTML || '') + '<\/div>', 'text\/html');\n  var root = existingDoc.getElementById('root');\n  var forewordEl = root ? root.querySelector('.foreword') : null;\n  var forewordHTML = forewordEl ? forewordEl.outerHTML : '';\n\n  var titlePage    = buildTitlePage(title, author);\n  var copyPage     = buildCopyrightPage(title, author, isbn);\n  var dedPage      = dedSeed ? buildDedicationHTML(dedSeed) : '';\n  var tocHTML      = buildDualTOCHTML(F.chapters);\n  var chaptersHTML = chaptersToHTML(F.chapters);\n  F.formattedHTML  = titlePage + copyPage + (dedPage || '') + tocHTML + forewordHTML + chaptersHTML;\n  pushHTMLToCanvas(F.formattedHTML);\n  rebuildChapterList();\n  renderTitleChooserUI();\n  setStatus(changed + ' chapter title(s) applied. TOC updated.', 'ok');\n  addMsg('\u2726 ' + changed + ' title(s) applied. Canvas and TOC refreshed.', false, 'purple');\n}\n\n\/\/ Dual TOC: before and after name changes (or single if no pending suggestions)\nfunction buildDualTOCHTML(chapters) {\n  var hasSuggestions = chapters.some(function(c) { return c.suggestedTitles && c.suggestedTitles.length; });\n  var currentItems = chapters.map(function(c, i) {\n    return '<div class=\"toc-entry\" style=\"display:flex;justify-content:space-between;border-bottom:1px dotted rgba(0,0,0,.2);padding:4px 0;\"><a href=\"#ch' + i + '\" style=\"color:inherit;text-decoration:none;\">' + escHtml(c.title) + '<\/a><span>' + (i + 1) + '<\/span><\/div>';\n  }).join('');\n  if (!hasSuggestions) {\n    return '<div class=\"toc-section\" style=\"page-break-after:always;\"><h2 style=\"font-family:Georgia,serif;font-size:16pt;text-align:center;margin-bottom:0.3in;\">Table of Contents<\/h2>' + currentItems + '<\/div>';\n  }\n  \/\/ Show both: original and suggested\n  var suggestedItems = chapters.map(function(c, i) {\n    var title = (c.suggestedTitles && c.suggestedTitles[0]) ? c.suggestedTitles[0] : c.title;\n    return '<div class=\"toc-entry\" style=\"display:flex;justify-content:space-between;border-bottom:1px dotted rgba(0,0,0,.2);padding:4px 0;\"><a href=\"#ch' + i + '\" style=\"color:inherit;text-decoration:none;\">' + escHtml(title) + '<\/a><span>' + (i + 1) + '<\/span><\/div>';\n  }).join('');\n  return '<div class=\"toc-section\" style=\"page-break-after:always;\">' +\n    '<h2 style=\"font-family:Georgia,serif;font-size:16pt;text-align:center;margin-bottom:0.15in;\">Table of Contents<\/h2>' +\n    '<p style=\"font-size:9pt;color:#888;text-align:center;margin-bottom:0.2in;font-style:italic;\">(Current chapter titles)<\/p>' +\n    currentItems +\n    '<div style=\"margin-top:0.3in;padding-top:0.2in;border-top:2px solid rgba(0,0,0,.15);\">' +\n    '<p style=\"font-size:9pt;color:#888;text-align:center;margin-bottom:0.15in;font-style:italic;\">After Suggested Renames<\/p>' +\n    suggestedItems + '<\/div><\/div>';\n}\n\n\/\/ ================================================================\n\/\/ FULL CHAPTER REVIEW\n\/\/ ================================================================\nasync function startFullReview() {\n  if (F.reviewRunning) return;\n  if (F.chapters.length === 0) { setStatus('Forge manuscript first.', 'err'); return; }\n  var key = getGroqKey();\n  if (!key) {\n    setStatus('Groq API key required.', 'err');\n    addMsg('Add your Groq API key to run the Full Chapter Review.', false);\n    return;\n  }\n  F.reviewRunning = true;\n  showTab('review');\n  document.getElementById('review-start-btn').disabled = true;\n  document.getElementById('review-progress').style.display = 'block';\n  var title = document.getElementById('book-title').value || 'Untitled';\n  var genre = document.getElementById('book-genre').value || 'general';\n  var cards = document.getElementById('review-cards');\n  cards.innerHTML = '';\n\n  F.chapters.forEach(function(ch, i) {\n    var card = document.createElement('div');\n    card.className = 'ch-review-card';\n    card.id = 'review-card-' + i;\n    var wc = ch.body ? ch.body.trim().split(\/\\s+\/).length : 0;\n    card.innerHTML = '<div class=\"ch-review-header\">' +\n      '<span class=\"ch-review-num\">CH ' + String(i + 1).padStart(2, '0') + '<\/span>' +\n      '<span class=\"ch-review-title\">' + escHtml(ch.title.substring(0, 40)) + '<\/span>' +\n      '<span class=\"ch-review-wc\">' + wc.toLocaleString() + ' words<\/span>' +\n      '<\/div><div class=\"ch-review-body\"><span class=\"ch-review-placeholder\">Waiting for review...<\/span><\/div>';\n    cards.appendChild(card);\n  });\n\n  addMsg('\u2726 Full Chapter Review started \u2014 Groq is reading each chapter...', false, 'purple');\n\n  for (var i = 0; i < F.chapters.length; i++) {\n    var ch = F.chapters[i];\n    var pct = Math.round((i \/ F.chapters.length) * 100);\n    document.getElementById('review-fill').style.width = pct + '%';\n    document.getElementById('review-lbl').textContent = 'Reviewing Chapter ' + (i + 1) + ' of ' + F.chapters.length + ': ' + ch.title.substring(0, 30) + '...';\n    var card = document.getElementById('review-card-' + i);\n    card.classList.add('reviewing');\n    var bodyEl = card.querySelector('.ch-review-body');\n    bodyEl.innerHTML = '<span class=\"ch-review-placeholder\">\u27f3 Groq reviewing...<\/span>';\n\n    try {\n      var excerpt = ch.body.substring(0, 3000) + (ch.body.length > 3000 ? '\\n[...truncated...]' : '');\n      var reviewResult = await callAI(\n        'You are a master editor. Give direct, specific, actionable feedback. Reference the actual text. Be honest. Output only JSON.',\n        'Review this chapter from \"' + title + '\" (Genre: ' + genre + '):\\n\\nCHAPTER: ' + ch.title + '\\n\\n' + excerpt +\n        '\\n\\nOutput JSON:\\n{\"hook_score\":1-10,\"pacing\":\"strong|good|weak\",\"voice\":\"strong|good|weak\",\"suggestions\":[{\"icon\":\"\ud83c\udfaf\",\"text\":\"specific suggestion\"},{\"icon\":\"\u2702\ufe0f\",\"text\":\"cut suggestion\"},{\"icon\":\"\ud83d\udca1\",\"text\":\"idea\"},{\"icon\":\"\u26a1\",\"text\":\"what works\"}],\"summary\":\"one-sentence verdict\"}\\nOutput ONLY JSON.',\n        700\n      );\n      var review;\n      try {\n        review = JSON.parse(reviewResult.replace(\/```json|```\/g, '').trim());\n      } catch(pe) {\n        review = { hook_score: null, pacing: '\u2014', voice: '\u2014', suggestions: [{ icon: '\ud83d\udcdd', text: reviewResult.substring(0, 300) }], summary: 'Review parsed.' };\n      }\n      card.classList.remove('reviewing');\n      var chItem = document.querySelector('#ch-list .ch-item:nth-child(' + (i + 1) + ')');\n      if (chItem) chItem.classList.add('reviewed');\n\n      var ratingsHTML = '<div class=\"rating-row\">';\n      if (review.hook_score !== null) {\n        var hc = review.hook_score >= 7 ? 'good' : review.hook_score >= 5 ? 'neutral' : 'warn';\n        ratingsHTML += '<span class=\"rating-badge ' + hc + '\">Hook ' + review.hook_score + '\/10<\/span>';\n      }\n      if (review.pacing) {\n        var pc = review.pacing === 'strong' ? 'good' : review.pacing === 'good' ? 'neutral' : 'warn';\n        ratingsHTML += '<span class=\"rating-badge ' + pc + '\">Pacing: ' + review.pacing + '<\/span>';\n      }\n      if (review.voice) {\n        var vc = review.voice === 'strong' ? 'good' : review.voice === 'good' ? 'neutral' : 'warn';\n        ratingsHTML += '<span class=\"rating-badge ' + vc + '\">Voice: ' + review.voice + '<\/span>';\n      }\n      ratingsHTML += '<\/div>';\n\n      var sugHTML = '';\n      if (review.suggestions && review.suggestions.length) {\n        review.suggestions.forEach(function(s) {\n          sugHTML += '<div class=\"suggestion\"><span class=\"suggestion-icon\">' + s.icon + '<\/span><span>' + escHtml(s.text) + '<\/span><\/div>';\n        });\n      }\n      var sumHTML = review.summary ? '<div style=\"margin-top:8px;font-family:\\'Crimson Text\\',serif;font-size:12px;font-style:italic;color:var(--slate2);border-top:1px solid rgba(201,168,76,0.1);padding-top:8px;\">' + escHtml(review.summary) + '<\/div>' : '';\n      bodyEl.innerHTML = ratingsHTML + sugHTML + sumHTML;\n\n      if (i < F.chapters.length - 1) await sleep(400);\n    } catch(e) {\n      card.classList.remove('reviewing');\n      bodyEl.innerHTML = '<div class=\"suggestion\"><span class=\"suggestion-icon\">\u26a0\ufe0f<\/span><span>Review failed: ' + escHtml(e.message) + '<\/span><\/div>';\n    }\n  }\n\n  document.getElementById('review-fill').style.width = '100%';\n  document.getElementById('review-lbl').textContent  = 'Review complete \u2014 ' + F.chapters.length + ' chapters analyzed \u2713';\n  setTimeout(function() { document.getElementById('review-progress').style.display = 'none'; }, 3000);\n  F.reviewRunning = false;\n  document.getElementById('review-start-btn').disabled = false;\n  setStatus('Chapter review complete \u2014 ' + F.chapters.length + ' chapters analyzed.', 'ok');\n  addMsg('\u2726 Chapter Review complete. ' + F.chapters.length + ' chapters analyzed with hook scores, pacing, voice ratings, and specific suggestions.<br><br>Individual chapter failures show in each card \u2014 those chapters can be re-run by clicking their card.', false, 'purple');\n}\n\nfunction sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }\n\n\/\/ ================================================================\n\/\/ A.A. BRAIN CHAT\n\/\/ ================================================================\nfunction buildForgeSystemPrompt() {\n  var title  = document.getElementById('book-title').value  || 'this book';\n  var author = document.getElementById('book-author').value || 'the author';\n  var genre  = document.getElementById('book-genre').value  || 'unknown genre';\n  return 'You are the T.W.A.I.N. Forge Brain \u2014 master editor and literary strategist for Rebellion Command.\\n\\nBook: \"' + title + '\" by ' + author + '\\nGenre: ' + genre + '\\n\\nRULES:\\n1. Be direct, punchy, no filler.\\n2. Commercial viability and author intent in equal measure.\\n3. When analyzing \u2014 quote the text, name the chapter. Be specific.';\n}\n\nasync function callBrain(userMsg) {\n  var key = getGroqKey();\n  if (!key) { addMsg('Enter your Groq API key to activate the A.A. Brain.', false); return null; }\n  F.conversation.push({ role: 'user', content: userMsg });\n  addThinking(); setLight('busy', 'Thinking...');\n  var messages = [{ role: 'system', content: buildForgeSystemPrompt() }].concat(F.conversation.slice(-14));\n  try {\n    var reply = await callGroq(messages, 800);\n    F.conversation.push({ role: 'assistant', content: reply });\n    removeThinking(); setLight('on', 'Ready');\n    return reply;\n  } catch(e) {\n    removeThinking(); setLight('error', 'Error');\n    addMsg('Brain error: ' + e.message, false);\n    return null;\n  }\n}\n\nasync function sendMsg() {\n  var inp = document.getElementById('chat-inp');\n  var text = inp.value.trim(); if (!text) return;\n  addMsg(text, true); inp.value = '';\n  var reply = await callBrain(text);\n  if (reply) addMsg(reply, false);\n}\n\n\/\/ Quick actions\nasync function analyzeStructure() {\n  var raw = F.rawText || document.getElementById('ms-raw').value;\n  if (!raw) { addMsg('Load a manuscript first.', false); return; }\n  addMsg('[Analyzing structural integrity...]', true);\n  var reply = await callBrain('Analyze the structure. Identify: 1) Hook quality, 2) Pacing consistency, 3) Strongest and weakest chapters by name, 4) Structural gaps. Be specific.\\n\\nManuscript:\\n' + raw.substring(0, 4000));\n  if (reply) addMsg(reply, false);\n}\nasync function suggestTitle() {\n  var raw = F.rawText || document.getElementById('ms-raw').value;\n  if (!raw) { addMsg('Load a manuscript first.', false); return; }\n  addMsg('[Generating title options...]', true);\n  var reply = await callBrain('Suggest 5 alternative titles. Each commercially strong, genre-appropriate, reflecting the core theme. Numbered list with one-line explanation.\\n\\nSample:\\n' + raw.substring(0, 2000));\n  if (reply) addMsg(reply, false);\n}\nasync function checkPacing() {\n  var raw = F.rawText || document.getElementById('ms-raw').value;\n  if (!raw) { addMsg('Load a manuscript first.', false); return; }\n  addMsg('[Checking pacing...]', true);\n  var reply = await callBrain('Analyze pacing. Where does momentum build? Where does it stall? Top 3 momentum killers with chapter references. One concrete fix each.\\n\\nManuscript:\\n' + raw.substring(0, 4000));\n  if (reply) addMsg(reply, false);\n}\nasync function hookAnalysis() {\n  var raw = F.rawText || document.getElementById('ms-raw').value;\n  if (!raw) { addMsg('Load a manuscript first.', false); return; }\n  addMsg('[Analyzing opening hook...]', true);\n  var reply = await callBrain('Score the opening hook 1-10 for: immediate engagement, voice clarity, promise of value. Then rewrite the opening paragraph to make it a 9 or 10. Show both versions.\\n\\nOpening:\\n' + raw.substring(0, 1500));\n  if (reply) addMsg(reply, false);\n}\nasync function audienceCheck() {\n  var raw = F.rawText || document.getElementById('ms-raw').value;\n  if (!raw) { addMsg('Load a manuscript first.', false); return; }\n  addMsg('[Running audience alignment check...]', true);\n  var reply = await callBrain('Identify: 1) Primary target reader in detail, 2) Whether writing consistently speaks to that reader, 3) Moments where the author loses the audience.\\n\\nSample:\\n' + raw.substring(0, 3000));\n  if (reply) addMsg(reply, false);\n}\nasync function genSummary() {\n  var raw   = F.rawText || document.getElementById('ms-raw').value;\n  var title = document.getElementById('book-title').value || 'this book';\n  if (!raw) { addMsg('Load a manuscript first.', false); return; }\n  addMsg('[Generating back-cover summary...]', true);\n  var reply = await callBrain('Write a 3-paragraph back cover summary for \"' + title + '\". First: hook. Second: core conflict or transformation. Third: what the reader gains. Commercial and punchy.\\n\\nSample:\\n' + raw.substring(0, 3000));\n  if (reply) addMsg(reply, false);\n}\n\n\/\/ ================================================================\n\/\/ EXPORT\n\/\/ ================================================================\nfunction saveForge() {\n  var title  = document.getElementById('book-title').value  || 'Untitled';\n  var author = document.getElementById('book-author').value || 'Author';\n  var raw    = document.getElementById('ms-raw').value      || F.rawText;\n  var words  = raw.trim() ? raw.trim().split(\/\\s+\/).length : 0;\n  var forgeData = {\n    title, author, rawText: raw, formattedHTML: F.formattedHTML,\n    chapters: F.chapters.map(function(c) { return { title: c.title, suggestedTitles: c.suggestedTitles, wordCount: c.body ? c.body.split(\/\\s+\/).length : 0 }; }),\n    isbn: document.getElementById('book-isbn').value || '',\n    genre: document.getElementById('book-genre').value || '',\n    dedication: document.getElementById('book-dedication').value || '',\n    bible: F.bible, words, savedAt: new Date().toISOString()\n  };\n  localStorage.setItem('twain_forge_data', JSON.stringify(forgeData));\n  localStorage.setItem('twain_active_manuscript', JSON.stringify({\n    title, author, rawText: raw, formattedHTML: F.formattedHTML,\n    bible: F.bible, chapters: forgeData.chapters, genre: forgeData.genre,\n    words, savedAt: forgeData.savedAt\n  }));\n  setStatus('Saved. Forge data ready for Pulse, Cover Studio, Deploy.', 'ok');\n}\n\nfunction exportHTML() {\n  if (!F.formattedHTML || F.formattedHTML.indexOf('Upload or pull') !== -1) {\n    setStatus('Forge your manuscript first.', 'err'); return;\n  }\n  var title  = document.getElementById('book-title').value  || 'manuscript';\n  var author = document.getElementById('book-author').value || 'Author';\n  var fullHTML = '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>' + escHtml(title) + '<\/title>' +\n    '<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Crimson+Text:ital,wght@0,400;0,600;1,400&#038;family=Cinzel:wght@700;900&#038;display=swap\" rel=\"stylesheet\">' +\n    '<style>' +\n    '@page{size:6in 9in;margin-top:0.75in;margin-bottom:0.75in;}' +\n    '@page:left{margin-left:0.875in;margin-right:0.5in;}' +\n    '@page:right{margin-left:0.5in;margin-right:0.875in;}' +\n    'body{font-family:\"Crimson Text\",serif;font-size:12pt;line-height:1.15;margin:0;padding:0;}' +\n    'p{margin:0 0 0.6em 0;text-indent:0.3in;}p:first-of-type{text-indent:0;}' +\n    'h1{font-family:\"Cinzel\",serif;font-size:18pt;font-weight:700;text-align:center;margin:0.5in 0 0.4in 0;page-break-after:avoid;}' +\n    'h2{font-family:\"Cinzel\",serif;font-size:13pt;font-weight:700;text-align:center;margin:0.3in 0;}' +\n    'a{color:inherit;text-decoration:none;}' +\n    '.title-page{page-break-after:always;text-align:center;padding-top:2in;}' +\n    '.title-page h1{font-size:24pt;page-break-before:avoid;}' +\n    '.copyright-page{page-break-after:always;font-size:10pt;line-height:1.4;}' +\n    '.dedication{page-break-after:always;text-align:center;padding-top:2in;font-style:italic;font-size:13pt;}' +\n    '.toc-section{page-break-after:always;}' +\n    '.foreword{page-break-after:always;}' +\n    '.toc-entry{display:flex;justify-content:space-between;border-bottom:1px dotted rgba(0,0,0,.2);padding:4px 0;font-size:11pt;}' +\n    '.chapter{page-break-before:always;page-break-after:always;}' +\n    '.rebellion-brand{background:#f0ece4;border-left:3px solid #c9a84c;padding:10px 14px;margin:14px 0;font-size:10pt;}' +\n    '@media print{.chapter{page-break-before:always;page-break-after:always;}.title-page,.copyright-page,.dedication,.toc-section,.foreword{page-break-after:always;}}' +\n    '<\/style><\/head><body>' + F.formattedHTML + '<\/body><\/html>';\n  var blob = new Blob([fullHTML], { type: 'text\/html' });\n  var a = document.createElement('a');\n  a.href = URL.createObjectURL(blob);\n  a.download = 'TWAIN-' + title.replace(\/\\s+\/g, '-') + '.html';\n  a.click();\n  setStatus('HTML exported. Open in Chrome \u2192 Print \u2192 Save as PDF. Paper: 6\u00d79in, margins: None.', 'ok');\n  addMsg('HTML exported with KDP-correct formatting.<br><br><strong>To create your PDF:<\/strong><br>1. Open the HTML file in Chrome<br>2. File \u2192 Print (Ctrl+P)<br>3. Destination \u2192 Save as PDF<br>4. More settings \u2192 Paper size \u2192 Custom: 6in \u00d7 9in<br>5. Margins \u2192 None (margins are built into the file)<br>6. Save<br><br>That PDF goes directly to KDP.', false);\n}\n\nfunction exportEpubBundle() {\n  var title  = document.getElementById('book-title').value  || 'Untitled';\n  var author = document.getElementById('book-author').value || 'Author';\n  if (!F.formattedHTML || F.formattedHTML.indexOf('Upload or pull') !== -1) {\n    setStatus('Forge your manuscript first.', 'err'); return;\n  }\n  var year = new Date().getFullYear();\n  var opf  = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<package xmlns=\"http:\/\/www.idpf.org\/2007\/opf\" version=\"3.0\">\\n  <metadata xmlns:dc=\"http:\/\/purl.org\/dc\/elements\/1.1\/\"><dc:title>' + title + '<\/dc:title><dc:creator>' + author + '<\/dc:creator><dc:language>en<\/dc:language><dc:publisher>T.W.A.I.N. Studio<\/dc:publisher><\/metadata>\\n  <manifest><item id=\"content\" href=\"content.xhtml\" media-type=\"application\/xhtml+xml\"\/><\/manifest>\\n  <spine><itemref idref=\"content\"\/><\/spine>\\n<\/package>';\n  var xhtml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<!DOCTYPE html>\\n<html xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\" xml:lang=\"en\">\\n<head><meta charset=\"UTF-8\"\/><title>' + title + '<\/title><\/head>\\n<body>\\n' + F.formattedHTML + '\\n<\/body>\\n<\/html>';\n  var bundle = '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>ePub Bundle: ' + title + '<\/title><style>body{font-family:monospace;background:#0a1628;color:#f0e6c8;padding:30px;}h1{color:#c9a84c;}h2{color:#8a9ab5;font-size:14px;margin-top:20px;}pre{background:#0d1e35;border:1px solid rgba(201,168,76,0.2);padding:16px;overflow:auto;font-size:11px;}.inst{background:rgba(29,158,117,0.1);border-left:3px solid #1D9E75;padding:14px;margin:16px 0;font-size:13px;line-height:1.7;}<\/style><\/head><body>' +\n    '<h1>T.W.A.I.N. ePub Bundle \u2014 ' + title + '<\/h1><div class=\"inst\"><strong>To build your ePub:<\/strong><br>1. Create a folder, save each file into it<br>2. Linux\/Mac: <code>zip -X book.epub mimetype && zip -rg book.epub META-INF content.opf content.xhtml<\/code><br>3. Windows: Use Calibre \u2014 Add Books \u2192 Convert to ePub<\/div>' +\n    '<h2>content.opf<\/h2><pre>' + opf.replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;') + '<\/pre>' +\n    '<h2>content.xhtml (preview)<\/h2><pre>' + xhtml.substring(0,1500).replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;') + '...<\/pre>' +\n    '<\/body><\/html>';\n  var blob = new Blob([bundle], { type: 'text\/html' });\n  var a = document.createElement('a');\n  a.href = URL.createObjectURL(blob);\n  a.download = 'TWAIN-ePub-Bundle-' + title.replace(\/\\s+\/g, '-') + '.html';\n  a.click();\n  setStatus('ePub bundle exported with instructions.', 'ok');\n}\n\nfunction sendToPulse() {\n  saveForge();\n  addMsg('Forge data saved. Opening The Pulse...', false);\n  setTimeout(function() { window.location.href = 'twain-pulse.html'; }, 800);\n}\n\nfunction clearForge() {\n  if (!confirm('Clear all manuscript data?')) return;\n  F.rawText = ''; F.formattedHTML = ''; F.chapters = [];\n  document.getElementById('ms-raw').value = '';\n  renderPlaceholderDocument('<div style=\"text-align:center;padding:80px 40px;font-family:Georgia,serif;color:#888;\"><h2 style=\"font-family:Georgia,serif;color:#aaa;margin-bottom:16px;\">T.W.A.I.N. Forge<\/h2><p>Upload or pull from Genesis \u2014 then hit Forge Manuscript<\/p><\/div>');\n  document.getElementById('ch-list').innerHTML = '<div style=\"font-family:Oswald,sans-serif;font-size:9px;color:var(--slate);\">No chapters detected<\/div>';\n  document.getElementById('review-cards').innerHTML = '<div style=\"text-align:center;padding:40px 20px;font-family:\\'Oswald\\',sans-serif;font-size:9px;letter-spacing:.15em;text-transform:uppercase;color:rgba(138,154,181,0.3);\">Forge your manuscript first<\/div>';\n  document.getElementById('btn-build-all').disabled  = true;\n  document.getElementById('btn-review').disabled     = true;\n  document.getElementById('build-all-btn').disabled  = true;\n  document.getElementById('review-start-btn').disabled = true;\n  updateStats(); setStatus('Cleared.', '');\n}\n\n\/\/ ================================================================\n\/\/ UTILITY\n\/\/ ================================================================\nfunction showTab(name) {\n  ['preview','raw','build','review'].forEach(function(p) {\n    var panel = document.getElementById('panel-' + p);\n    var tab   = document.getElementById('tab-'   + p);\n    if (panel) panel.style.display = p === name ? (p === 'preview' ? 'block' : 'block') : 'none';\n    if (tab) {\n      tab.className = 'ptab' +\n        (p === 'build' || p === 'review' ? ' purple-ptab' : '') +\n        (p === name ? ' active' : '');\n    }\n  });\n}\n\nfunction scrollToChapter(idx) {\n  showTab('preview');\n  setStatus('Chapter ' + (idx + 1) + ' \u2014 ' + (F.chapters[idx] ? F.chapters[idx].title : ''), 'info');\n}\n\nfunction updateStats() {\n  var raw   = document.getElementById('ms-raw').value || F.rawText || '';\n  var words = raw.trim() ? raw.trim().split(\/\\s+\/).length : 0;\n  document.getElementById('stat-words').textContent    = words.toLocaleString();\n  document.getElementById('stat-chapters').textContent = F.chapters.length;\n  var title = document.getElementById('book-title').value || F.title || 'None';\n  document.getElementById('stat-project').textContent = title.substring(0, 20);\n}\n\nfunction escHtml(str) {\n  if (!str) return '';\n  return str.replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;').replace(\/\"\/g,'&quot;');\n}\n\nfunction setProgress(pct, label) {\n  document.getElementById('progress-fill').style.width = pct + '%';\n  document.getElementById('progress-lbl').textContent  = label;\n}\nfunction showProgress(show) {\n  document.getElementById('progress-wrap').style.display = show ? 'block' : 'none';\n}\nfunction setStatus(msg, type) {\n  var el = document.getElementById('status-msg');\n  el.textContent = msg;\n  el.className   = 'status-msg' + (type ? ' ' + type : '');\n}\nfunction setLight(state, name) {\n  document.getElementById('eng-light').className    = 'eng-light ' + state;\n  document.getElementById('eng-name').textContent   = name || '';\n  document.getElementById('eng-status').textContent = state === 'busy' ? 'Processing...' : state === 'error' ? 'Check key' : 'Ready';\n}\nfunction addMsg(text, isUser, variant) {\n  var chat = document.getElementById('chat');\n  var div  = document.createElement('div');\n  div.className = isUser ? 'msg-user' : (variant === 'purple' ? 'msg-aa purple-msg' : 'msg-aa');\n  div.innerHTML = String(text).replace(\/\\n\/g, '<br>');\n  chat.appendChild(div); chat.scrollTop = chat.scrollHeight;\n}\nfunction addThinking() {\n  var chat = document.getElementById('chat');\n  var div  = document.createElement('div');\n  div.className = 'msg-think'; div.id = 'think-el';\n  div.innerHTML = '<span><\/span><span><\/span><span><\/span>';\n  chat.appendChild(div); chat.scrollTop = chat.scrollHeight;\n}\nfunction removeThinking() {\n  var el = document.getElementById('think-el'); if (el) el.remove();\n}\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>T.W.A.I.N. Forge \u2014 Rebellion Command T.W.A.I.N. FORGE Manuscript Studio Words 0 Chapters 0 Project None Load from Genesis \u26a1 AI Build All Export HTML Send to Pulse Save 01 Genesis \u203a 02 Forge \u203a 03 Pulse \u203a 04 Cover Studio \u203a 05 Command \u203a 06 Deploy Rebellion Command Ecosystem Groq API Key Powers: AI Build [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_themeisle_gutenberg_block_has_review":false,"footnotes":""},"class_list":["post-22","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/twainstudio.dev\/index.php?rest_route=\/wp\/v2\/pages\/22","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/twainstudio.dev\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/twainstudio.dev\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/twainstudio.dev\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/twainstudio.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=22"}],"version-history":[{"count":1,"href":"https:\/\/twainstudio.dev\/index.php?rest_route=\/wp\/v2\/pages\/22\/revisions"}],"predecessor-version":[{"id":23,"href":"https:\/\/twainstudio.dev\/index.php?rest_route=\/wp\/v2\/pages\/22\/revisions\/23"}],"wp:attachment":[{"href":"https:\/\/twainstudio.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=22"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}