document.addEventListener('DOMContentLoaded', () => {
const emojiConverter = new EmojiConvertor();
emojiConverter.replace_mode = 'img';
emojiConverter.img_sets.apple.path = 'https://cdn.jsdelivr.net/npm/emoji-datasource-apple@15.0.1/img/apple/64/';
const emojis = [
'😀', '😂', '😍', '🥲', '🫦', '🤡', '😳', '😩', '🥺', '😒', '😏', '😜', '🤪', '😵💫', '🙃', '😮', '🤔', '😎', '😭', '😡', '😱', '👍', '👎', '❤️', '💔', '🥀', '🔥', '💧', '⚡️', '⭐️', '🌍', '🍕', '🚀', '🤖',
'👻', '💀', '💩', '👾', '👽', '👶', '💂♂️', '👑', '💎', '💰', '💡', '💻', '🧠', '👀', '👅', '👄', '👙', '👋', '💪', '🙏', '🎉', '🎁', '💯', '✅', '❌',
'🐶', '🐱', '🐭', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐸', '🐵', '🐔', '🐧', '🐦', '🐤', '🦋', '🐢', '🐍',
'🐙', '🐳', '🦄', '🐝', '🐞', '🐌', '⚽️', '🏀', '🏈', '⚾️', '🎾', '🎸', '🎹', '🎺', '🎻', '🎲', '🎯', '🚗', '✈️', '⛵️',
'🍎', '🍌', '🍓', '🍇', '🍉', '🍔', '🍟', '🌮', '🍣', '🍦', '🍩', '🍪', '☕️', '🍺', '🌞', '🌕', '🌚', '☁️', '🌈', '🌊', '🌋'
];
emojis.push(...['🤩','🥳','😴','🤒','🤕','🤧','🥶','🥵','🤯','😇','🫠','🫣','🫢','🤫','🫡','😐','😶','🙄','😬','🤥','🤤','🤮','🤌','👏','👐','✌️','🤟','🖖','👌','👉','👈','👇','👆','✊','🤝','🫶','💅','🦵','🦶','🫵','🎩','🧢','🥽','🕶️','👗','👔','👟','🥾','🧤','🧣','👜','🎒','⌚️','📱','🖥️','🖨️','🎧','📷','🎥','📺','📻','🕹️','💿','☎️','🔋','🔌','💡','🪑','🛏️','🚿','🧼','🧽','🧻','🚽','🪥','🧹','🧰','🔧','🔨','🪚','🔩','⚙️','🧲','🪜','🪛','🧪','🧬','🔭','🔬','🧫','⚗️','🧮','📦','✉️','📫','📌','🗂️','🗃️','🗑️','🧷','🧵','🪡','🧶','🧱','⚖️','🔒','🔑','🗝️','🚪','🪟','🪞','🖼️']);
emojis.push(...['🌵','🌲','🌸','🌼','🍁','🍂','🍃','🌶️','🥑','🍜','🍱','🧇','🥞','🧁','🍰','🍫','🍿','🍷','🍸','🍹','🥤','🥐','🥯','🥨','🥖','🧀','🥩','🍗','🍤','🍙','🍛','🍝','🌯','🥙','🥗','🦓','🦒','🦚','🦜','🦢','🦩','🦨','🦔','🦦','🦫','🦥','🦘','🦧','🦎','🦂','🦀','🦑','🦞','🦐','🦈','🚲','🛴','🛵','🏎️','🚑','🚒','🚜','🚂','🚆','🚁','🛰️','🛸','⛽️','🛞','🥊','🥋','🥅','🏓','🏸','🥏','🛼','⛷️','🏂','🏹','🤿','🏊♂️','♻️','⚠️','🚸','⛔️','❗️','❓','♾️','🔁','🔀','🔂','🆕','🆗','🆒','🆙','🅿️','🏁','🎌','🏳️🌈','🌧️','⛈️','🌪️','❄️','🌨️','🌤️','🌥️','🌦️','🌫️','🌙','🌟','✨','☄️','⏰','⏳','⌛️','📅','📆','🏖️','🏔️','🏕️','🏛️','🏟️','🏗️','🗽','🗼','🗿','🛕','⛩️','🕌','⛪️']);
const selectionBox1 = document.getElementById('selection-1');
const selectionBox2 = document.getElementById('selection-2');
const selectionBox3 = document.getElementById('selection-3');
const mergeButton = document.getElementById('merge-button');
const resultContainer = document.getElementById('result-container');
const resultWrapper = document.getElementById('result-image-wrapper');
const loader = document.getElementById('loader');
const randomButton = document.getElementById('random-button');
const postCommentButton = document.getElementById('post-comment-button');
const drawer = document.getElementById('emoji-drawer');
const drawerGrid = document.getElementById('drawer-grid');
const overlay = document.getElementById('drawer-overlay');
let sentinel, io;
let selectedEmoji1 = null;
let selectedEmoji2 = null;
let selectedEmoji3 = null;
let selectedEmoji1Url = null;
let selectedEmoji2Url = null;
let selectedEmoji3Url = null;
let activePicker = null;
let currentImageUrl = null;
let currentCreativeDirection = null;
const isMobile = () => window.matchMedia("(max-width: 768px)").matches;
const EMOJIS_PER_PAGE = 60;
let currentPage = 0, allEmojis = [], filteredEmojis = [], isLoading = false, dataLoaded = false;
function populateDrawer() {
emojis.forEach(emoji => {
const emojiContainer = document.createElement('div');
emojiContainer.className = 'emoji';
emojiContainer.dataset.emoji = emoji;
emojiContainer.innerHTML = emojiConverter.replace_unified(emoji);
drawerGrid.appendChild(emojiContainer);
});
}
function openDrawer(pickerId) {
activePicker = pickerId;
drawer.classList.add('open');
overlay.classList.remove('hidden');
loadEmojiData().then(() => setupObserver());
}
function closeDrawer() {
drawer.classList.remove('open');
overlay.classList.add('hidden');
activePicker = null;
}
function handleEmojiSelection(e) {
const target = e.target.closest('.emoji');
if (!target || !activePicker) return;
const emoji = target.dataset.emoji;
const emojiHtml = emojiConverter.replace_unified(emoji);
const imgUrl = emojiHtml.match(/src="([^"]*)"/)[1];
if (activePicker === 1) {
selectedEmoji1 = emoji;
selectedEmoji1Url = imgUrl;
selectionBox1.innerHTML = `<img src="${imgUrl}" alt="${emoji}">`;
selectionBox1.classList.add('filled');
} else if (activePicker === 2) {
selectedEmoji2 = emoji;
selectedEmoji2Url = imgUrl;
selectionBox2.innerHTML = `<img src="${imgUrl}" alt="${emoji}">`;
selectionBox2.classList.add('filled');
} else {
selectedEmoji3 = emoji;
selectedEmoji3Url = imgUrl;
selectionBox3.innerHTML = `<img src="${imgUrl}" alt="${emoji}">`;
selectionBox3.classList.add('filled');
}
checkSelections();
closeDrawer();
document.getElementById('drawer-search').value = ''; // Clear the search input
handleDrawerSearch(); // Re-filter and display all emojis if there was a search
}
function checkSelections() {
if (selectedEmoji1 && selectedEmoji2 && selectedEmoji3) {
mergeButton.disabled = false;
} else {
mergeButton.disabled = true;
}
}
function updateStateAndURL() {
if (!selectedEmoji1 || !selectedEmoji2 || !selectedEmoji3 || !currentImageUrl) return;
const params = new URLSearchParams();
params.set('e1', selectedEmoji1);
params.set('e2', selectedEmoji2);
params.set('e3', selectedEmoji3);
params.set('img', currentImageUrl);
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.pushState({ path: newUrl }, '', newUrl);
}
function initializeState() {
const params = new URLSearchParams(window.location.search);
const e1 = params.get('e1');
const e2 = params.get('e2');
const e3 = params.get('e3');
const imgUrl = params.get('img');
// 1. URL params have highest precedence
if (e1 && e2 && e3 && imgUrl) {
loadState(e1, e2, e3, imgUrl);
return;
}
// 2. For first-time users, preload with random emojis
setInitialEmojis();
}
function loadState(e1, e2, e3, imgUrl) {
selectedEmoji1 = e1;
selectedEmoji2 = e2;
selectedEmoji3 = e3;
currentImageUrl = imgUrl;
const emojiHtml1 = emojiConverter.replace_unified(selectedEmoji1);
selectedEmoji1Url = emojiHtml1.match(/src="([^"]*)"/)[1];
selectionBox1.innerHTML = `<img src="${selectedEmoji1Url}" alt="${selectedEmoji1}">`;
selectionBox1.classList.add('filled');
const emojiHtml2 = emojiConverter.replace_unified(selectedEmoji2);
selectedEmoji2Url = emojiHtml2.match(/src="([^"]*)"/)[1];
selectionBox2.innerHTML = `<img src="${selectedEmoji2Url}" alt="${selectedEmoji2}">`;
selectionBox2.classList.add('filled');
const emojiHtml3 = emojiConverter.replace_unified(selectedEmoji3);
selectedEmoji3Url = emojiHtml3.match(/src="([^"]*)"/)[1];
selectionBox3.innerHTML = `<img src="${selectedEmoji3Url}" alt="${selectedEmoji3}">`;
selectionBox3.classList.add('filled');
const img = document.createElement('img');
img.src = currentImageUrl;
img.alt = `Merged emoji of ${selectedEmoji1}, ${selectedEmoji2} and ${selectedEmoji3}`;
resultWrapper.innerHTML = '';
resultWrapper.appendChild(img);
checkSelections();
}
function setInitialEmojis() {
const index1 = Math.floor(Math.random() * emojis.length);
let index2 = Math.floor(Math.random() * emojis.length);
let index3 = Math.floor(Math.random() * emojis.length);
// Ensure three different emojis are selected
while (index1 === index2) {
index2 = Math.floor(Math.random() * emojis.length);
}
while (index1 === index3 || index2 === index3) {
index3 = Math.floor(Math.random() * emojis.length);
}
selectedEmoji1 = emojis[index1];
const emojiHtml1 = emojiConverter.replace_unified(selectedEmoji1);
selectedEmoji1Url = emojiHtml1.match(/src="([^"]*)"/)[1];
selectionBox1.innerHTML = `<img src="${selectedEmoji1Url}" alt="${selectedEmoji1}">`;
selectionBox1.classList.add('filled');
selectedEmoji2 = emojis[index2];
const emojiHtml2 = emojiConverter.replace_unified(selectedEmoji2);
selectedEmoji2Url = emojiHtml2.match(/src="([^"]*)"/)[1];
selectionBox2.innerHTML = `<img src="${selectedEmoji2Url}" alt="${selectedEmoji2}">`;
selectionBox2.classList.add('filled');
selectedEmoji3 = emojis[index3];
const emojiHtml3 = emojiConverter.replace_unified(selectedEmoji3);
selectedEmoji3Url = emojiHtml3.match(/src="([^"]*)"/)[1];
selectionBox3.innerHTML = `<img src="${selectedEmoji3Url}" alt="${selectedEmoji3}">`;
selectionBox3.classList.add('filled');
currentImageUrl = null;
currentCreativeDirection = null;
checkSelections();
}
async function urlToDataUrl(url) {
return new Promise(async (resolve, reject) => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
} catch (error) {
console.error(`Failed to convert ${url} to data URL:`, error);
reject(error);
}
});
}
async function mergeEmojis() {
if (!selectedEmoji1 || !selectedEmoji2 || !selectedEmoji3) return;
resultContainer.classList.remove('hidden');
resultWrapper.innerHTML = ''; // Clear previous result
loader.classList.remove('hidden');
resultWrapper.appendChild(loader);
mergeButton.disabled = true;
mergeButton.textContent = 'Merging...';
try {
// Convert image URLs to data URLs for the vision model
const [emoji1DataUrl, emoji2DataUrl, emoji3DataUrl] = await Promise.all([
urlToDataUrl(selectedEmoji1Url),
urlToDataUrl(selectedEmoji2Url),
urlToDataUrl(selectedEmoji3Url)
]);
// Step 1: Get creative direction from LLM
const directionCompletion = await websim.chat.completions.create({
messages: [
{
role: "system",
content: `You are an expert in emoji fusion. Your task is to provide a succinct, one to three-sentence creative direction for combining three emojis provided as images. Analyze their visual elements and describe how to merge or arrange them to create a coherent composition. Describe the salient qualities to highlight from each emoji. Be creative with your composition. Make sure include some direction on emotions if and only if the emojis explicity capture emotion; otherwise, focus just on visual composition. Aim for conceptual coherence and simplicity while balancing all three elements.
For three-way combinations, consider hierarchical arrangements (main subject with two supporting elements) or balanced compositions where all three elements are integrated equally. If multiple animals are picked, create a fusion creature. If animals and objects are picked, anthropomorphize appropriately. If faces are involved, ensure expressions are recognizable.
Additional instructions for smooth fusions: Make very smooth blend of the three elements. They should be fused smoothly and seamlessly while ensuring individual elements remain recognizable. Start your direction with 'Envision a fusion of...' when creating blended combinations.`
},
{
role: "user",
content: [
{
type: "text",
text: `First emoji: ${selectedEmoji1}, Second emoji: ${selectedEmoji2}, Third emoji: ${selectedEmoji3}`
},
{
type: "image_url",
image_url: { url: emoji1DataUrl }
},
{
type: "image_url",
image_url: { url: emoji2DataUrl }
},
{
type: "image_url",
image_url: { url: emoji3DataUrl }
}
]
}
]
});
const creativeDirection = directionCompletion.content;
currentCreativeDirection = creativeDirection;
const imageGenPrompt = `${creativeDirection}. The style should be the iconic Apple iOS emoji style. This means a high-quality 3D render with smooth gradients, subtle shadows, and a clean, semi-glossy finish. Aim for conceptual simplicity as we are designing a hybrid emoji. Ensure the design is vibrant and easily recognizable. Render on a white background. Prioritize visual coherence.`;
const result = await websim.imageGen({
prompt: imageGenPrompt,
transparent: true,
aspect_ratio: "1:1",
image_inputs: [
{ url: selectedEmoji1Url },
{ url: selectedEmoji2Url },
{ url: selectedEmoji3Url }
]
});
currentImageUrl = result.url;
const img = document.createElement('img');
img.src = result.url;
img.alt = `Merged emoji of ${selectedEmoji1}, ${selectedEmoji2} and ${selectedEmoji3}`;
img.onload = () => {
loader.classList.add('hidden');
resultWrapper.innerHTML = ''; // Clear loader
resultWrapper.appendChild(img);
if (isMobile()) {
postCommentButton.classList.remove('hidden');
} else {
// automatically post the generated image once it is loaded, after a delay
setTimeout(() => {
postComment().catch(err => console.error('Auto-post failed', err));
}, 2000);
}
updateStateAndURL();
};
img.onerror = () => {
showError("Failed to load the generated image.");
};
} catch (error) {
console.error("Image generation failed:", error);
showError("Sorry, we couldn't create your emoji. Please try again.");
} finally {
mergeButton.disabled = false;
mergeButton.textContent = 'Create Emoji';
}
}
function showError(message) {
loader.classList.add('hidden');
resultWrapper.innerHTML = `<p style="color: red;">${message}</p>`;
}
function handleRandomClick() {
resultWrapper.innerHTML = '';
postCommentButton.classList.add('hidden');
setInitialEmojis();
}
initializeState();
selectionBox1.addEventListener('click', () => openDrawer(1));
selectionBox2.addEventListener('click', () => openDrawer(2));
selectionBox3.addEventListener('click', () => openDrawer(3));
overlay.addEventListener('click', closeDrawer);
drawerGrid.addEventListener('click', handleEmojiSelection);
mergeButton.addEventListener('click', mergeEmojis);
randomButton.addEventListener('click', handleRandomClick);
postCommentButton.addEventListener('click', postComment);
document.getElementById('drawer-search').addEventListener('input', handleDrawerSearch);
const unifiedToChar = u => u.split('-').map(h => String.fromCodePoint(parseInt(h,16))).join('');
function createDrawerEmojiCard(e){
const el=document.createElement('div');el.className='emoji';
const ch = typeof e === 'string' ? e : unifiedToChar(e.unified);
el.dataset.emoji=ch;el.innerHTML=emojiConverter.replace_unified(ch);return el;
}
function ensureSentinel(){
if (!sentinel || !drawerGrid.contains(sentinel)) {
sentinel = document.createElement('div');
sentinel.id = 'drawer-sentinel';
sentinel.style.height = '1px';
drawerGrid.appendChild(sentinel);
}
}
function setupObserver(){
if(io) io.disconnect();
io = new IntersectionObserver((entries)=>{
if(entries.some(e=>e.isIntersecting)) loadMoreEmojis();
}, { root: drawerGrid, rootMargin: '200px', threshold: 0 });
ensureSentinel(); io.observe(sentinel);
}
async function loadEmojiData(){
if(dataLoaded) return;
try{
const res=await fetch('https://cdn.jsdelivr.net/npm/emoji-datasource@15.0.1/emoji.json');
const data=await res.json();
allEmojis=data.filter(e=>e.unified&&e.short_name).sort((a,b)=>(a.category||'').localeCompare(b.category||'')|| (a.sort_order||0)-(b.sort_order||0));
const popSet=new Set(emojis);
const rest=allEmojis.filter(e=>!popSet.has(unifiedToChar(e.unified)));
filteredEmojis=[...emojis, ...rest]; dataLoaded=true; currentPage=0; drawerGrid.innerHTML='';
ensureSentinel(); loadMoreEmojis(); setupObserver();
}catch(err){console.error('Failed to load emoji data:',err);}
}
function loadMoreEmojis(){
if(isLoading) return;
const total=filteredEmojis.length;
if(currentPage*EMOJIS_PER_PAGE>=total) return;
isLoading=true; ensureSentinel();
const start=currentPage*EMOJIS_PER_PAGE, end=Math.min(start+EMOJIS_PER_PAGE,total);
for(let i=start;i<end;i++){ drawerGrid.insertBefore(createDrawerEmojiCard(filteredEmojis[i]), sentinel); }
currentPage++; isLoading=false;
}
function handleDrawerSearch(){
const q=(document.getElementById('drawer-search').value||'').toLowerCase();
if(!q){
const popSet=new Set(emojis);
const rest=allEmojis.filter(e=>!popSet.has(unifiedToChar(e.unified)));
filteredEmojis=[...emojis, ...rest];
}else{
filteredEmojis = allEmojis.filter(e =>
(e.short_name||'').toLowerCase().includes(q) ||
(e.short_names||[]).join(' ').toLowerCase().includes(q) ||
(e.category||'').toLowerCase().includes(q)
);
}
currentPage=0; drawerGrid.innerHTML=''; ensureSentinel(); loadMoreEmojis(); setupObserver();
}
async function postComment() {
if (!selectedEmoji1 || !selectedEmoji2 || !selectedEmoji3 || !currentImageUrl) {
console.error('Missing data for posting comment.');
return;
}
try {
// Hide button once clicked
postCommentButton.classList.add('hidden');
const params = new URLSearchParams();
params.set('e1', selectedEmoji1);
params.set('e2', selectedEmoji2);
params.set('e3', selectedEmoji3);
params.set('img', currentImageUrl);
// Fallback for local development if __WEBSIM_DATA__ is not available
const baseUrl = window.__WEBSIM_DATA__ ? window.__WEBSIM_DATA__.baseUrl : `${window.location.origin}${window.location.pathname}`;
const shareableUrl = `${baseUrl}?${params.toString()}`;
const commentContent = `[${selectedEmoji1} + ${selectedEmoji2} + ${selectedEmoji3}](${shareableUrl})\n\n`;
await window.websim.postComment({
content: commentContent
});
} catch (e) {
console.error('Failed to post comment', e);
}
}
});
Comentarios (0)
Inicia sesión para comentar
¡Aún no hay comentarios. Sé el primero en comentar!