document.addEventListener('DOMContentLoaded', function() { var CBLE = { extractLines: function(t) { return t.split('\n').map(function(l) { return l.trim(); }).filter(function(l) { return l.length > 0; }); }, groupIntoParagraphBeats: function(lines) { var beats = []; var current = ''; for (var i = 0; i < lines.length; i++) { current += (current ? ' ' : '') + lines[i]; if (current.length > 800) { beats.push(current.trim()); current = ''; } } if (current.trim()) beats.push(current.trim()); while (beats.length > 28) { var merged = []; for (var i = 0; i < beats.length; i += 2) { if (i + 1 < beats.length) merged.push(beats[i] + ' ' + beats[i + 1]); else merged.push(beats[i]); } beats = merged; } return beats; }, vocabularyMap: { "Opening Image": { commandment: "Setup", valueFrom: "life", valueTo: "calm", emotion: "peace" }, "Inciting Incident": { commandment: "Inciting Incident", valueFrom: "stable", valueTo: "unstable", emotion: "curiosity" }, "Catalyst": { commandment: "Inciting Incident", valueFrom: "safety", valueTo: "danger", emotion: "fear" }, "Debate": { commandment: "Progressive Complication", valueFrom: "hope", valueTo: "despair", emotion: "grief" }, "Turning Point": { commandment: "Progressive Complication", valueFrom: "expected", valueTo: "unexpected", emotion: "surprise" }, "Break Into Two": { commandment: "Progressive Complication", valueFrom: "despair", valueTo: "decision", emotion: "determination" }, "B Story": { commandment: "Progressive Complication", valueFrom: "order", valueTo: "chaos", emotion: "love" }, "Midpoint": { commandment: "Crisis", valueFrom: "despair", valueTo: "hope", emotion: "determination" }, "Crisis": { commandment: "Crisis", valueFrom: "certain", valueTo: "uncertain", emotion: "tension" }, "All Is Lost": { commandment: "Crisis", valueFrom: "hope", valueTo: "despair", emotion: "loss" }, "Finale": { commandment: "Climax", valueFrom: "danger", valueTo: "safety", emotion: "victory" }, "Climax": { commandment: "Climax", valueFrom: "inactive", valueTo: "active", emotion: "determination" }, "Final Image": { commandment: "Resolution", valueFrom: "safety", valueTo: "peace", emotion: "peace" }, "Resolution": { commandment: "Resolution", valueFrom: "unresolved", valueTo: "resolved", emotion: "relief" } }, inferSceneType: function(beat, i, total, prevBeat, nextBeat) { var t = beat.toLowerCase(); var p = i / Math.max(total - 1, 1); var prev = prevBeat ? prevBeat.toLowerCase() : ''; var next = nextBeat ? nextBeat.toLowerCase() : ''; // Determine arc position var arcPos = ""; if (i === 0) arcPos = "Opening"; else if (i === total - 1) arcPos = "Closing"; else if (p < 0.20) arcPos = "Early"; else if (p < 0.40) arcPos = "Building"; else if (p < 0.55) arcPos = "Middle"; else if (p < 0.75) arcPos = "Late"; else arcPos = "Final"; // Determine dramatic function (StoryGrid Commandment) var func = ""; if (i === 0) func = "Image"; else if (i === total - 1) func = "Image"; else if (/but then|suddenly|without warning|new enemy|new threat|new wave|appeared|emerged|stepped forward|protocol|orders[^.]*changed|redeploy|abandoned|left him|left us|betrayal|fake|article|headline/.test(t)) func = "Catalyst"; else if (/unexpected|surprise|instead|however|unfortunately|twist|realized the truth|then something|just then/.test(t)) func = "Turning Point"; else if (/can'?t|shouldn'?t|wasn'?t sure|didn'?t know|maybe|perhaps|what if|afraid|fear|hesitat|froze|stumbled|staggered|not like this|not again|shame|regret/.test(t)) func = "Debate"; else if (/either|or|choice|decide|dilemma|whether|if he|if she|best bad|irreconcilable|what now|how could/.test(t)) func = "Crisis"; else if (/overwhelmed|swallowed|crushed|buried|drowning|fading|too many|too strong|can'?t save|won'?t survive|dropped to one knee|barely|broken|heartbroken/.test(t)) func = "All Is Lost"; else if (/chooses|decides|acts|strikes|jumps|commits|refuses|accepts|grabs|lunges|roared|charged|slammed|threw|dove|bolted|didn'?t hesitate|without thinking|no going back/.test(t)) func = "Break Into Two"; else if (/(love|hug|embrace|kiss|hand[^.]*shoulder|arm[^.]*around)/.test(t)) func = "B Story"; else if (/(mom|dad|ma |pa |grandma|grandpa|grandson|family|together)/.test(t) && /(said|whispered|smiled|looked|watched|chuckled)/.test(t)) func = "B Story"; else if (/proud|love you|believe in you|not alone|never alone/.test(t)) func = "B Story"; else if (p > 0.35 && p < 0.65 && (/rescue|arrived|came|appeared|gate|opened|breach|breakthrough|realized|understood|finally saw|finally knew|relief/.test(t))) func = "Midpoint"; else if (p > 0.65 && (/final|last stand|last push|ultimate|climax|final battle|hold fast|hold the line|storm|thunder|coming|hunting|patient/.test(t))) func = "Finale"; else if (/result|outcome|aftermath|consequence|now that|because of|led to|silence|stood|still|quiet|settled|faded/.test(t)) func = "Resolution"; else if (p < 0.25) func = "Setup"; else if (p < 0.50) func = "Complication"; else if (p < 0.75) func = "Crisis"; else func = "Climax"; if (arcPos === "Opening") return "Opening Image"; if (arcPos === "Closing") return "Final Image"; return func; }, inferSpeaker: function(t) { var l = t.toLowerCase(); if (t.match(/\b(tao|his|he)\b.*\b(thought|felt|realized|knew|believed|understood|saw)\b/)) return 'tao'; if (t.match(/\b(she|her|blossom)\b.*\b(knew|understood|decided|chose|refused|felt)\b/)) return 'blossom'; if (t.match(/[""][^""]*[""]/) && (l.includes('tao') || l.includes(' my ') || l.includes(' me ') || l.includes(" i "))) return 'tao'; if (l.includes('tif') && !l.includes('tao') && !l.includes('beautiful') && !l.includes('tify')) return 'tif'; if (l.includes('flip') && !l.includes('tao')) return 'flip'; if (l.includes('zip')) return 'zip'; if (l.includes('li wei') || l.includes('liwei')) return 'liwei'; if (l.includes('tao')) return 'tao'; if (l.includes('magpie')) return 'magpie'; if (l.includes('tinting')) return 'tinting'; if (l.match(/\bmy\b|\bme\b|\bi\b/) && l.length < 300) return 'tao'; return 'narrator'; }, inferEffect: function(t) { var lower = t.toLowerCase(); // Value shifts from StoryGrid if (/died|killed|murdered|sacrificed|corpse|last breath/.test(lower)) return '"Life hung in the balance."'; if (/threatened|attacked|ambushed|hunted|trapped|danger|not safe/.test(lower)) return '"Nowhere was safe now."'; if (/chosen|summoned|called upon|assigned|selected|singled out/.test(lower)) return '"Chosen. For better or worse."'; if (/failed|lost everything|gave up|surrendered|crushed|despair|hopeless/.test(lower)) return '"Hope died in that moment."'; if (/hope|spark|maybe|just maybe|chance|possibility|dawn|new day/.test(lower)) return '"A spark in the darkness."'; if (/betrayed|abandoned|rejected|humiliated|scorned|liar|fake|wasn'?t real/.test(lower)) return '"Betrayal cut deeper than any blade."'; if (/love|connection|together|united|joined|embrace|kiss|held|arms around/.test(lower)) return '"Connection, finally."'; if (/collapsed|erupted|exploded|shattered|unraveled|chaos|madness|mayhem/.test(lower)) return '"The world unraveled."'; if (/order|plan|strategy|formation|organized|gathered|assembled/.test(lower)) return '"A plan emerged."'; if (/mastered|unlocked|awakened|transformed|leveled up|stronger|power/.test(lower)) return '"Power awakened."'; if (/understood|finally saw|realized the truth|learned the lesson|wisdom/.test(lower)) return '"The truth changed everything."'; if (/not alone|never again|stood together|family|team|crew|united/.test(lower)) return '"Not alone. Never again."'; if (/charged|roared|faced|confronted|stood up|defied|no more running|courage/.test(lower)) return '"Courage answered the call."'; // Chapter-specific if (lower.includes('flowers') && lower.includes('chocolate')) return '"Flowers. Chocolate. Legendary."'; if (lower.includes('rappel') || lower.includes('midnight')) return '"A girl doesn\'t rappel down walls for a handshake."'; if (lower.includes('article') || lower.includes('headline') || lower.includes('lucan')) return '"My heart sank."'; if (lower.includes('outgrow') || lower.includes('first crush')) return '"Sometimes you outgrow your first crush."'; if (lower.includes('girl stuff') || lower.includes('hardest fight')) return '"Girl stuff. Hardest fight you\'ll ever face."'; if (lower.includes('pancake heart') || lower.includes('steamroller')) return '"She ran over my heart with a steamroller."'; if (lower.includes('gonna hurt') || lower.includes('century')) return '"Gonna hurt for a while, kiddo."'; if (lower.includes('no do-overs') || lower.includes('someone pays')) return '"No do-overs. Someone pays the price."'; if (lower.includes('storm isn\'t waiting')) return '"The storm isn\'t waiting."'; if (lower.includes('colliding') || lower.includes('misstep')) return '"One misstep. Someone gets hurt."'; if (lower.includes('light changed') || lower.includes('sunlight')) return '"Something was terribly wrong."'; if (lower.includes('coiling') || lower.includes('defied weather')) return '"The clouds were coiling, twisting."'; if (lower.includes('that\'s not thunder')) return '"That\'s not thunder."'; if (lower.includes('laughter') && lower.includes('devoid of warmth')) return '"Cold laughter from nowhere."'; if (lower.includes('stay close')) return '"Stay close."'; if (lower.includes('coming for him') || lower.includes('trap centuries')) return '"This time, it was coming for him."'; if (lower.includes('learning the hard way')) return '"Learning the hard way."'; if (lower.includes('we love you') && lower.includes('grandson')) return '"We love you, grandson."'; if (lower.includes('note') && lower.includes('raccoon')) return '"Raccoon was supposed to give you the note."'; if (lower.includes('beat me') && lower.includes('drill')) return '"Beat me in this drill and I\'ll tell you."'; return '"The value shifted."'; }, generateStoryEvent: function(t, type) { var e = { "Opening Image": "The world before", "Final Image": "After the end", "Catalyst": "Everything changes", "Debate": "The choice", "Break Into Two": "The journey", "B Story": "Inner needs", "Midpoint": "Turning point", "All Is Lost": "Darkest moment", "Finale": "Climax", "Turning Point": "The unexpected complication", "Crisis": "The best bad choice", "Climax": "The decision and action", "Resolution": "The outcome" }; return e[type] || type; }, getPlaceholderImage: function(i) { var imgs = ['https://koru-imprint.com/wp-content/uploads/2026/03/9.png', 'https://koru-imprint.com/wp-content/uploads/2026/03/8.png', 'https://koru-imprint.com/wp-content/uploads/2026/03/2.png']; return imgs[i % imgs.length]; }, sceneColors: { "Opening Image": "#6f6f6f", "Final Image": "#6f6f6f", "Catalyst": "#ff6b35", "Inciting Incident": "#ff6b35", "Debate": "#9b59b6", "Turning Point": "#9b59b6", "Break Into Two": "#3498db", "Complication": "#9b59b6", "B Story": "#e74c6f", "Midpoint": "#ffd700", "All Is Lost": "#ff4444", "Crisis": "#ff4444", "Finale": "#2ecc71", "Climax": "#ffd700", "Setup": "#6f6f6f", "Resolution": "#2ecc71" }, assignVocabulary: function(beats) { var self = this, result = []; for (var i = 0; i < beats.length; i++) { var prevBeat = i > 0 ? beats[i-1] : null; var nextBeat = i < beats.length-1 ? beats[i+1] : null; var type = self.inferSceneType(beats[i], i, beats.length, prevBeat, nextBeat); var vocab = self.vocabularyMap[type] || { commandment: "Beat", valueFrom: "?", valueTo: "?", emotion: "neutral" }; result.push({ beat: i + 1, sceneType: type, commandment: vocab.commandment || "Beat", valueFrom: vocab.valueFrom || "?", valueTo: vocab.valueTo || "?", emotion: vocab.emotion || "neutral", speaker: self.inferSpeaker(beats[i]), cause: beats[i], effect: self.inferEffect(beats[i]), storyEvent: self.generateStoryEvent(beats[i], type), imageUrl: self.getPlaceholderImage(i) }); } return result; }, renderFull: function(data) { if (!data || !data.length) return '

No beats

'; var h = ''; for (var i = 0; i < data.length; i++) { var d = data[i], c = this.sceneColors[d.sceneType] || "#6f6f6f", sc = 'speaker-' + (d.speaker || 'narrator'); h += '
● BEAT ' + (i + 1) + ' OF ' + data.length + '
'; h += '
' + d.sceneType + '
'; h += '
' + d.cause + ''; h += '
~ ~ ~ ~ ~
' + d.effect + '
'; h += '● ' + (d.speaker || 'NARRATOR').toUpperCase() + '
'; h += '
'; } var css = '@import url("https://fonts.googleapis.com/css2?family=Special+Elite&display=swap");'; css += '*{margin:0;padding:0;box-sizing:border-box;}body{background:#000;font-family:"Special Elite",monospace;}'; css += '.card{display:flex;flex-direction:column;background:#0d0d0d;width:100%;min-height:100vh;overflow:hidden;opacity:1;transform:translateY(0);}'; css += '.card-number{color:rgba(255,255,255,0.06);font-size:0.7rem;letter-spacing:4px;text-align:center;padding:12px 0 8px 0;background:#0d0d0d;border-bottom:1px solid rgba(255,255,255,0.03);font-family:"Special Elite",monospace;flex-shrink:0;}'; css += '.meta{color:rgba(255,255,255,0.04);font-size:0.6rem;text-align:center;padding:4px 0;font-family:"Special Elite",monospace;flex-shrink:0;}'; css += '.bubble{position:relative;background:linear-gradient(180deg,#fff 0%,#fcfcfc 45%,#f3f3f3 100%);border:4px solid #111;border-radius:30px 30px 24px 30px;padding:22px 26px;font-size:1rem;color:#1a1a1a;line-height:1.6;box-shadow:0 8px 20px rgba(0,0,0,.35);height:50vh;max-height:50vh;min-height:50vh;width:calc(100% - 28px);margin:0 14px;z-index:20;overflow:hidden;display:flex;flex-direction:column;font-family:"Special Elite",monospace;flex-shrink:0;}'; css += '.bubble::after{content:"";position:absolute;bottom:-20px;left:18%;border-left:20px solid transparent;border-right:8px solid transparent;border-top:22px solid #fff;z-index:30;}'; css += '.bubble::before{content:"";position:absolute;bottom:-25px;left:calc(18% - 3px);border-left:23px solid transparent;border-right:10px solid transparent;border-top:27px solid #111;z-index:29;}'; css += '.scroll{flex:1;overflow-y:auto;min-height:0;}.cause{display:block;font-weight:400;font-size:1rem;line-height:1.6;outline:none;cursor:text;}'; css += '.effect{display:block;font-weight:700;font-style:italic;font-size:1rem;line-height:1.6;outline:none;cursor:text;}'; css += '.divider{text-align:center;color:rgba(0,0,0,0.06);font-size:0.7rem;padding:4px 0;}'; css += '.speaker{display:inline-block;font-size:0.7rem;font-weight:700;text-transform:uppercase;padding:3px 16px;border-radius:10px;margin-top:6px;}'; css += '.speaker-tif{color:#e74c6f;background:rgba(231,76,111,0.1);}.speaker-flip{color:#f39c12;background:rgba(243,156,18,0.1);}.speaker-zip{color:#2ecc71;background:rgba(46,204,113,0.1);}.speaker-liwei{color:#3498db;background:rgba(52,152,219,0.1);}.speaker-tao{color:#e67e22;background:rgba(230,126,34,0.1);}.speaker-magpie{color:#9b59b6;background:rgba(155,89,182,0.1);}.speaker-narrator{color:#CD7F32;background:rgba(205,127,50,0.1);}'; css += '.panel{width:100%;flex:1;overflow:hidden;background:#1a120b;}.panel img{width:100%;height:100%;object-fit:cover;}'; css += '[contenteditable="true"]:hover{outline:2px dashed rgba(205,127,50,0.4);outline-offset:2px;}'; css += '[contenteditable="true"]:focus{outline:2px solid #CD7F32;outline-offset:2px;}'; css += '@media(max-width:600px){.bubble{height:40vh;max-height:40vh;min-height:40vh;padding:16px;font-size:0.9rem;}}'; return '
' + h + '
'; }, process: function(text) { var lines = this.extractLines(text); var beats = this.groupIntoParagraphBeats(lines); var data = this.assignVocabulary(beats); var html = this.renderFull(data); return { lines: lines.length, beats: data.length, data: data, html: html }; } }; var inp = document.getElementById('cble-input'); var out = document.getElementById('cble-output'); var log = document.getElementById('cble-log'); var pBtn = document.getElementById('cble-process-btn'); var cBtn = document.getElementById('cble-clear-btn'); var sBtn = document.getElementById('cble-sample-btn'); var dBtn = document.getElementById('cble-download-btn'); var rBtn = document.getElementById('cble-replace-btn'); if (!inp || !out || !pBtn) return; function addLog(m, t) { if (!log) return; var colors = { success: '#2ecc71', error: '#e74c3c', info: '#3498db' }; var d = document.createElement('div'); d.innerHTML = '' + m + ''; log.appendChild(d); } function updateStats(l, b) { ['cble-lines', 'cble-beats', 'cble-cards', 'cble-images'].forEach(function(id) { var e = document.getElementById(id); if (e) e.textContent = (id === 'cble-lines') ? l : b; }); } pBtn.onclick = function() { var t = inp.value.trim(); if (!t) { addLog('No transcript', 'error'); return; } addLog('Processing...', 'info'); pBtn.disabled = true; try { var r = CBLE.process(t); window.cble_result = r; updateStats(r.lines, r.beats); var h = '
'; for (var i = 0; i < r.data.length; i++) { var d = r.data[i]; h += '
'; h += 'BEAT ' + (i+1) + ' — ' + d.sceneType + ' (' + d.speaker.toUpperCase() + ')

'; h += '
'; h += '
'; h += '
'; h += '

'; h += '
'; h += ''; h += '
'; } h += '
'; out.innerHTML = h; out.style.cssText = 'background:#000;max-height:70vh;overflow-y:auto;padding:0;'; dBtn.style.display = 'inline-block'; rBtn.style.display = 'inline-block'; addLog('Done. ' + r.beats + ' beats.', 'success'); } catch (e) { addLog('Error: ' + e.message, 'error'); } pBtn.disabled = false; }; dBtn.onclick = function() { if (!window.cble_result) return; var imgs = out.querySelectorAll('.img'); var causes = out.querySelectorAll('.ce'); var effects = out.querySelectorAll('.ef'); imgs.forEach(function(el, i) { window.cble_result.data[i].imageUrl = el.value; }); causes.forEach(function(el, i) { window.cble_result.data[i].cause = el.value; }); effects.forEach(function(el, i) { window.cble_result.data[i].effect = el.value; }); var html = CBLE.renderFull(window.cble_result.data); var tempDiv = document.createElement('div'); tempDiv.innerHTML = html; var styleTag = tempDiv.querySelector('style'); var css = styleTag ? styleTag.textContent : ''; css = css.replace(/opacity:0/g, 'opacity:1'); var cards = tempDiv.querySelectorAll('.card'); var newHtml = ''; cards.forEach(function(card) { newHtml += card.outerHTML; }); var fullHtml = 'Comic Beat Layout' + newHtml + ''; var b = new Blob([fullHtml], { type: 'text/html' }); var a = document.createElement('a'); a.href = URL.createObjectURL(b); a.download = 'comic-layout.html'; a.click(); addLog('Downloaded.', 'success'); }; rBtn.onclick = function() { if (!window.cble_result) return; rBtn.disabled = true; fetch(ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=cble_generate_images&nonce=' + cble_ajax.nonce + '&beats=' + encodeURIComponent(JSON.stringify(window.cble_result.data)) }) .then(function(r) { return r.json(); }).then(function(r) { if (r.success) { window.cble_result.data = r.data.beats; window.cble_result.html = CBLE.renderFull(r.data.beats); out.innerHTML = window.cble_result.html; } }).finally(function() { rBtn.disabled = false; }); }; cBtn.onclick = function() { inp.value = ''; out.innerHTML = 'Ready.'; out.style.cssText = 'background:#0d0d0d;color:#aaa;max-height:500px;padding:20px;font-family:monospace;'; log.innerHTML = ''; updateStats(0, 0); dBtn.style.display = 'none'; rBtn.style.display = 'none'; window.cble_result = null; }; sBtn.onclick = function() { cBtn.onclick(); inp.value = "CHAPTER 1: SOS IN THE NIGHT\n\nThe broadcast shimmered downward.\nTif lay beneath a tangle of antenna cables.\nHer cracked lens caught the galaxies.\nShe flicked her mic.\nA sharp ping sliced through the feed.\nHer smile froze.\nTif ripped out the power cell.\nShe swung onto her hover-bike.\nTif frowned. \"Analog in space?\""; }; addLog('Ready.', 'info'); }); https://koru-imprint.com/wp-sitemap-posts-post-1.xmlhttps://koru-imprint.com/wp-sitemap-posts-page-1.xmlhttps://koru-imprint.com/wp-sitemap-taxonomies-category-1.xmlhttps://koru-imprint.com/wp-sitemap-users-1.xml