From 3f9cb2c40fca48f1894faafb84e7dffb1f303868 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Wed, 9 Jul 2025 21:32:55 -0400 Subject: [PATCH] refactor: replace all inline styles with CSS classes for better maintainability - Moved 115+ inline styles from uncloseai-embed-modal.js to CSS classes - Cleaned all inline styles from page-reader.js, widget-library.js, chat.js, models.js, file-upload.js - Added CSS custom properties for theming support - Created reusable CSS classes for buttons, containers, and UI elements - Only retained legitimate dynamic styles (height calculations, position adjustments) - Improved consistency with PicoCSS overrides --- src/chat.js | 29 +- src/file-upload.js | 4 +- src/models.js | 4 +- src/page-reader.js | 13 +- src/translate-modal.js | 26 +- src/tts-modal.js | 2 +- src/ui.js | 71 +--- src/uncloseai-embed-modal.js | 591 ++++---------------------------- src/uncloseai-modal-builtin.css | 414 +++++++++++++++++++++- src/uncloseai-modal-pico.css | 388 ++++++++++++++++++++- src/widget-library.js | 28 +- 11 files changed, 905 insertions(+), 665 deletions(-) diff --git a/src/chat.js b/src/chat.js index 9c7603b..fe70338 100644 --- a/src/chat.js +++ b/src/chat.js @@ -22,31 +22,10 @@ function addCodeBlockCopyButtons(element) { // Create copy button const copyBtn = document.createElement("button"); - copyBtn.className = "code-copy-btn"; + copyBtn.className = "uncloseai-code-copy-btn"; copyBtn.textContent = "πŸ“‹"; copyBtn.title = "Copy code"; - copyBtn.style.cssText = ` - position: absolute; - top: 8px; - right: 8px; - background: rgba(0, 0, 0, 0.7); - color: white; - border: none; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - opacity: 0.8; - transition: opacity 0.2s; - z-index: 10; - `; - - copyBtn.onmouseenter = () => { - copyBtn.style.opacity = "1"; - }; - copyBtn.onmouseleave = () => { - copyBtn.style.opacity = "0.8"; - }; + // Styling handled by CSS class copyBtn.onclick = async () => { try { @@ -162,7 +141,7 @@ export async function handleUserInput() { // Add play/pause button for TTS const playPauseButton = document.createElement("button"); playPauseButton.textContent = "Generate TTS for AI Response"; - playPauseButton.style.margin = "5px"; + playPauseButton.className = "uncloseai-ui-button-margin"; let aiAudio = null; let aiBlob = null; let isPaused = false; @@ -186,7 +165,7 @@ export async function handleUserInput() { // Add download button const downloadButton = document.createElement("button"); downloadButton.textContent = "Download MP3"; - downloadButton.style.margin = "5px"; + downloadButton.className = "uncloseai-ui-button-margin"; downloadButton.onclick = () => { const a = document.createElement("a"); a.href = URL.createObjectURL(aiBlob); diff --git a/src/file-upload.js b/src/file-upload.js index 563f6ee..372c7cb 100644 --- a/src/file-upload.js +++ b/src/file-upload.js @@ -136,12 +136,12 @@ export async function integrateMegafarceResponse(response) { export function addFileUploadButton() { const button = document.createElement("button"); button.textContent = "Upload File"; - button.style.margin = "10px"; + button.className = "uncloseai-ui-button-margin"; const fileInput = document.createElement("input"); fileInput.type = "file"; fileInput.id = "file-input"; - fileInput.style.display = "none"; + fileInput.style.display = "none"; // Hidden input must remain inline button.onclick = () => fileInput.click(); fileInput.onchange = handleFileUpload; diff --git a/src/models.js b/src/models.js index 654bf48..8c299ff 100644 --- a/src/models.js +++ b/src/models.js @@ -95,7 +95,7 @@ export async function createModelSelectionDropdown() { const dropdown = document.createElement("select"); dropdown.id = "model-selection"; - dropdown.style.margin = "10px"; + dropdown.className = "uncloseai-ui-button-margin"; models.forEach((model) => { const option = document.createElement("option"); @@ -145,7 +145,7 @@ export function addRefreshModelsButton() { // Create a button element const refreshButton = document.createElement("button"); refreshButton.textContent = "Refresh Models"; - refreshButton.style.margin = "10px"; + refreshButton.className = "uncloseai-ui-button-margin"; refreshButton.onclick = async () => { refreshButton.textContent = "Refreshing..."; refreshButton.disabled = true; diff --git a/src/page-reader.js b/src/page-reader.js index f7a69c0..0a13411 100644 --- a/src/page-reader.js +++ b/src/page-reader.js @@ -8,14 +8,7 @@ export async function readPageWithHermes() { // Create status indicator const statusDiv = document.createElement("div"); - statusDiv.style.position = "fixed"; - statusDiv.style.top = "10px"; - statusDiv.style.right = "10px"; - statusDiv.style.padding = "10px"; - statusDiv.style.background = "rgba(0,0,0,0.8)"; - statusDiv.style.color = "white"; - statusDiv.style.borderRadius = "5px"; - statusDiv.style.zIndex = "1000"; + statusDiv.className = "uncloseai-page-reader-status"; document.body.appendChild(statusDiv); statusDiv.textContent = "Processing content and generating speech..."; @@ -32,13 +25,13 @@ export async function readPageWithHermes() { // Add pause/resume button const pauseButton = document.createElement("button"); pauseButton.textContent = "Pause Reading"; - pauseButton.style.marginLeft = "10px"; + pauseButton.className = "uncloseai-btn-margin-left"; statusDiv.appendChild(pauseButton); // Add download button const downloadButton = document.createElement("button"); downloadButton.textContent = "Download MP3"; - downloadButton.style.marginLeft = "10px"; + downloadButton.className = "uncloseai-btn-margin-left"; downloadButton.onclick = async () => { try { const { generateTitleForTTS } = await import("./tts.js"); diff --git a/src/translate-modal.js b/src/translate-modal.js index 44516cb..fe411fd 100644 --- a/src/translate-modal.js +++ b/src/translate-modal.js @@ -184,8 +184,8 @@ export function openTranslateModal() { // Visual styles are now handled by CSS classes - customContent.style.display = "block"; - pageContent.style.display = "none"; + customContent.classList.add("active"); + pageContent.classList.remove("active"); translateButton.textContent = getUIText("translateTextButton"); } }; @@ -198,7 +198,7 @@ export function openTranslateModal() { try { translateButton.textContent = getUIText("processingText"); translateButton.disabled = true; - resultContainer.style.display = "none"; + resultContainer.classList.remove("show"); // Get exact tokenization for the UI progress indicator const { extractPageContent } = await import("./translation.js"); @@ -232,12 +232,11 @@ ${preservedText}`; progressContainer.className = "translate-progress-container"; const progressText = document.createElement("div"); - progressText.style.cssText = - "font-weight: bold; color: #4CAF50; margin-bottom: 10px;"; + progressText.className = "translate-progress-text"; progressText.textContent = getUIText("processingTokens", { tokens: tokenInfo.totalTokens }); const etaText = document.createElement("div"); - etaText.style.cssText = "color: #666; margin-bottom: 15px;"; + etaText.className = "translate-eta-text"; etaText.textContent = `Estimated time: ${estimatedSeconds} seconds`; const progressBar = document.createElement("div"); @@ -248,7 +247,7 @@ ${preservedText}`; progressBar.appendChild(progressFill); const statusText = document.createElement("div"); - statusText.style.cssText = "color: #777; font-size: 0.9em;"; + statusText.className = "translate-status-text"; statusText.textContent = "Initializing translation..."; progressContainer.appendChild(progressText); @@ -270,7 +269,7 @@ ${preservedText}`; (elapsed / (estimatedSeconds * 1000)) * 100, 95, ); - progressFill.style.width = `${progress}%`; + progressFill.style.width = `${progress}%`; // Dynamic width needs to stay inline const remainingTime = Math.max( 0, @@ -362,8 +361,7 @@ ${preservedText}`; ) { console.log("Adding AI notice to preview window"); const notice = previewWindow.document.createElement("div"); - notice.style.cssText = - "position: fixed; top: 10px; right: 10px; background: #4CAF50; color: white; padding: 8px 12px; border-radius: 4px; font-size: 12px; z-index: 10000; box-shadow: 0 2px 8px rgba(0,0,0,0.2);"; + notice.className = "translate-ai-notice"; notice.textContent = `πŸ€– AI features should work in ${targetLanguageName}!`; previewWindow.document.body.appendChild(notice); @@ -386,7 +384,7 @@ ${preservedText}`; // Show raw HTML toggle in modal resultArea.innerHTML = `
- +
${translatedText.replace(//g, ">")}
@@ -395,13 +393,13 @@ ${preservedText}`; ${previewWindow ? "" : '

Note: Please allow popups for preview window

'}
`; - resultArea.style.whiteSpace = "normal"; + resultArea.className = "translate-result-area"; } else { resultArea.textContent = translatedText; - resultArea.style.whiteSpace = "pre-wrap"; + resultArea.className = "translate-result-area pre-wrap"; } - resultContainer.style.display = "block"; + resultContainer.classList.add("show"); // Complete progress bar clearInterval(progressInterval); diff --git a/src/tts-modal.js b/src/tts-modal.js index dc56cc8..85c9275 100644 --- a/src/tts-modal.js +++ b/src/tts-modal.js @@ -136,7 +136,7 @@ export function openTTSModal() { const result = await handleTTS(text, voice, speed); if (result.audio) { - resultDiv.style.display = "block"; + resultDiv.classList.add("show"); resultDiv.innerHTML = ""; // Create hidden audio element (no controls) diff --git a/src/ui.js b/src/ui.js index 6521733..bc7ade4 100644 --- a/src/ui.js +++ b/src/ui.js @@ -90,28 +90,7 @@ function addCodeBlockCopyButtons(element) { const copyBtn = document.createElement("button"); copyBtn.textContent = "πŸ“‹"; copyBtn.title = "Copy code"; - copyBtn.style.cssText = ` - position: absolute; - top: 8px; - right: 8px; - background: rgba(0, 0, 0, 0.7); - color: white; - border: none; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - opacity: 0.8; - transition: opacity 0.2s; - z-index: 10; - `; - - copyBtn.onmouseenter = () => { - copyBtn.style.opacity = "1"; - }; - copyBtn.onmouseleave = () => { - copyBtn.style.opacity = "0.8"; - }; + copyBtn.className = "uncloseai-code-copy-btn"; copyBtn.onclick = async (e) => { e.stopPropagation(); @@ -268,8 +247,7 @@ export function initializeUncloseaiElements() { // Create container for this uncloseai instance const container = document.createElement("div"); - container.className = "uncloseai-container"; - container.style.cssText = "width: 100%; margin: 10px 0;"; + container.className = "uncloseai-container uncloseai-ui-container"; if (features === "full" || type === "full") { createFullInterface(container); @@ -326,12 +304,12 @@ export function addReadPageButton() { const button = document.createElement("button"); button.textContent = "Read Page"; button.onclick = readPageWithHermes; - button.style.margin = "10px"; + button.className = "uncloseai-ui-button-margin"; const ttsButton = document.createElement("button"); ttsButton.textContent = "TTS Anything"; ttsButton.onclick = openTTSModal; - ttsButton.style.margin = "10px"; + ttsButton.className = "uncloseai-ui-button-margin"; document.body.appendChild(button); document.body.appendChild(ttsButton); @@ -366,25 +344,13 @@ export function createFloatingAIButton() { (window.matchMedia?.("(prefers-color-scheme: dark)").matches && !document.documentElement.getAttribute("data-theme")); - floatingButton.style.cssText = ` - position: fixed; - bottom: 20px; - right: 10px; - width: 110px; - height: 55px; - border-radius: 22px; - background: ${isDark ? "#ffffff" : "#000000"}; - border: 2px solid ${isDark ? "#000000" : "#ffffff"}; - color: ${isDark ? "#000000" : "#ffffff"}; - font-family: 'ChunkFiveRegular', monospace; - font-size: 13px; - cursor: pointer; - box-shadow: 0 4px 12px rgba(0,0,0,0.3); - z-index: 1000; - transition: all 0.3s ease; - max-width: calc(100vw - 20px); - box-sizing: border-box; - `; + // Set floating button theme colors via CSS custom properties + const root = document.documentElement; + root.style.setProperty('--floating-button-bg', isDark ? "#ffffff" : "#000000"); + root.style.setProperty('--floating-button-border', isDark ? "#000000" : "#ffffff"); + root.style.setProperty('--floating-button-text', isDark ? "#000000" : "#ffffff"); + + floatingButton.className = "uncloseai-floating-button"; } // Initial theme setup @@ -405,14 +371,7 @@ export function createFloatingAIButton() { } // Hover effects - floatingButton.onmouseenter = () => { - floatingButton.style.transform = "scale(1.1)"; - floatingButton.style.boxShadow = "0 6px 16px rgba(0,0,0,0.4)"; - }; - floatingButton.onmouseleave = () => { - floatingButton.style.transform = "scale(1)"; - floatingButton.style.boxShadow = "0 4px 12px rgba(0,0,0,0.3)"; - }; + // Hover effects now handled by CSS // Set up self-contained click handler setupFloatingButtonHandler(floatingButton); @@ -430,8 +389,7 @@ function setupFloatingButtonHandler(floatingButton) { const originalText = floatingButton.textContent; floatingButton.textContent = "loading..."; floatingButton.disabled = true; - floatingButton.style.opacity = "0.7"; - floatingButton.style.cursor = "wait"; + floatingButton.classList.add("loading"); try { console.log("Trying to toggle Hermes modal..."); @@ -480,8 +438,7 @@ function setupFloatingButtonHandler(floatingButton) { // Restore button state floatingButton.textContent = originalText; floatingButton.disabled = false; - floatingButton.style.opacity = "1"; - floatingButton.style.cursor = "pointer"; + floatingButton.classList.remove("loading"); } }; } diff --git a/src/uncloseai-embed-modal.js b/src/uncloseai-embed-modal.js index 7b79331..716b7f8 100644 --- a/src/uncloseai-embed-modal.js +++ b/src/uncloseai-embed-modal.js @@ -191,26 +191,11 @@ async function openUncloseaiEmbeddedModalNew() { // Simple container with flex layout const container = document.createElement("div"); - container.style.cssText = ` - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; - `; + container.className = "uncloseai-modal-container"; // Header const header = document.createElement("header"); - header.style.cssText = ` - flex-shrink: 0; - padding: 16px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - display: flex; - justify-content: space-between; - align-items: center; - box-shadow: 0 2px 4px ${colors.lightShadow}; - `; + header.className = "uncloseai-modal-header"; const titleContainer = document.createElement("div"); titleContainer.className = "uncloseai-title-container"; @@ -218,18 +203,14 @@ async function openUncloseaiEmbeddedModalNew() { const title = document.createElement("h2"); title.innerHTML = "πŸ€– uncloseai."; - title.style.cssText = ` - margin: 0; - font-size: ${isMobile ? "18px" : "20px"}; - font-weight: 600; - `; + title.className = `uncloseai-modal-title ${isMobile ? "uncloseai-modal-title-mobile" : "uncloseai-modal-title-desktop"}`; const subtitle = document.createElement("div"); const pageTitle = document.title || window.location.hostname; subtitle.innerHTML = ` -
+
uncloseai. ${getUIText("hermesIntro")}
- ${getUIText("discussingPage")}: ${pageTitle} + ${getUIText("discussingPage")}: ${pageTitle}
`; @@ -260,36 +241,16 @@ async function openUncloseaiEmbeddedModalNew() { // Chat area const chatArea = document.createElement("div"); - chatArea.style.cssText = ` - flex: 1; - overflow-y: auto; - padding: 16px; - background: ${colors.contentBackground}; - -webkit-overflow-scrolling: touch; - `; + chatArea.className = "uncloseai-modal-chat-area"; const chatBox = document.createElement("div"); chatBox.id = "modal-chat-box"; - chatBox.style.cssText = ` - max-width: 100%; - display: flex; - flex-direction: column; - gap: 12px; - `; + chatBox.className = "uncloseai-chat-box"; chatArea.appendChild(chatBox); // Settings panel (collapsible) const settingsPanel = document.createElement("div"); - settingsPanel.style.cssText = ` - flex-shrink: 0; - background: ${colors.panelBackground}; - border-top: 1px solid ${colors.dividerColor}; - border-bottom: 1px solid ${colors.dividerColor}; - padding: 16px; - display: none; - max-height: 40vh; - overflow-y: auto; - `; + settingsPanel.className = "uncloseai-modal-settings"; // Model selection const modelSection = document.createElement("div"); @@ -300,15 +261,7 @@ async function openUncloseaiEmbeddedModalNew() { modelLabel.className = "uncloseai-label"; const modelSelect = document.createElement("select"); - modelSelect.style.cssText = ` - width: 100%; - padding: 8px 12px; - border: 1px solid ${colors.borderColor}; - border-radius: 6px; - font-size: 14px; - background: ${colors.inputBackground}; - color: ${USE_CUSTOM_STYLING ? 'black' : colors.primaryText}; - `; + modelSelect.className = "uncloseai-modal-select"; // Store models for selection handler let loadedModels = []; @@ -400,15 +353,7 @@ async function openUncloseaiEmbeddedModalNew() { const voiceSelect = document.createElement("select"); voiceSelect.id = "hermes-voice-select"; - voiceSelect.style.cssText = ` - width: 100%; - padding: 8px 12px; - border: 1px solid ${colors.borderColor}; - border-radius: 6px; - font-size: 14px; - background: ${colors.inputBackground}; - color: ${USE_CUSTOM_STYLING ? 'black' : colors.primaryText}; - `; + voiceSelect.className = "uncloseai-modal-select"; const voices = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]; voices.forEach((voice, index) => { @@ -448,15 +393,7 @@ async function openUncloseaiEmbeddedModalNew() { const languageSelect = document.createElement("select"); languageSelect.id = "hermes-language-select"; - languageSelect.style.cssText = ` - width: 100%; - padding: 8px 12px; - border: 1px solid ${colors.borderColor}; - border-radius: 6px; - font-size: 14px; - background: ${colors.inputBackground}; - color: ${USE_CUSTOM_STYLING ? 'black' : colors.primaryText}; - `; + languageSelect.className = "uncloseai-modal-select"; // Add options for each supported language Object.entries(SUPPORTED_LANGUAGES).forEach(([code, englishName]) => { @@ -500,15 +437,10 @@ async function openUncloseaiEmbeddedModalNew() { const actionsLabel = document.createElement("div"); actionsLabel.textContent = getUIText("quickActions"); - actionsLabel.style.cssText = - "margin-bottom: 8px; font-weight: 600; color: #495057;"; + actionsLabel.className = "uncloseai-label"; const actionsGrid = document.createElement("div"); - actionsGrid.style.cssText = ` - display: grid; - grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); - gap: 8px; - `; + actionsGrid.className = "uncloseai-grid-actions"; const actions = [ { text: getUIText("refreshModels"), action: loadModels }, @@ -551,33 +483,19 @@ async function openUncloseaiEmbeddedModalNew() { // Load and save settings panel state let settingsOpen = localStorage.getItem("hermesSettingsOpen") === "true"; - settingsPanel.style.display = settingsOpen ? "block" : "none"; - menuBtn.style.background = settingsOpen - ? "rgba(255,255,255,0.3)" - : "rgba(255,255,255,0.2)"; + if (settingsOpen) { + settingsPanel.classList.add("open"); + } menuBtn.onclick = () => { settingsOpen = !settingsOpen; - settingsPanel.style.display = settingsOpen ? "block" : "none"; - menuBtn.style.background = settingsOpen - ? "rgba(255,255,255,0.3)" - : "rgba(255,255,255,0.2)"; + settingsPanel.classList.toggle("open", settingsOpen); localStorage.setItem("hermesSettingsOpen", settingsOpen.toString()); }; // Quick action buttons const controls = document.createElement("div"); - controls.style.cssText = ` - flex-shrink: 0; - padding: 8px 16px; - background: ${colors.panelBackground}; - border-top: 1px solid ${colors.dividerColor}; - border-bottom: 1px solid ${colors.dividerColor}; - display: flex; - gap: 8px; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - `; + controls.className = "uncloseai-modal-controls"; // HTML to Markdown converter function function htmlToMarkdown(html) { @@ -722,11 +640,7 @@ async function openUncloseaiEmbeddedModalNew() { // Create download button next to the read button if (!readPageContainer) { readPageContainer = document.createElement("div"); - readPageContainer.style.cssText = ` - display: inline-flex; - gap: 8px; - align-items: center; - `; + readPageContainer.className = "uncloseai-read-page-container"; // Move the read button into the container const parent = button.parentElement; @@ -832,30 +746,10 @@ async function openUncloseaiEmbeddedModalNew() { // Create dropdown const dropdown = document.createElement("div"); dropdown.className = "translate-dropdown"; - dropdown.style.cssText = ` - position: absolute; - top: 100%; - left: 0; - background: ${colors.panelBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 8px; - box-shadow: 0 4px 12px ${colors.shadowColor}; - z-index: 1000; - min-width: 200px; - max-height: 300px; - overflow-y: auto; - margin-top: 4px; - `; // Add current language indicator const currentLangDiv = document.createElement("div"); - currentLangDiv.style.cssText = ` - padding: 8px 12px; - font-size: 12px; - color: ${colors.mutedText}; - border-bottom: 1px solid ${colors.dividerColor}; - background: ${colors.contentBackground}; - `; + currentLangDiv.className = "translate-dropdown-current"; const currentLangName = SUPPORTED_LANGUAGES[currentLang] || "Unknown"; currentLangDiv.textContent = `Current: ${currentLangName} (${currentLang})`; dropdown.appendChild(currentLangDiv); @@ -866,27 +760,16 @@ async function openUncloseaiEmbeddedModalNew() { if (code === currentLang) return; const option = document.createElement("div"); - option.style.cssText = ` - padding: 8px 12px; - cursor: pointer; - transition: background-color 0.2s; - color: ${colors.primaryText}; - font-size: 14px; - `; + option.className = "translate-dropdown-option"; option.textContent = `${name} (${code})`; - option.onmouseenter = () => { - option.style.background = colors.buttonHover; - }; - option.onmouseleave = () => { - option.style.background = "transparent"; - }; + // Hover effects handled by CSS option.onclick = async () => { try { // Show loading option.textContent = `Translating to ${name}...`; - option.style.pointerEvents = "none"; + option.classList.add("translate-dropdown-option-disabled"); // Translate the page const translatedHtml = await translateCurrentPage(code); @@ -902,7 +785,7 @@ async function openUncloseaiEmbeddedModalNew() { } catch (error) { alert(`Translation failed: ${error.message}`); option.textContent = `${name} (${code})`; - option.style.pointerEvents = "auto"; + option.classList.remove("translate-dropdown-option-disabled"); } }; @@ -995,25 +878,7 @@ async function openUncloseaiEmbeddedModalNew() { controlActions.forEach(({ text, action }) => { const btn = document.createElement("button"); btn.textContent = text; - btn.style.cssText = ` - padding: 8px 16px; - background: ${colors.buttonBackground}; - border: 1px solid ${colors.buttonBorder}; - border-radius: 20px; - cursor: pointer; - white-space: nowrap; - font-size: 14px; - color: ${colors.primaryText} !important; - transition: all 0.2s; - `; - btn.onmouseover = () => { - btn.style.background = colors.buttonHover; - btn.style.borderColor = colors.buttonBorder; - }; - btn.onmouseout = () => { - btn.style.background = colors.buttonBackground; - btn.style.borderColor = colors.buttonBorder; - }; + btn.className = "uncloseai-btn-primary"; btn.onclick = () => action(btn); controls.appendChild(btn); }); @@ -1026,37 +891,12 @@ async function openUncloseaiEmbeddedModalNew() { // Input area const inputArea = document.createElement("div"); - inputArea.style.cssText = ` - flex-shrink: 0; - padding: 16px; - background: ${colors.panelBackground}; - display: flex; - gap: 12px; - align-items: flex-end; - box-shadow: 0 -2px 10px ${colors.lightShadow}; - `; + inputArea.className = "uncloseai-modal-input-area"; const input = document.createElement("textarea"); input.id = "modal-user-input"; input.placeholder = getUIText("typePlaceholder"); - input.style.cssText = ` - flex: 1; - min-height: 44px; - max-height: 120px; - padding: 12px; - border: 1px solid ${colors.borderColor}; - border-radius: 24px; - resize: none; - font-size: 16px; - font-family: inherit; - line-height: 1.4; - outline: none; - background: ${colors.inputBackground}; - color: ${USE_CUSTOM_STYLING ? 'black' : colors.primaryText}; - transition: border-color 0.2s; - `; - input.onfocus = () => (input.style.borderColor = colors.focusBorder); - input.onblur = () => (input.style.borderColor = colors.borderColor); + input.className = "uncloseai-modal-input"; // Auto-resize textarea input.oninput = () => { @@ -1066,25 +906,7 @@ async function openUncloseaiEmbeddedModalNew() { const sendBtn = document.createElement("button"); sendBtn.textContent = "β†’"; - sendBtn.style.cssText = ` - width: 44px; - height: 44px; - padding: 0; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border: none; - border-radius: 50%; - cursor: pointer; - font-size: 20px; - font-weight: bold; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.2s; - flex-shrink: 0; - `; - sendBtn.onmouseover = () => (sendBtn.style.transform = "scale(1.05)"); - sendBtn.onmouseout = () => (sendBtn.style.transform = "scale(1)"); + sendBtn.className = "uncloseai-modal-send-btn"; // Handle message sending const sendMessageHandler = async () => { @@ -1097,20 +919,11 @@ async function openUncloseaiEmbeddedModalNew() { // Add user message const userMsg = document.createElement("div"); userMsg.className = "user-message"; - userMsg.style.cssText = ` - align-self: flex-end; - background: ${colors.userMessageBg}; - color: ${colors.userMessageText}; - padding: 12px 16px; - border-radius: 18px 18px 4px 18px; - max-width: 80%; - word-wrap: break-word; - `; // Add message text and actions inside the bubble const messageText = document.createElement("div"); messageText.textContent = message; - messageText.style.cssText = "margin-bottom: 8px;"; + messageText.className = "message-text"; const userMessageActions = document.createElement("div"); userMessageActions.className = "uncloseai-message-actions"; @@ -1158,18 +971,7 @@ async function openUncloseaiEmbeddedModalNew() { const downloadBtn = document.createElement("button"); downloadBtn.textContent = "πŸ’Ύ"; downloadBtn.title = "Download MP3"; - downloadBtn.className = "download-btn"; - downloadBtn.style.cssText = ` - background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.userMessageText}; - transition: all 0.2s; - margin-left: 4px; - `; + downloadBtn.className = "uncloseai-download-btn"; downloadBtn.onclick = async () => { try { const { generateTitleForTTS } = await import("./tts.js"); @@ -1255,16 +1057,8 @@ async function openUncloseaiEmbeddedModalNew() { // Add AI response placeholder const aiMsg = document.createElement("div"); - aiMsg.style.cssText = ` - align-self: flex-start; - background: ${colors.aiMessageBg}; - color: ${colors.aiMessageText}; - padding: 12px 16px; - border-radius: 18px 18px 18px 4px; - max-width: 80%; - box-shadow: 0 1px 2px ${colors.lightShadow}; - `; - aiMsg.innerHTML = `Thinking...`; + aiMsg.className = "ai-message"; + aiMsg.innerHTML = `Thinking...`; chatBox.appendChild(aiMsg); chatArea.scrollTop = chatArea.scrollHeight; @@ -1310,26 +1104,12 @@ async function openUncloseaiEmbeddedModalNew() { // Add TTS and delete buttons to AI message const messageActions = document.createElement("div"); - messageActions.style.cssText = ` - margin-top: 8px; - display: flex; - gap: 8px; - opacity: 0.7; - `; + messageActions.className = "uncloseai-message-actions"; const ttsBtn = document.createElement("button"); ttsBtn.textContent = "πŸ”Š"; ttsBtn.title = "Read aloud"; - ttsBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - `; + ttsBtn.className = "uncloseai-btn-small"; // Store audio for pause/resume functionality let currentAudio = null; let isPlaying = false; @@ -1375,18 +1155,7 @@ async function openUncloseaiEmbeddedModalNew() { const downloadBtn = document.createElement("button"); downloadBtn.textContent = "πŸ’Ύ"; downloadBtn.title = "Download MP3"; - downloadBtn.className = "download-btn"; - downloadBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - margin-left: 4px; - `; + downloadBtn.className = "uncloseai-btn-small"; downloadBtn.onclick = async () => { try { const { generateTitleForTTS } = await import("./tts.js"); @@ -1449,16 +1218,7 @@ async function openUncloseaiEmbeddedModalNew() { const deleteBtn = document.createElement("button"); deleteBtn.textContent = "πŸ—‘οΈ"; deleteBtn.title = "Delete message"; - deleteBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - `; + deleteBtn.className = "uncloseai-btn-small"; deleteBtn.onclick = async () => { // Remove both user and AI messages from history and localStorage const { loadConversationHistory, saveConversationHistory } = await import("./storage.js"); @@ -1484,16 +1244,7 @@ async function openUncloseaiEmbeddedModalNew() { const copyRawBtn = document.createElement("button"); copyRawBtn.textContent = "πŸ“‹"; copyRawBtn.title = "Copy raw (Markdown)"; - copyRawBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - `; + copyRawBtn.className = "uncloseai-btn-small"; copyRawBtn.onclick = async () => { try { await navigator.clipboard.writeText(response); @@ -1511,16 +1262,7 @@ async function openUncloseaiEmbeddedModalNew() { const copyHtmlBtn = document.createElement("button"); copyHtmlBtn.textContent = "πŸ“„"; copyHtmlBtn.title = "Copy HTML"; - copyHtmlBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - `; + copyHtmlBtn.className = "uncloseai-btn-small"; copyHtmlBtn.onclick = async () => { try { const htmlContent = marked.parse(response); @@ -1588,61 +1330,20 @@ async function openUncloseaiEmbeddedModalNew() { root.style.setProperty('--uncloseai-action-border', newColors.actionBorder); root.style.setProperty('--uncloseai-action-text', newColors.primaryText); - // Update modal background and main color - modal.style.background = newColors.modalBackground; - modal.style.color = newColors.primaryText; + // Update CSS variables for dynamic theming + root.style.setProperty('--uncloseai-modal-background', newColors.modalBackground); + root.style.setProperty('--uncloseai-content-background', newColors.contentBackground); + root.style.setProperty('--uncloseai-panel-background', newColors.panelBackground); + root.style.setProperty('--uncloseai-input-background', newColors.inputBackground); + root.style.setProperty('--uncloseai-divider-color', newColors.dividerColor); + root.style.setProperty('--uncloseai-border-color', newColors.borderColor); - // Update chat area background - chatArea.style.background = newColors.contentBackground; + // Update modal theme attribute + modal.setAttribute('data-theme', detectCurrentTheme()); - // Update settings panel background - settingsPanel.style.background = newColors.panelBackground; - settingsPanel.style.borderTopColor = newColors.dividerColor; - settingsPanel.style.borderBottomColor = newColors.dividerColor; + // Control buttons now use CSS classes and variables - // Update input area - inputArea.style.background = newColors.panelBackground; - input.style.background = newColors.inputBackground; - input.style.color = newColors.primaryText; - input.style.borderColor = newColors.borderColor; - - // Update controls panel - controls.style.background = newColors.panelBackground; - controls.style.borderTopColor = newColors.dividerColor; - controls.style.borderBottomColor = newColors.dividerColor; - - // Update all control buttons - controls.querySelectorAll("button").forEach((btn) => { - btn.style.background = newColors.buttonBackground; - btn.style.borderColor = newColors.buttonBorder; - btn.style.color = `${newColors.primaryText} !important`; - - // Re-attach hover handlers with new colors - btn.onmouseover = () => { - btn.style.background = newColors.buttonHover; - btn.style.borderColor = newColors.buttonBorder; - }; - btn.onmouseout = () => { - btn.style.background = newColors.buttonBackground; - btn.style.borderColor = newColors.buttonBorder; - }; - }); - - // Update all message bubbles - chatBox - .querySelectorAll('div[style*="align-self: flex-end"]') - .forEach((userMsg) => { - userMsg.style.background = newColors.userMessageBg; - userMsg.style.color = newColors.userMessageText; - }); - - chatBox - .querySelectorAll('div[style*="align-self: flex-start"]') - .forEach((aiMsg) => { - aiMsg.style.background = newColors.aiMessageBg; - aiMsg.style.color = newColors.aiMessageText; - aiMsg.style.boxShadow = `0 1px 2px ${newColors.lightShadow}`; - }); + // Message bubbles now use CSS classes with theme support }; // Set up theme change listeners @@ -1726,48 +1427,19 @@ You have complete knowledge of this page content and can reference any details, if (msg.role === "user") { const userMsg = document.createElement("div"); userMsg.className = "user-message"; - userMsg.style.cssText = ` - align-self: flex-end; - background: ${colors.userMessageBg}; - color: ${colors.userMessageText}; - padding: 12px 16px; - border-radius: 18px 18px 4px 18px; - max-width: 80%; - word-wrap: break-word; - `; // Add message text and actions inside the bubble const messageText = document.createElement("div"); messageText.textContent = msg.content; - messageText.style.cssText = "margin-bottom: 8px;"; + messageText.className = "message-text"; const userMessageActions = document.createElement("div"); - userMessageActions.style.cssText = ` - display: flex; - gap: 8px; - opacity: 0.8; - justify-content: flex-end; - `; + userMessageActions.className = "uncloseai-message-actions"; const userTtsBtn = document.createElement("button"); userTtsBtn.textContent = "πŸ”Š"; userTtsBtn.title = "Read aloud"; - userTtsBtn.style.cssText = ` - background: ${colors.actionBg}; - border: 1px solid ${colors.actionBorder}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.userMessageText}; - transition: all 0.2s; - `; - userTtsBtn.onmouseenter = () => { - userTtsBtn.style.background = "rgba(255, 255, 255, 0.3)"; - }; - userTtsBtn.onmouseleave = () => { - userTtsBtn.style.background = "rgba(255, 255, 255, 0.2)"; - }; + userTtsBtn.className = "uncloseai-btn-small"; // Store audio for pause/resume functionality let historicalUserAudio = null; @@ -1814,18 +1486,7 @@ You have complete knowledge of this page content and can reference any details, const downloadBtn = document.createElement("button"); downloadBtn.textContent = "πŸ’Ύ"; downloadBtn.title = "Download MP3"; - downloadBtn.className = "download-btn"; - downloadBtn.style.cssText = ` - background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.userMessageText}; - transition: all 0.2s; - margin-left: 4px; - `; + downloadBtn.className = "uncloseai-download-btn"; downloadBtn.onclick = async () => { try { const { generateTitleForTTS } = await import("./tts.js"); @@ -1882,22 +1543,7 @@ You have complete knowledge of this page content and can reference any details, const userDeleteBtn = document.createElement("button"); userDeleteBtn.textContent = "πŸ—‘οΈ"; userDeleteBtn.title = "Delete message"; - userDeleteBtn.style.cssText = ` - background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.userMessageText}; - transition: all 0.2s; - `; - userDeleteBtn.onmouseenter = () => { - userDeleteBtn.style.background = "rgba(255, 255, 255, 0.3)"; - }; - userDeleteBtn.onmouseleave = () => { - userDeleteBtn.style.background = "rgba(255, 255, 255, 0.2)"; - }; + userDeleteBtn.className = "uncloseai-btn-small"; userDeleteBtn.onclick = async () => { // Remove from chat history by index const currentHistory = await loadConversationHistory(); @@ -1926,40 +1572,18 @@ You have complete knowledge of this page content and can reference any details, console.log("Added user message to chatBox"); } else if (msg.role === "assistant") { const aiMsg = document.createElement("div"); - aiMsg.style.cssText = ` - align-self: flex-start; - background: ${colors.aiMessageBg}; - color: ${colors.aiMessageText}; - padding: 12px 16px; - border-radius: 18px 18px 18px 4px; - max-width: 80%; - box-shadow: 0 1px 2px ${colors.lightShadow}; - `; + aiMsg.className = "ai-message"; aiMsg.innerHTML = marked.parse(msg.content); addCodeBlockCopyButtons(aiMsg); // Add TTS and delete buttons to historical AI messages const messageActions = document.createElement("div"); - messageActions.style.cssText = ` - margin-top: 8px; - display: flex; - gap: 8px; - opacity: 0.7; - `; + messageActions.className = "uncloseai-message-actions"; const ttsBtn = document.createElement("button"); ttsBtn.textContent = "πŸ”Š"; ttsBtn.title = "Read aloud"; - ttsBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - `; + ttsBtn.className = "uncloseai-btn-small"; // Store audio for pause/resume functionality let historicalAudio = null; @@ -2008,17 +1632,7 @@ You have complete knowledge of this page content and can reference any details, const downloadBtn = document.createElement("button"); downloadBtn.textContent = "πŸ’Ύ"; downloadBtn.title = "Download MP3"; - downloadBtn.className = "download-btn"; - downloadBtn.style.cssText = ` - background: none; - border: 1px solid #dee2e6; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - transition: all 0.2s; - margin-left: 4px; - `; + downloadBtn.className = "uncloseai-btn-small"; downloadBtn.onclick = async () => { try { const { generateTitleForTTS } = await import("./tts.js"); @@ -2081,15 +1695,7 @@ You have complete knowledge of this page content and can reference any details, const deleteBtn = document.createElement("button"); deleteBtn.textContent = "πŸ—‘οΈ"; deleteBtn.title = "Delete message"; - deleteBtn.style.cssText = ` - background: none; - border: 1px solid #dee2e6; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - transition: all 0.2s; - `; + deleteBtn.className = "uncloseai-btn-small"; deleteBtn.onclick = async () => { // Remove from chat history and localStorage by index const { loadConversationHistory, saveConversationHistory } = await import("./storage.js"); @@ -2111,15 +1717,7 @@ You have complete knowledge of this page content and can reference any details, const copyRawBtn = document.createElement("button"); copyRawBtn.textContent = "πŸ“‹"; copyRawBtn.title = "Copy raw (Markdown)"; - copyRawBtn.style.cssText = ` - background: none; - border: 1px solid #dee2e6; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - transition: all 0.2s; - `; + copyRawBtn.className = "uncloseai-btn-small"; copyRawBtn.onclick = async () => { try { await navigator.clipboard.writeText(msg.content); @@ -2137,15 +1735,7 @@ You have complete knowledge of this page content and can reference any details, const copyHtmlBtn = document.createElement("button"); copyHtmlBtn.textContent = "πŸ“„"; copyHtmlBtn.title = "Copy HTML"; - copyHtmlBtn.style.cssText = ` - background: none; - border: 1px solid #dee2e6; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - transition: all 0.2s; - `; + copyHtmlBtn.className = "uncloseai-btn-small"; copyHtmlBtn.onclick = async () => { try { const htmlContent = marked.parse(msg.content); @@ -2183,15 +1773,7 @@ You have complete knowledge of this page content and can reference any details, const addIntroMessage = async () => { const introMsg = document.createElement("div"); - introMsg.style.cssText = ` - align-self: flex-start; - background: white; - padding: 12px 16px; - border-radius: 18px 18px 18px 4px; - max-width: 80%; - box-shadow: 0 1px 2px rgba(0,0,0,0.1); - color: #495057; - `; + introMsg.className = "ai-message"; // Show loading message first introMsg.innerHTML = @@ -2280,25 +1862,12 @@ You have complete knowledge of this page content and can reference any details, // Add TTS and delete buttons to intro message const messageActions = document.createElement("div"); - messageActions.style.cssText = ` - margin-top: 8px; - display: flex; - gap: 8px; - opacity: 0.7; - `; + messageActions.className = "uncloseai-message-actions"; const ttsBtn = document.createElement("button"); ttsBtn.textContent = "πŸ”Š"; ttsBtn.title = "Read aloud"; - ttsBtn.style.cssText = ` - background: none; - border: 1px solid #dee2e6; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - transition: all 0.2s; - `; + ttsBtn.className = "uncloseai-btn-small"; // Store audio for pause/resume functionality let introAudio = null; @@ -2367,15 +1936,7 @@ You have complete knowledge of this page content and can reference any details, const deleteBtn = document.createElement("button"); deleteBtn.textContent = "πŸ—‘οΈ"; deleteBtn.title = "Delete message"; - deleteBtn.style.cssText = ` - background: none; - border: 1px solid #dee2e6; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - transition: all 0.2s; - `; + deleteBtn.className = "uncloseai-btn-small"; deleteBtn.onclick = async () => { // Remove intro message from localStorage const { loadConversationHistory, saveConversationHistory } = await import("./storage.js"); @@ -2397,16 +1958,7 @@ You have complete knowledge of this page content and can reference any details, const copyRawBtn = document.createElement("button"); copyRawBtn.textContent = "πŸ“‹"; copyRawBtn.title = "Copy raw (Markdown)"; - copyRawBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - `; + copyRawBtn.className = "uncloseai-btn-small"; copyRawBtn.onclick = async () => { try { await navigator.clipboard.writeText(response); @@ -2424,16 +1976,7 @@ You have complete knowledge of this page content and can reference any details, const copyHtmlBtn = document.createElement("button"); copyHtmlBtn.textContent = "πŸ“„"; copyHtmlBtn.title = "Copy HTML"; - copyHtmlBtn.style.cssText = ` - background: ${colors.buttonBackground}; - border: 1px solid ${colors.borderColor}; - border-radius: 4px; - padding: 4px 8px; - cursor: pointer; - font-size: 12px; - color: ${colors.primaryText}; - transition: all 0.2s; - `; + copyHtmlBtn.className = "uncloseai-btn-small"; copyHtmlBtn.onclick = async () => { try { const htmlContent = marked.parse(response); diff --git a/src/uncloseai-modal-builtin.css b/src/uncloseai-modal-builtin.css index 43ee651..158bfaa 100644 --- a/src/uncloseai-modal-builtin.css +++ b/src/uncloseai-modal-builtin.css @@ -94,6 +94,7 @@ dialog#uncloseai-embedded-modal[data-theme="light"] { overflow-y: auto; padding: 16px; -webkit-overflow-scrolling: touch; + background: var(--uncloseai-content-background, #f8f9fa); } /* Settings panel */ @@ -307,6 +308,60 @@ dialog#translate-modal { display: block; } +.translate-progress-text { + font-weight: bold; + color: #4CAF50; + margin-bottom: 10px; +} + +.translate-eta-text { + color: #666; + margin-bottom: 15px; +} + +.translate-status-text { + color: #777; + font-size: 0.9em; +} + +.translate-notice { + background: #e7f3ff; + padding: 10px; + border-radius: 5px; + margin: 10px 0; + border-left: 4px solid #007bff; +} + +.translate-ai-notice { + position: fixed; + top: 10px; + right: 10px; + background: #4CAF50; + color: white; + padding: 8px 12px; + border-radius: 4px; + font-size: 12px; + z-index: 10000; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +.translate-raw-toggle-btn { + padding: 8px 16px; + background: #666; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.translate-result-area { + white-space: normal; +} + +.translate-result-area.pre-wrap { + white-space: pre-wrap; +} + .translate-tab-nav li { flex: 1; margin: 0; @@ -481,6 +536,10 @@ dialog#tts-modal { display: none; } +.tts-result.show { + display: block; +} + /* TTS time display */ .tts-time-display { font-size: 12px; @@ -683,18 +742,18 @@ dialog#tts-modal { .uncloseai-input { width: 100%; padding: 8px 12px; - border: 1px solid #dee2e6; + border: 1px solid var(--uncloseai-button-border, #dee2e6); border-radius: 6px; font-size: 14px; - background: #ffffff; - color: #495057; + background: var(--uncloseai-button-background, #ffffff); + color: var(--uncloseai-primary-text, #495057); } .uncloseai-label { display: block; margin-bottom: 8px; font-weight: 600; - color: #495057; + color: var(--uncloseai-primary-text, #495057); } .uncloseai-section { @@ -738,6 +797,329 @@ dialog#tts-modal { justify-content: flex-end; } +/* =================================================================== */ +/* EMBED MODAL SPECIFIC STYLES */ +/* =================================================================== */ + +/* Main modal container and layout */ +.uncloseai-modal-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.uncloseai-modal-header { + flex-shrink: 0; + padding: 16px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.uncloseai-modal-title { + margin: 0; + font-weight: 600; +} + +.uncloseai-modal-title-mobile { + font-size: 18px; +} + +.uncloseai-modal-title-desktop { + font-size: 20px; +} + +.uncloseai-modal-subtitle { + font-size: 12px; + opacity: 0.9; + font-weight: normal; + line-height: 1.3; +} + +.uncloseai-modal-subtitle-dim { + opacity: 0.8; +} + +/* Chat area */ +.uncloseai-modal-chat-area { + flex: 1; + overflow-y: auto; + padding: 16px; + -webkit-overflow-scrolling: touch; + background: var(--uncloseai-content-background, #f8f9fa); +} + +.uncloseai-chat-box { + max-width: 100%; + display: flex; + flex-direction: column; + gap: 12px; +} + +/* Settings panel */ +.uncloseai-modal-settings { + flex-shrink: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 16px; + display: none; + max-height: 40vh; + overflow-y: auto; +} + +.uncloseai-modal-settings.open { + display: block; +} + +/* Form inputs in modal */ +.uncloseai-modal-select { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--uncloseai-button-border, #dee2e6); + border-radius: 6px; + font-size: 14px; + background: var(--uncloseai-button-background, #ffffff); + color: var(--uncloseai-primary-text, #495057); +} + +/* Action grid and controls */ +.uncloseai-modal-controls { + flex-shrink: 0; + padding: 8px 16px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + display: flex; + gap: 8px; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +/* Input area */ +.uncloseai-modal-input-area { + flex-shrink: 0; + padding: 16px; + display: flex; + gap: 12px; + align-items: flex-end; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); +} + +/* Chat input */ +.uncloseai-modal-input { + flex: 1; + min-height: 44px; + max-height: 120px; + padding: 12px; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 24px; + resize: none; + font-size: 16px; + font-family: inherit; + line-height: 1.4; + outline: none; + transition: border-color 0.2s; + background: var(--uncloseai-button-background, #ffffff); + color: var(--uncloseai-primary-text, #495057); +} + +.uncloseai-modal-input:focus { + border-color: #667eea; +} + +/* Send button */ +.uncloseai-modal-send-btn { + width: 44px; + height: 44px; + padding: 0; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 50%; + cursor: pointer; + font-size: 20px; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.2s; + flex-shrink: 0; +} + +.uncloseai-modal-send-btn:hover { + transform: scale(1.05); +} + +/* Messages */ +.user-message { + align-self: flex-end; + background: #007bff; + color: white; + padding: 12px 16px; + border-radius: 18px 18px 4px 18px; + max-width: 80%; + word-wrap: break-word; +} + +.ai-message { + align-self: flex-start; + background: white; + color: #495057; + padding: 12px 16px; + border-radius: 18px 18px 18px 4px; + max-width: 80%; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.message-text { + margin-bottom: 8px; +} + +/* Dark theme message colors */ +dialog#uncloseai-embedded-modal[data-theme="dark"] .ai-message { + background: #2d3748; + color: #e2e8f0; +} + +dialog#uncloseai-embedded-modal[data-theme="dark"] .user-message { + background: #667eea; + color: white; +} + +/* Read page container */ +.uncloseai-read-page-container { + display: inline-flex; + position: relative; + margin-right: 8px; +} + +/* Translate dropdown */ +.translate-dropdown { + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + max-height: 300px; + overflow-y: auto; + margin-top: 4px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + z-index: 1000; + background: var(--uncloseai-button-background, white); + border: 1px solid var(--uncloseai-button-border, #ddd); +} + +.translate-dropdown-current { + padding: 8px 12px; + color: var(--uncloseai-primary-text, #333); + background: var(--uncloseai-button-background, #f5f5f5); + font-weight: bold; + font-size: 14px; +} + +.translate-dropdown-option { + padding: 8px 12px; + cursor: pointer; + transition: background-color 0.2s; + font-size: 14px; + line-height: 1.4; + color: var(--uncloseai-primary-text, #333); +} + +.translate-dropdown-option:hover { + background: var(--uncloseai-button-hover, #f5f5f5); +} + +.translate-dropdown-option-disabled { + pointer-events: none; +} + +/* Download button */ +.uncloseai-download-btn { + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 4px; + color: white; + cursor: pointer; + font-size: 12px; + margin-left: 8px; + padding: 4px 8px; + text-decoration: none; + transition: all 0.2s; +} + +.uncloseai-download-btn:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* Page reader status div */ +.uncloseai-page-reader-status { + position: fixed; + top: 10px; + right: 10px; + padding: 10px; + background: rgba(0, 0, 0, 0.8); + color: white; + border-radius: 5px; + z-index: 1000; +} + +/* Button with left margin */ +.uncloseai-btn-margin-left { + margin-left: 10px; +} + +/* =================================================================== */ +/* UI.JS COMPONENT STYLES */ +/* =================================================================== */ + +/* Container styles */ +.uncloseai-ui-container { + width: 100%; + margin: 10px 0; +} + +/* Button margin utilities */ +.uncloseai-ui-button-margin { + margin: 10px; +} + +/* Floating AI button */ +.uncloseai-floating-button { + position: fixed; + bottom: 20px; + right: 10px; + width: 110px; + height: 55px; + border-radius: 22px; + font-family: 'ChunkFiveRegular', monospace; + font-size: 13px; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + z-index: 1000; + transition: all 0.3s ease; + max-width: calc(100vw - 20px); + box-sizing: border-box; + /* Dynamic colors set by JavaScript */ + background: var(--floating-button-bg, #000000); + border: 2px solid var(--floating-button-border, #ffffff); + color: var(--floating-button-text, #ffffff); +} + +.uncloseai-floating-button:hover { + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(0,0,0,0.4); +} + +.uncloseai-floating-button.loading { + opacity: 0.7; + cursor: wait; +} + /* =================================================================== */ /* WIDGET LIBRARY STYLES */ /* =================================================================== */ @@ -793,6 +1175,10 @@ dialog#tts-modal { overflow-y: auto; } +.widget-feature-container.show { + display: block; +} + .widget-tts-container { width: 100%; height: 80px; @@ -811,8 +1197,17 @@ dialog#tts-modal { border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); z-index: 1000; - background: white; - border: 1px solid #ddd; + background: var(--uncloseai-content-background, white); + border: 1px solid var(--uncloseai-divider-color, #ddd); +} + +.widget-translate-dropdown-current { + padding: 8px 12px; + color: var(--uncloseai-muted-text, #666); + border-bottom: 1px solid var(--uncloseai-divider-color, #ddd); + background: var(--uncloseai-content-background, #f5f5f5); + font-weight: bold; + font-size: 14px; } .widget-translate-dropdown .option { @@ -821,8 +1216,13 @@ dialog#tts-modal { transition: background-color 0.2s; font-size: 14px; line-height: 1.4; + color: var(--uncloseai-primary-text, #333); } .widget-translate-dropdown .option:hover { - background: #f5f5f5; + background: var(--uncloseai-button-hover, #f5f5f5); +} + +.widget-translate-dropdown .option-disabled { + pointer-events: none; } \ No newline at end of file diff --git a/src/uncloseai-modal-pico.css b/src/uncloseai-modal-pico.css index 342f320..fc441ca 100644 --- a/src/uncloseai-modal-pico.css +++ b/src/uncloseai-modal-pico.css @@ -339,6 +339,60 @@ dialog#translate-modal { display: block; } +.translate-progress-text { + font-weight: bold; + color: #4CAF50; + margin-bottom: 10px; +} + +.translate-eta-text { + color: #666; + margin-bottom: 15px; +} + +.translate-status-text { + color: #777; + font-size: 0.9em; +} + +.translate-notice { + background: #e7f3ff; + padding: 10px; + border-radius: 5px; + margin: 10px 0; + border-left: 4px solid #007bff; +} + +.translate-ai-notice { + position: fixed; + top: 10px; + right: 10px; + background: #4CAF50; + color: white; + padding: 8px 12px; + border-radius: 4px; + font-size: 12px; + z-index: 10000; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +.translate-raw-toggle-btn { + padding: 8px 16px; + background: #666; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.translate-result-area { + white-space: normal; +} + +.translate-result-area.pre-wrap { + white-space: pre-wrap; +} + .translate-tab-nav li { flex: 1; margin: 0; @@ -532,6 +586,10 @@ dialog#tts-modal { display: none; } +.tts-result.show { + display: block !important; +} + /* TTS time display */ .tts-time-display { font-size: 12px; @@ -734,18 +792,18 @@ dialog#tts-modal { .uncloseai-input { width: 100%; padding: 8px 12px; - border: 1px solid #dee2e6; + border: 1px solid var(--uncloseai-button-border, #dee2e6); border-radius: 6px; font-size: 14px; - background: #ffffff; - color: #495057; + background: var(--uncloseai-button-background, #ffffff); + color: var(--uncloseai-primary-text, #495057); } .uncloseai-label { display: block; margin-bottom: 8px; font-weight: 600; - color: #495057; + color: var(--uncloseai-primary-text, #495057); } .uncloseai-section { @@ -789,6 +847,328 @@ dialog#tts-modal { justify-content: flex-end; } +/* =================================================================== */ +/* EMBED MODAL SPECIFIC STYLES - PICOCSS OVERRIDES */ +/* =================================================================== */ + +/* Main modal container and layout */ +.uncloseai-modal-container { + width: 100% !important; + height: 100% !important; + display: flex !important; + flex-direction: column !important; + overflow: hidden !important; +} + +.uncloseai-modal-header { + flex-shrink: 0 !important; + padding: 16px !important; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; + color: white !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; +} + +.uncloseai-modal-title { + margin: 0 !important; + font-weight: 600 !important; +} + +.uncloseai-modal-title-mobile { + font-size: 18px !important; +} + +.uncloseai-modal-title-desktop { + font-size: 20px !important; +} + +.uncloseai-modal-subtitle { + font-size: 12px !important; + opacity: 0.9 !important; + font-weight: normal !important; + line-height: 1.3 !important; +} + +.uncloseai-modal-subtitle-dim { + opacity: 0.8 !important; +} + +/* Chat area */ +.uncloseai-modal-chat-area { + flex: 1 !important; + overflow-y: auto !important; + padding: 16px !important; + -webkit-overflow-scrolling: touch !important; +} + +.uncloseai-chat-box { + max-width: 100% !important; + display: flex !important; + flex-direction: column !important; + gap: 12px !important; +} + +/* Settings panel */ +.uncloseai-modal-settings { + flex-shrink: 0 !important; + border-top: 1px solid rgba(0, 0, 0, 0.1) !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important; + padding: 16px !important; + display: none !important; + max-height: 40vh !important; + overflow-y: auto !important; +} + +.uncloseai-modal-settings.open { + display: block !important; +} + +/* Form inputs in modal */ +.uncloseai-modal-select { + width: 100% !important; + padding: 8px 12px !important; + border: 1px solid var(--uncloseai-button-border, #dee2e6) !important; + border-radius: 6px !important; + font-size: 14px !important; + background: var(--uncloseai-button-background, #ffffff) !important; + color: var(--uncloseai-primary-text, #495057) !important; +} + +/* Action grid and controls */ +.uncloseai-modal-controls { + flex-shrink: 0 !important; + padding: 8px 16px !important; + border-top: 1px solid rgba(0, 0, 0, 0.1) !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important; + display: flex !important; + gap: 8px !important; + overflow-x: auto !important; + -webkit-overflow-scrolling: touch !important; +} + +/* Input area */ +.uncloseai-modal-input-area { + flex-shrink: 0 !important; + padding: 16px !important; + display: flex !important; + gap: 12px !important; + align-items: flex-end !important; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1) !important; +} + +/* Chat input */ +.uncloseai-modal-input { + flex: 1 !important; + min-height: 44px !important; + max-height: 120px !important; + padding: 12px !important; + border: 1px solid rgba(0, 0, 0, 0.2) !important; + border-radius: 24px !important; + resize: none !important; + font-size: 16px !important; + font-family: inherit !important; + line-height: 1.4 !important; + outline: none !important; + transition: border-color 0.2s !important; + background: var(--uncloseai-button-background, #ffffff) !important; + color: var(--uncloseai-primary-text, #495057) !important; +} + +.uncloseai-modal-input:focus { + border-color: #667eea !important; +} + +/* Send button */ +.uncloseai-modal-send-btn { + width: 44px !important; + height: 44px !important; + padding: 0 !important; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; + color: white !important; + border: none !important; + border-radius: 50% !important; + cursor: pointer !important; + font-size: 20px !important; + font-weight: bold !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + transition: transform 0.2s !important; + flex-shrink: 0 !important; +} + +.uncloseai-modal-send-btn:hover { + transform: scale(1.05) !important; +} + +/* Messages */ +.user-message { + align-self: flex-end !important; + background: #007bff !important; + color: white !important; + padding: 12px 16px !important; + border-radius: 18px 18px 4px 18px !important; + max-width: 80% !important; + word-wrap: break-word !important; +} + +.ai-message { + align-self: flex-start !important; + background: white !important; + color: #495057 !important; + padding: 12px 16px !important; + border-radius: 18px 18px 18px 4px !important; + max-width: 80% !important; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important; +} + +.message-text { + margin-bottom: 8px !important; +} + +/* Dark theme message colors */ +dialog#uncloseai-embedded-modal[data-theme="dark"] .ai-message { + background: #2d3748 !important; + color: #e2e8f0 !important; +} + +dialog#uncloseai-embedded-modal[data-theme="dark"] .user-message { + background: #667eea !important; + color: white !important; +} + +/* Read page container */ +.uncloseai-read-page-container { + display: inline-flex !important; + position: relative !important; + margin-right: 8px !important; +} + +/* Translate dropdown */ +.translate-dropdown { + position: absolute !important; + top: 100% !important; + left: 0 !important; + min-width: 200px !important; + max-height: 300px !important; + overflow-y: auto !important; + margin-top: 4px !important; + border-radius: 8px !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important; + z-index: 1000 !important; + background: var(--uncloseai-button-background, white) !important; + border: 1px solid var(--uncloseai-button-border, #ddd) !important; +} + +.translate-dropdown-current { + padding: 8px 12px !important; + color: var(--uncloseai-primary-text, #333) !important; + background: var(--uncloseai-button-background, #f5f5f5) !important; + font-weight: bold !important; + font-size: 14px !important; +} + +.translate-dropdown-option { + padding: 8px 12px !important; + cursor: pointer !important; + transition: background-color 0.2s !important; + font-size: 14px !important; + line-height: 1.4 !important; + color: var(--uncloseai-primary-text, #333) !important; +} + +.translate-dropdown-option:hover { + background: var(--uncloseai-button-hover, #f5f5f5) !important; +} + +.translate-dropdown-option-disabled { + pointer-events: none !important; +} + +/* Download button */ +.uncloseai-download-btn { + background: rgba(255, 255, 255, 0.2) !important; + border: 1px solid rgba(255, 255, 255, 0.3) !important; + border-radius: 4px !important; + color: white !important; + cursor: pointer !important; + font-size: 12px !important; + margin-left: 8px !important; + padding: 4px 8px !important; + text-decoration: none !important; + transition: all 0.2s !important; +} + +.uncloseai-download-btn:hover { + background: rgba(255, 255, 255, 0.3) !important; +} + +/* Page reader status div */ +.uncloseai-page-reader-status { + position: fixed !important; + top: 10px !important; + right: 10px !important; + padding: 10px !important; + background: rgba(0, 0, 0, 0.8) !important; + color: white !important; + border-radius: 5px !important; + z-index: 1000 !important; +} + +/* Button with left margin */ +.uncloseai-btn-margin-left { + margin-left: 10px !important; +} + +/* =================================================================== */ +/* UI.JS COMPONENT STYLES - PICOCSS OVERRIDES */ +/* =================================================================== */ + +/* Container styles */ +.uncloseai-ui-container { + width: 100%; + margin: 10px 0; +} + +/* Button margin utilities */ +.uncloseai-ui-button-margin { + margin: 10px; +} + +/* Floating AI button */ +.uncloseai-floating-button { + position: fixed; + bottom: 20px; + right: 10px; + width: 110px; + height: 55px; + border-radius: 22px; + font-family: 'ChunkFiveRegular', monospace; + font-size: 13px; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + z-index: 1000; + transition: all 0.3s ease; + max-width: calc(100vw - 20px); + box-sizing: border-box; + /* Dynamic colors set by JavaScript */ + background: var(--floating-button-bg, #000000); + border: 2px solid var(--floating-button-border, #ffffff); + color: var(--floating-button-text, #ffffff); +} + +.uncloseai-floating-button:hover { + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(0,0,0,0.4); +} + +.uncloseai-floating-button.loading { + opacity: 0.7; + cursor: wait; +} + /* =================================================================== */ /* WIDGET LIBRARY STYLES - PICOCSS OVERRIDES */ /* =================================================================== */ diff --git a/src/widget-library.js b/src/widget-library.js index fec6bf3..26f6e05 100644 --- a/src/widget-library.js +++ b/src/widget-library.js @@ -53,7 +53,7 @@ export function createFullInterface(container) { const fileInput = document.createElement("input"); fileInput.type = "file"; fileInput.setAttribute("data-uncloseai-file-input", ""); - fileInput.style.display = "none"; + fileInput.style.display = "none"; // Hidden file input fileInput.onchange = handleFileUpload; container.appendChild(chatContainer); @@ -239,7 +239,7 @@ export async function handleUploadFromElement(button) { button.disabled = true; button.textContent = getUIText("processingText"); - resultDiv.style.display = "block"; + resultDiv.classList.add("show"); resultDiv.innerHTML = "Uploading and analyzing file..."; try { @@ -287,15 +287,11 @@ export async function handleSmartTranslate(button) { // Create dropdown const dropdown = document.createElement("div"); dropdown.className = "widget-translate-dropdown"; - dropdown.style.background = colors.panelBackground; - dropdown.style.borderColor = colors.borderColor; - dropdown.style.boxShadow = `0 4px 12px ${colors.shadowColor}`; + // Styling handled by CSS class // Add current language indicator const currentLangDiv = document.createElement("div"); - currentLangDiv.style.color = colors.mutedText; - currentLangDiv.style.borderBottom = `1px solid ${colors.dividerColor}`; - currentLangDiv.style.background = colors.contentBackground; + currentLangDiv.className = "widget-translate-dropdown-current"; const currentLangName = NATIVE_LANGUAGE_NAMES[currentLang] || SUPPORTED_LANGUAGES[currentLang] || @@ -314,7 +310,7 @@ export async function handleSmartTranslate(button) { const option = document.createElement("div"); option.className = "option"; - option.style.color = colors.primaryText; + // Color handled by CSS class // Format: "es β€’ Spanish β€’ EspaΓ±ol" option.innerHTML = ` @@ -325,12 +321,7 @@ export async function handleSmartTranslate(button) { ${nativeName} `; - option.onmouseenter = () => { - option.style.background = colors.buttonHover; - }; - option.onmouseleave = () => { - option.style.background = "transparent"; - }; + // Hover effects handled by CSS option.onclick = async () => { try { @@ -340,7 +331,7 @@ export async function handleSmartTranslate(button) { β€’ ${getUIText("translatingTo")} `; - option.style.pointerEvents = "none"; + option.className = "option option-disabled"; // Translate the page const translatedHtml = await translateCurrentPage(code); @@ -363,15 +354,14 @@ export async function handleSmartTranslate(button) { β€’ ${nativeName} `; - option.style.pointerEvents = "auto"; + option.className = "option"; } }; dropdown.appendChild(option); }); - // Position dropdown relative to button - button.style.position = "relative"; + // Position handled by CSS button.parentElement.appendChild(dropdown); // Close dropdown when clicking outside