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 = `
+
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