if (window.__emojiStudioInit) return [];
window.__emojiStudioInit = true;
/* ── 1. Build the picker and attach it to
── */
const picker = document.createElement('div');
picker.id = 'emoji-overlay-picker';
picker.innerHTML = `
No emojis yet, ask me to make one!
`;
document.body.appendChild(picker);
/* Tooltip element — also on body, fixed */
const tooltip = document.createElement('div');
tooltip.className = 'eop-tip';
document.body.appendChild(tooltip);
/* ── 2. Helpers ── */
window.__eopOpen = false;
function openPicker() {
const btn = document.getElementById('emoji-pick-btn');
if (!btn) return;
const r = btn.getBoundingClientRect();
const pickerW = 280;
let left = r.right - pickerW;
if (left < 8) left = 8;
const bottom = window.innerHeight - r.top + 8;
picker.style.left = left + 'px';
picker.style.bottom = bottom + 'px';
picker.style.top = 'auto';
picker.classList.add('eop-open');
window.__eopOpen = true;
}
function closePicker() {
picker.classList.remove('eop-open');
window.__eopOpen = false;
tooltip.style.display = 'none';
}
window.__syncComposer = function() {
const composer = document.getElementById('composer');
const ta = document.querySelector('#hidden-txt textarea, #hidden-txt input');
if (!composer || !ta) return;
let value = '';
composer.childNodes.forEach(node => {
if (node.nodeType === Node.TEXT_NODE) {
value += node.textContent;
} else if (node.tagName === 'IMG') {
value += '' + (node.dataset.emojiName || '') + '';
} else {
value += node.textContent || '';
}
});
ta.value = value;
ta.dispatchEvent(new Event('input', { bubbles: true }));
};
function insertEmoji(name, src) {
const composer = document.getElementById('composer');
if (!composer) return;
composer.focus();
const img = document.createElement('img');
img.src = src;
img.alt = '' + name + '';
img.className = 'ei';
img.dataset.emojiName = name;
img.draggable = false;
const sel = window.getSelection();
if (sel && sel.rangeCount) {
const range = sel.getRangeAt(0);
if (composer.contains(range.commonAncestorContainer)) {
range.deleteContents();
range.insertNode(img);
range.setStartAfter(img);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
closePicker();
window.__syncComposer();
return;
}
}
composer.appendChild(img);
closePicker();
window.__syncComposer();
}
/* ── 3. Render picker grid from inventory ── */
window.__renderEmojiPicker = function(inventory) {
const grid = document.getElementById('eop-grid');
const empty = document.getElementById('eop-empty');
if (!grid || !empty) return;
grid.innerHTML = '';
const items = (inventory || []).filter(it => it.name && it.image_b64);
if (items.length === 0) {
grid.style.display = 'none';
empty.style.display = 'block';
} else {
grid.style.display = 'grid';
empty.style.display = 'none';
items.forEach(it => {
const btn = document.createElement('button');
btn.className = 'eop-btn';
btn.type = 'button';
const img = document.createElement('img');
img.src = 'data:image/png;base64,' + it.image_b64;
img.alt = it.name;
btn.appendChild(img);
const label = it.name.slice(0, 28);
btn.addEventListener('mouseenter', e => {
tooltip.textContent = label;
tooltip.style.display = 'block';
const br = btn.getBoundingClientRect();
tooltip.style.left = (br.left + br.width / 2 - tooltip.offsetWidth / 2) + 'px';
tooltip.style.top = (br.top - tooltip.offsetHeight - 6) + 'px';
});
btn.addEventListener('mouseleave', () => { tooltip.style.display = 'none'; });
btn.addEventListener('click', e => {
e.stopPropagation();
tooltip.style.display = 'none';
insertEmoji(it.name, img.src);
});
grid.appendChild(btn);
});
}
};
/* ── 4. Wire the toggle button (poll until it exists) ── */
function wireToggleBtn() {
const btn = document.getElementById('emoji-pick-btn');
if (!btn) { setTimeout(wireToggleBtn, 200); return; }
/* Fix icon centering: the button itself and its Gradio wrapper div */
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.lineHeight = '1';
if (btn.parentElement) {
btn.parentElement.style.display = 'flex';
btn.parentElement.style.alignItems = 'center';
btn.parentElement.style.justifyContent = 'center';
btn.parentElement.style.padding = '0';
}
btn.addEventListener('click', e => {
e.stopPropagation();
if (window.__eopOpen) { closePicker(); } else { openPicker(); }
});
}
wireToggleBtn();
/* 4b. Watch picker-sync container for inventory updates */
function watchPickerSync() {
const container = document.getElementById('picker-sync');
if (!container) { setTimeout(watchPickerSync, 200); return; }
function apply() {
const target = container.querySelector('#picker-sync-data');
if (!target) return;
try {
const raw = target.getAttribute('data-inv').replace(/"/g, '"');
const inv = JSON.parse(raw);
if (window.__renderEmojiPicker) window.__renderEmojiPicker(inv);
} catch(e) { console.error('picker sync parse error', e); }
}
const observer = new MutationObserver(apply);
observer.observe(container, { childList: true, subtree: true });
apply();
}
watchPickerSync();
function findDirectChild(el, ancestor) {
while (el && el.parentElement !== ancestor) {
el = el.parentElement;
}
return el;
}
function fixTopRowLayout() {
const composer = document.getElementById('composer');
const emojiBtn = document.getElementById('emoji-pick-btn');
const topRow = document.getElementById('top-row');
if (!composer || !emojiBtn || !topRow) { setTimeout(fixTopRowLayout, 200); return; }
const composerWrap = findDirectChild(composer, topRow);
const emojiWrap = findDirectChild(emojiBtn, topRow);
if (composerWrap) {
composerWrap.style.flex = '1 1 auto';
composerWrap.style.width = 'auto';
composerWrap.style.minWidth = '0';
}
if (emojiWrap) {
emojiWrap.style.flex = '0 0 44px';
emojiWrap.style.width = '44px';
emojiWrap.style.display = 'flex';
emojiWrap.style.alignItems = 'center';
emojiWrap.style.justifyContent = 'center';
emojiWrap.style.margin = '0';
emojiWrap.style.padding = '0';
}
}
fixTopRowLayout();
/* ── 5. Close picker on outside click ── */
document.addEventListener('click', e => {
if (!window.__eopOpen) return;
const btn = document.getElementById('emoji-pick-btn');
if (picker.contains(e.target)) return;
if (btn && btn.contains(e.target)) return;
closePicker();
}, true);
/* ── 6. Close picker button ── */
document.addEventListener('click', e => {
if (e.target && e.target.id === 'eop-close') closePicker();
});
/* ── 7. Composer: sync on input, Enter to send ── */
function wireComposer() {
const composer = document.getElementById('composer');
if (!composer) { setTimeout(wireComposer, 200); return; }
composer.addEventListener('input', window.__syncComposer);
composer.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
window.__syncComposer();
const send = document.getElementById('send-btn');
if (send) {
send.click();
setTimeout(() => {
composer.innerHTML = '';
window.__syncComposer();
}, 60);
}
}
});
composer.addEventListener('paste', e => {
e.preventDefault();
const text = (e.clipboardData || window.clipboardData).getData('text');
document.execCommand('insertText', false, text);
});
}
wireComposer();
/* ── 8. Reposition picker on window resize/scroll ── */
window.addEventListener('resize', () => { if (window.__eopOpen) openPicker(); });
window.addEventListener('scroll', () => { if (window.__eopOpen) openPicker(); }, true);
/* ── 9. Open and close info overlay ── */
function wireInfoOverlay() {
const btn = document.getElementById('info-btn');
const overlay = document.getElementById('info-overlay');
const close = document.getElementById('info-close');
if (!btn || !overlay || !close) { setTimeout(wireInfoOverlay, 200); return; }
btn.addEventListener('click', () => overlay.classList.add('open'));
close.addEventListener('click', () => overlay.classList.remove('open'));
}
wireInfoOverlay();
/* ── Chat scroll and scrollbar management ── */
function setupChatScroll() {
const chatDisplay = document.getElementById('chat-display');
if (!chatDisplay) { setTimeout(setupChatScroll, 200); return; }
function scrollToBottom() {
const wrap = document.getElementById('chat-scroll-wrap');
if (!wrap) return;
wrap.scrollTop = wrap.scrollHeight;
}
const observer = new MutationObserver(() => {
requestAnimationFrame(() => requestAnimationFrame(scrollToBottom));
});
observer.observe(chatDisplay, { childList: true, subtree: true });
window.addEventListener('resize', scrollToBottom);
}
setupChatScroll();
const observer = new MutationObserver(() => {
document.querySelectorAll('.progress-text').forEach(el => {
el.style.setProperty('display', 'none', 'important');
});
});
observer.observe(document.body, { childList: true, subtree: true });