(() => { const OVERLAY_ID = "__kleap_overlay__"; let overlay, label; // The possible states are: // { type: 'inactive' } // { type: 'inspecting', element: ?HTMLElement } // { type: 'selected', element: HTMLElement } let state = { type: "inactive" }; /* ---------- i18n translations ----------------------------------------- */ const translations = { en: { editText: "Edit text", mentionToAI: "Mention to AI", replaceImage: "Replace image", chooseFromLibrary: "Choose from library", changeUrl: "Change URL", generateWithAI: "Generate with AI", uploadImage: "Upload image", imageUrl: "Image URL", cancel: "Cancel", apply: "Apply", generate: "🎨 Generate", generating: "Generating...", enterImageUrl: "Enter image URL...", chooseModel: "Choose Model:", describeImage: "Describe your image:", aiPromptPlaceholder: "A beautiful landscape with mountains and a sunset...", stylePresets: "Style presets (optional):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "Generating with", mayTakeTime: "This may take 10-30 seconds", }, fr: { editText: "Modifier le texte", mentionToAI: "Mentionner à l'IA", replaceImage: "Remplacer l'image", chooseFromLibrary: "Choisir dans la bibliothèque", changeUrl: "Changer l'URL", generateWithAI: "Générer avec l'IA", uploadImage: "Télécharger une image", imageUrl: "URL de l'image", cancel: "Annuler", apply: "Appliquer", generate: "🎨 Générer", generating: "Génération...", enterImageUrl: "Entrez l'URL de l'image...", chooseModel: "Choisir le modèle :", describeImage: "Décrivez votre image :", aiPromptPlaceholder: "Un beau paysage avec des montagnes et un coucher de soleil...", stylePresets: "Préréglages de style (optionnel) :", urlPlaceholder: "https://exemple.com/image.jpg", generatingWith: "Génération avec", mayTakeTime: "Cela peut prendre 10-30 secondes", }, de: { editText: "Text bearbeiten", mentionToAI: "KI erwähnen", replaceImage: "Bild ersetzen", chooseFromLibrary: "Aus Bibliothek wählen", changeUrl: "URL ändern", generateWithAI: "Mit KI generieren", uploadImage: "Bild hochladen", imageUrl: "Bild-URL", cancel: "Abbrechen", apply: "Anwenden", generate: "🎨 Generieren", generating: "Generierung...", enterImageUrl: "Bild-URL eingeben...", chooseModel: "Modell wählen:", describeImage: "Beschreiben Sie Ihr Bild:", aiPromptPlaceholder: "Eine schöne Landschaft mit Bergen und Sonnenuntergang...", stylePresets: "Stilvorlagen (optional):", urlPlaceholder: "https://beispiel.com/bild.jpg", generatingWith: "Generierung mit", mayTakeTime: "Dies kann 10-30 Sekunden dauern", }, es: { editText: "Editar texto", mentionToAI: "Mencionar a la IA", replaceImage: "Reemplazar imagen", chooseFromLibrary: "Elegir de la biblioteca", changeUrl: "Cambiar URL", generateWithAI: "Generar con IA", uploadImage: "Subir imagen", imageUrl: "URL de imagen", cancel: "Cancelar", apply: "Aplicar", generate: "🎨 Generar", generating: "Generando...", enterImageUrl: "Ingrese URL de imagen...", chooseModel: "Elegir modelo:", describeImage: "Describe tu imagen:", aiPromptPlaceholder: "Un hermoso paisaje con montañas y atardecer...", stylePresets: "Estilos predefinidos (opcional):", urlPlaceholder: "https://ejemplo.com/imagen.jpg", generatingWith: "Generando con", mayTakeTime: "Esto puede tomar 10-30 segundos", }, pt: { editText: "Editar texto", mentionToAI: "Mencionar à IA", replaceImage: "Substituir imagem", chooseFromLibrary: "Escolher da biblioteca", changeUrl: "Alterar URL", generateWithAI: "Gerar com IA", uploadImage: "Enviar imagem", imageUrl: "URL da imagem", cancel: "Cancelar", apply: "Aplicar", generate: "🎨 Gerar", generating: "Gerando...", enterImageUrl: "Digite a URL da imagem...", chooseModel: "Escolher modelo:", describeImage: "Descreva sua imagem:", aiPromptPlaceholder: "Uma bela paisagem com montanhas e pôr do sol...", stylePresets: "Estilos predefinidos (opcional):", urlPlaceholder: "https://exemplo.com/imagem.jpg", generatingWith: "Gerando com", mayTakeTime: "Isso pode levar 10-30 segundos", }, it: { editText: "Modifica testo", mentionToAI: "Menziona all'IA", replaceImage: "Sostituisci immagine", chooseFromLibrary: "Scegli dalla libreria", changeUrl: "Cambia URL", generateWithAI: "Genera con IA", uploadImage: "Carica immagine", imageUrl: "URL immagine", cancel: "Annulla", apply: "Applica", generate: "🎨 Genera", generating: "Generazione...", enterImageUrl: "Inserisci URL immagine...", chooseModel: "Scegli modello:", describeImage: "Descrivi la tua immagine:", aiPromptPlaceholder: "Un bellissimo paesaggio con montagne e tramonto...", stylePresets: "Stili predefiniti (opzionale):", urlPlaceholder: "https://esempio.com/immagine.jpg", generatingWith: "Generazione con", mayTakeTime: "Potrebbe richiedere 10-30 secondi", }, nl: { editText: "Tekst bewerken", mentionToAI: "AI vermelden", replaceImage: "Afbeelding vervangen", chooseFromLibrary: "Kies uit bibliotheek", changeUrl: "URL wijzigen", generateWithAI: "Genereren met AI", uploadImage: "Afbeelding uploaden", imageUrl: "Afbeelding-URL", cancel: "Annuleren", apply: "Toepassen", generate: "🎨 Genereren", generating: "Genereren...", enterImageUrl: "Voer afbeelding-URL in...", chooseModel: "Kies model:", describeImage: "Beschrijf je afbeelding:", aiPromptPlaceholder: "Een prachtig landschap met bergen en zonsondergang...", stylePresets: "Stijlvoorinstellingen (optioneel):", urlPlaceholder: "https://voorbeeld.com/afbeelding.jpg", generatingWith: "Genereren met", mayTakeTime: "Dit kan 10-30 seconden duren", }, ar: { editText: "تعديل النص", mentionToAI: "إشارة للذكاء الاصطناعي", replaceImage: "استبدال الصورة", chooseFromLibrary: "اختر من المكتبة", changeUrl: "تغيير الرابط", generateWithAI: "إنشاء بالذكاء الاصطناعي", uploadImage: "رفع صورة", imageUrl: "رابط الصورة", cancel: "إلغاء", apply: "تطبيق", generate: "🎨 إنشاء", generating: "جاري الإنشاء...", enterImageUrl: "أدخل رابط الصورة...", chooseModel: "اختر النموذج:", describeImage: "صف صورتك:", aiPromptPlaceholder: "منظر طبيعي جميل مع الجبال وغروب الشمس...", stylePresets: "أنماط مسبقة (اختياري):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "إنشاء باستخدام", mayTakeTime: "قد يستغرق هذا 10-30 ثانية", }, zh: { editText: "编辑文本", mentionToAI: "提及AI", replaceImage: "替换图片", chooseFromLibrary: "从库中选择", changeUrl: "更改链接", generateWithAI: "AI生成", uploadImage: "上传图片", imageUrl: "图片链接", cancel: "取消", apply: "应用", generate: "🎨 生成", generating: "生成中...", enterImageUrl: "输入图片链接...", chooseModel: "选择模型:", describeImage: "描述你的图片:", aiPromptPlaceholder: "美丽的山景和日落...", stylePresets: "风格预设(可选):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "使用生成", mayTakeTime: "这可能需要10-30秒", }, ja: { editText: "テキストを編集", mentionToAI: "AIに言及", replaceImage: "画像を置換", chooseFromLibrary: "ライブラリから選択", changeUrl: "URLを変更", generateWithAI: "AIで生成", uploadImage: "画像をアップロード", imageUrl: "画像URL", cancel: "キャンセル", apply: "適用", generate: "🎨 生成", generating: "生成中...", enterImageUrl: "画像URLを入力...", chooseModel: "モデルを選択:", describeImage: "画像を説明してください:", aiPromptPlaceholder: "山と夕日の美しい風景...", stylePresets: "スタイルプリセット(オプション):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "生成中:", mayTakeTime: "10〜30秒かかる場合があります", }, el: { editText: "Επεξεργασία κειμένου", mentionToAI: "Αναφορά στην ΤΝ", replaceImage: "Αντικατάσταση εικόνας", chooseFromLibrary: "Επιλογή από βιβλιοθήκη", changeUrl: "Αλλαγή URL", generateWithAI: "Δημιουργία με ΤΝ", uploadImage: "Μεταφόρτωση εικόνας", imageUrl: "URL εικόνας", cancel: "Ακύρωση", apply: "Εφαρμογή", generate: "🎨 Δημιουργία", generating: "Δημιουργία...", enterImageUrl: "Εισάγετε URL εικόνας...", chooseModel: "Επιλογή μοντέλου:", describeImage: "Περιγράψτε την εικόνα σας:", aiPromptPlaceholder: "Ένα όμορφο τοπίο με βουνά και ηλιοβασίλεμα...", stylePresets: "Προεπιλογές στυλ (προαιρετικό):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "Δημιουργία με", mayTakeTime: "Μπορεί να χρειαστεί 10-30 δευτερόλεπτα", }, sq: { editText: "Redakto tekstin", mentionToAI: "Përmend AI-në", replaceImage: "Zëvendëso imazhin", chooseFromLibrary: "Zgjidh nga biblioteka", changeUrl: "Ndrysho URL-në", generateWithAI: "Gjenero me AI", uploadImage: "Ngarko imazh", imageUrl: "URL e imazhit", cancel: "Anulo", apply: "Apliko", generate: "🎨 Gjenero", generating: "Duke gjeneruar...", enterImageUrl: "Fut URL-në e imazhit...", chooseModel: "Zgjidh modelin:", describeImage: "Përshkruaj imazhin tënd:", aiPromptPlaceholder: "Një peizazh i bukur me male dhe perëndim dielli...", stylePresets: "Paracaktime stili (opsionale):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "Duke gjeneruar me", mayTakeTime: "Kjo mund të zgjasë 10-30 sekonda", }, id: { editText: "Edit teks", mentionToAI: "Sebut ke AI", replaceImage: "Ganti gambar", chooseFromLibrary: "Pilih dari perpustakaan", changeUrl: "Ubah URL", generateWithAI: "Hasilkan dengan AI", uploadImage: "Unggah gambar", imageUrl: "URL gambar", cancel: "Batal", apply: "Terapkan", generate: "🎨 Hasilkan", generating: "Menghasilkan...", enterImageUrl: "Masukkan URL gambar...", chooseModel: "Pilih model:", describeImage: "Deskripsikan gambar Anda:", aiPromptPlaceholder: "Pemandangan indah dengan gunung dan matahari terbenam...", stylePresets: "Preset gaya (opsional):", urlPlaceholder: "https://contoh.com/gambar.jpg", generatingWith: "Menghasilkan dengan", mayTakeTime: "Ini mungkin memakan waktu 10-30 detik", }, tr: { editText: "Metni düzenle", mentionToAI: "AI'ya bahset", replaceImage: "Resmi değiştir", chooseFromLibrary: "Kütüphaneden seç", changeUrl: "URL'yi değiştir", generateWithAI: "AI ile oluştur", uploadImage: "Resim yükle", imageUrl: "Resim URL'si", cancel: "İptal", apply: "Uygula", generate: "🎨 Oluştur", generating: "Oluşturuluyor...", enterImageUrl: "Resim URL'sini girin...", chooseModel: "Model seçin:", describeImage: "Resminizi tanımlayın:", aiPromptPlaceholder: "Dağlar ve gün batımı ile güzel bir manzara...", stylePresets: "Stil ön ayarları (isteğe bağlı):", urlPlaceholder: "https://ornek.com/resim.jpg", generatingWith: "İle oluşturuluyor", mayTakeTime: "Bu 10-30 saniye sürebilir", }, vi: { editText: "Chỉnh sửa văn bản", mentionToAI: "Đề cập đến AI", replaceImage: "Thay thế hình ảnh", chooseFromLibrary: "Chọn từ thư viện", changeUrl: "Thay đổi URL", generateWithAI: "Tạo với AI", uploadImage: "Tải lên hình ảnh", imageUrl: "URL hình ảnh", cancel: "Hủy", apply: "Áp dụng", generate: "🎨 Tạo", generating: "Đang tạo...", enterImageUrl: "Nhập URL hình ảnh...", chooseModel: "Chọn mô hình:", describeImage: "Mô tả hình ảnh của bạn:", aiPromptPlaceholder: "Phong cảnh đẹp với núi và hoàng hôn...", stylePresets: "Cài đặt phong cách (tùy chọn):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "Đang tạo với", mayTakeTime: "Có thể mất 10-30 giây", }, fa: { editText: "ویرایش متن", mentionToAI: "اشاره به هوش مصنوعی", replaceImage: "جایگزینی تصویر", chooseFromLibrary: "انتخاب از کتابخانه", changeUrl: "تغییر لینک", generateWithAI: "تولید با هوش مصنوعی", uploadImage: "آپلود تصویر", imageUrl: "لینک تصویر", cancel: "لغو", apply: "اعمال", generate: "🎨 تولید", generating: "در حال تولید...", enterImageUrl: "لینک تصویر را وارد کنید...", chooseModel: "انتخاب مدل:", describeImage: "تصویر خود را توصیف کنید:", aiPromptPlaceholder: "منظره زیبا با کوهها و غروب آفتاب...", stylePresets: "پیشتنظیمات سبک (اختیاری):", urlPlaceholder: "https://example.com/image.jpg", generatingWith: "تولید با", mayTakeTime: "این ممکن است ۱۰-۳۰ ثانیه طول بکشد", }, }; // Supported languages list const supportedLangs = [ "en", "fr", "de", "es", "pt", "it", "nl", "ar", "zh", "ja", "el", "sq", "id", "tr", "vi", "fa", ]; // Detect language from document or parent frame function detectLanguage() { // Check document lang attribute const docLang = document.documentElement.lang || ""; const langCode = docLang.split("-")[0].toLowerCase(); if (supportedLangs.includes(langCode)) return langCode; // Check URL for locale pattern (e.g., /fr/, /en/) const pathMatch = window.location.pathname.match(/^\/([a-z]{2})\//); if (pathMatch && supportedLangs.includes(pathMatch[1])) return pathMatch[1]; // Default to English return "en"; } const currentLang = detectLanguage(); const t = (key) => translations[currentLang]?.[key] || translations.en[key] || key; /* ---------- helpers --------------------------------------------------- */ const css = (el, obj) => Object.assign(el.style, obj); function getElementPath(element) { const path = []; let current = element; let depth = 0; const maxDepth = 5; while (current && current !== document.body && depth < maxDepth) { let identifier = current.tagName.toLowerCase(); if (current.id) { identifier += `#${current.id}`; } else if (current.className && typeof current.className === "string") { const firstClass = current.className .trim() .split(" ") .filter((c) => c && !c.startsWith("_"))[0]; if (firstClass) { identifier += `.${firstClass}`; } } path.unshift(identifier); current = current.parentElement; depth++; } return path.join(" > "); } function isTextElement(el) { // Check if element contains ANY text (even nested) if (!el) return false; // Get all text content including nested elements const textContent = el.textContent || ""; // Check if it's a text-focused element const textTags = [ "H1", "H2", "H3", "H4", "H5", "H6", "P", "SPAN", "A", "BUTTON", "LI", "TD", "TH", "LABEL", "DIV", ]; const hasText = textContent.trim().length > 0; const isTextTag = textTags.includes(el.tagName); // If element has text and is a common text element, it's editable return hasText && (isTextTag || el.childNodes.length === 1); } function isImageElement(el) { return el && el.tagName === "IMG"; } // Shared menu styles (shadcn-inspired) function injectMenuStyles() { if (document.getElementById("kleap-menu-styles")) return; const style = document.createElement("style"); style.id = "kleap-menu-styles"; style.textContent = ` @keyframes kleapMenuIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } .kleap-menu { position: fixed; background: white; border: 1px solid hsl(240 5.9% 90%); border-radius: 8px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); z-index: 2147483648; padding: 4px; min-width: 180px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; animation: kleapMenuIn 0.15s ease-out; } .kleap-menu-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 6px; cursor: pointer; transition: background-color 0.1s; color: hsl(240 10% 3.9%); font-size: 14px; line-height: 1; user-select: none; } .kleap-menu-item:hover { background: hsl(240 4.8% 95.9%); } .kleap-menu-item:active { background: hsl(240 5% 92%); } .kleap-menu-icon { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; color: hsl(240 3.8% 46.1%); } .kleap-menu-icon svg { width: 16px; height: 16px; } .kleap-menu-separator { height: 1px; background: hsl(240 5.9% 90%); margin: 4px -4px; } .kleap-menu-label { padding: 8px 10px 4px; font-size: 12px; font-weight: 500; color: hsl(240 3.8% 46.1%); } `; document.head.appendChild(style); } // SVG icons (Lucide-style) const icons = { image: ``, link: ``, upload: ``, sparkles: ``, pencil: ``, messageCircle: ``, }; function createMenu(rect, items) { injectMenuStyles(); const menu = document.createElement("div"); menu.className = "kleap-menu"; // Position menu below element, or above if not enough space const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; const menuHeight = items.length * 36 + 8; // approximate let top, left; if (spaceBelow >= menuHeight || spaceBelow >= spaceAbove) { top = Math.min(rect.bottom + 6, window.innerHeight - menuHeight - 10); } else { top = Math.max(10, rect.top - menuHeight - 6); } left = Math.min(Math.max(10, rect.left), window.innerWidth - 200); css(menu, { top: `${top}px`, left: `${left}px`, }); items.forEach((item) => { if (item.separator) { const sep = document.createElement("div"); sep.className = "kleap-menu-separator"; menu.appendChild(sep); return; } const menuItem = document.createElement("div"); menuItem.className = "kleap-menu-item"; const icon = document.createElement("span"); icon.className = "kleap-menu-icon"; icon.innerHTML = item.icon; const label = document.createElement("span"); label.textContent = item.label; menuItem.appendChild(icon); menuItem.appendChild(label); menuItem.onclick = (e) => { e.stopPropagation(); closeMenu(); item.action(); }; menu.appendChild(menuItem); }); document.body.appendChild(menu); // Close menu on click outside or escape const closeMenu = () => { menu.remove(); document.removeEventListener("click", handleOutsideClick); document.removeEventListener("keydown", handleEscape); }; const handleOutsideClick = (e) => { if (!menu.contains(e.target)) { closeMenu(); } }; const handleEscape = (e) => { if (e.key === "Escape") { closeMenu(); } }; setTimeout(() => { document.addEventListener("click", handleOutsideClick); document.addEventListener("keydown", handleEscape); }, 0); return { menu, closeMenu }; } function enableImageEdit(img) { const rect = img.getBoundingClientRect(); createMenu(rect, [ { icon: icons.image, label: t("chooseFromLibrary"), action: () => showLibraryDialog(img), }, { icon: icons.upload, label: t("uploadImage"), action: () => showUploadDialog(img), }, { icon: icons.link, label: t("changeUrl"), action: () => showUrlDialog(img), }, { separator: true }, { icon: icons.sparkles, label: t("generateWithAI"), action: () => showAIDialog(img), }, ]); } function showTextMenu(el) { const rect = el.getBoundingClientRect(); createMenu(rect, [ { icon: icons.pencil, label: t("editText"), action: () => enableInlineEdit(el), }, { icon: icons.messageCircle, label: t("mentionToAI"), action: () => sendToAI(el), }, ]); } function sendToAI(el) { // Generate ID and name let id = el.dataset.kleapId; let name = el.dataset.kleapName; let path = el.dataset.kleapPath; if (!id) { const tag = el.tagName.toLowerCase(); const uniqueId = Math.random().toString(36).substr(2, 9); if (el.id) { id = `${tag}#${el.id}`; } else if (el.className && typeof el.className === "string") { const firstClass = el.className.trim().split(" ")[0]; id = `${tag}.${firstClass}-${uniqueId}`; } else { id = `${tag}-${uniqueId}`; } } if (!name) { const tag = el.tagName.toLowerCase(); if (el.className && typeof el.className === "string") { const classes = el.className .trim() .split(" ") .filter((c) => c && !c.startsWith("_")); const meaningfulClass = classes.find( (c) => !c.match( /^(flex|grid|block|inline|absolute|relative|fixed|sticky|w-|h-|p-|m-|text-|bg-|border-|rounded-|shadow-|opacity-|z-)/, ), ); name = meaningfulClass ? `${tag}.${meaningfulClass}` : `${tag}.${classes[0] || ""}`; } else { name = tag; } } if (!path) { path = getElementPath(el); } window.parent.postMessage( { type: "kleap-component-selected", id: id, name: name, path: path, tagName: el.tagName.toLowerCase(), className: el.className || "", textContent: el.textContent ? el.textContent.substring(0, 100) : "", hasChildren: el.children.length > 0, rect: { width: el.offsetWidth, height: el.offsetHeight, }, }, "*", ); } function showLibraryDialog(img) { // Send message to parent to open Asset Manager window.parent.postMessage( { type: "kleap-image-library-select", id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`, oldSrc: img.src, alt: img.alt || "", }, "*", ); } function showUrlDialog(img) { const dialog = createDialog(t("changeUrl")); const input = document.createElement("input"); input.type = "text"; input.value = img.src; input.placeholder = t("enterImageUrl"); css(input, { width: "100%", padding: "10px 12px", border: "1px solid #ddd", borderRadius: "8px", fontSize: "14px", marginBottom: "16px", boxSizing: "border-box", fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', }); dialog.content.appendChild(input); dialog.onSave = () => { const newSrc = input.value.trim(); if (newSrc && newSrc !== img.src) { updateImage(img, newSrc); } }; input.focus(); input.select(); } function showUploadDialog(img) { // Open Asset Manager directly - it has full upload functionality window.parent.postMessage( { type: "kleap-image-library-select", id: img.dataset.kleapId || `img-${Math.random().toString(36).substr(2, 9)}`, oldSrc: img.src, alt: img.alt || "", }, "*", ); } function showAIDialog(img) { const dialog = createDialog(t("generateWithAI")); // Model selection const modelLabel = document.createElement("div"); css(modelLabel, { fontSize: "12px", color: "#666", marginBottom: "8px", fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', }); modelLabel.textContent = t("chooseModel"); const modelContainer = document.createElement("div"); css(modelContainer, { display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: "8px", marginBottom: "16px", }); const models = [ { id: "flux-pro", name: "🚀 Flux Pro", desc: "Fast & High quality" }, { id: "flux-dev", name: "⚡ Flux Dev", desc: "Good balance" }, { id: "sdxl", name: "🎨 Stable Diffusion", desc: "Classic & reliable" }, { id: "playground-v2", name: "✨ Playground v2", desc: "Aesthetic focus", }, { id: "kandinsky", name: "🖼️ Kandinsky", desc: "Artistic style" }, { id: "dalle-3", name: "🤖 DALL-E 3", desc: "OpenAI (if available)" }, ]; let selectedModel = "flux-pro"; const modelButtons = []; models.forEach((model) => { const button = document.createElement("div"); css(button, { padding: "8px", borderRadius: "8px", border: "2px solid #ddd", cursor: "pointer", transition: "all 0.2s", textAlign: "center", background: model.id === selectedModel ? "#ff0055" : "white", color: model.id === selectedModel ? "white" : "#333", }); button.innerHTML = `