() => { const applyTheme = dark => { document.body.classList.toggle('dark', dark); const frame = document.querySelector('iframe[title="Drama 3 Workflow"]'); try { frame?.contentDocument?.body.classList.toggle('dark', dark); } catch {} }; try { const saved = localStorage.getItem('drama3:theme'); if (saved) applyTheme(saved === 'dark'); } catch {} window.dramaToggleTheme = () => { const dark = !document.body.classList.contains('dark'); applyTheme(dark); try { localStorage.setItem('drama3:theme', dark ? 'dark' : 'light'); } catch {} }; window.dramaInsert = (text, tag, kind) => { const field = document.querySelector('#script textarea'); const start = field?.selectionStart ?? text.length; const end = field?.selectionEnd ?? start; const selected = text.slice(start, end); let snippet = kind === 'scope' ? `<${tag}>${selected || 'your words here'}` : `[${tag}]`; if (kind === 'mood') return snippet + ' ' + text.replace(/^\[[^\]]+\]\s*/, ''); const before = text.slice(0, start), after = text.slice(end); return before + (before && !/\s$/.test(before) ? ' ' : '') + snippet + (after && !/^\s/.test(after) ? ' ' : '') + after; }; const escape = text => text.replace(/&/g, '&').replace(//g, '>'); const highlight = text => { let result = escape(text); result = result.replace(/(^|\n)(Direction:|Line:)/g, '$1$2'); result = result.replace(/<(\/?[A-Za-z][\w-]*)>/g, '<$1>'); return result.replace(/\[([^\[\]\n]*)\]/g, (match, inner) => `${match}`); }; const installMirror = () => { const field = document.querySelector('#script textarea'); if (!field || field.dataset.dramaMirror) return; field.dataset.dramaMirror = 'true'; const wrapper = field.parentElement; const mirror = document.createElement('div'); mirror.className = 'drama-script-mirror'; mirror.setAttribute('aria-hidden', 'true'); wrapper.style.position = 'relative'; wrapper.append(mirror); field.classList.add('drama-highlighted'); const render = () => { const style = getComputedStyle(field); Object.assign(mirror.style, {left:field.offsetLeft+'px',top:field.offsetTop+'px',width:field.offsetWidth+'px',height:field.offsetHeight+'px', fontFamily:style.fontFamily,fontSize:style.fontSize,fontWeight:style.fontWeight,lineHeight:style.lineHeight,letterSpacing:style.letterSpacing, padding:style.padding,border:style.borderWidth+' solid transparent',borderRadius:style.borderRadius}); mirror.innerHTML = highlight(field.value) + '\n\u200b'; mirror.scrollTop = field.scrollTop; }; field.addEventListener('input', render); field.addEventListener('scroll', render); new ResizeObserver(render).observe(field); // Gradio can replace the textbox value without firing a DOM input event. let previous = field.value; const tick = setInterval(() => { if (!field.isConnected) { clearInterval(tick); return; } if (previous !== field.value) { previous = field.value; render(); } }, 100); render(); }; new MutationObserver(installMirror).observe(document.body, {childList:true,subtree:true}); installMirror(); document.addEventListener('keydown', event => { if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { event.preventDefault(); document.querySelector(event.target.closest('#stage-note') ? '#retake' : '#perform')?.click(); } }); }