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
This commit is contained in:
Russell Ballestrini 2025-07-09 21:32:55 -04:00
parent 4561b9edcc
commit 3f9cb2c40f
11 changed files with 905 additions and 665 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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");

View file

@ -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 = `
<div style="margin-bottom: 15px;">
<button onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.textContent = this.textContent.includes('Raw') ? 'Show Raw HTML' : 'Hide Raw HTML'" style="padding: 8px 16px; background: #666; color: white; border: none; border-radius: 4px; cursor: pointer;">Show Raw HTML</button>
<button onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.textContent = this.textContent.includes('Raw') ? 'Show Raw HTML' : 'Hide Raw HTML'" class="translate-raw-toggle-btn">Show Raw HTML</button>
<div style="display: none; margin: 10px 0; padding: 15px; background: #f0f0f0; border-radius: 4px; white-space: pre-wrap; font-family: monospace; font-size: 0.85em; max-height: 300px; overflow-y: auto; border: 1px solid #ddd;">${translatedText.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</div>
</div>
<div style="border: 2px solid #4CAF50; border-radius: 8px; background: #f0fff0; padding: 15px; text-align: center;">
@ -395,13 +393,13 @@ ${preservedText}`;
${previewWindow ? "" : '<p style="margin: 10px 0 0 0; color: #d32f2f;"><strong>Note:</strong> Please allow popups for preview window</p>'}
</div>
`;
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);

View file

@ -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)

View file

@ -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");
}
};
}

View file

@ -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 =
"🤖 <span style=\"font-family: 'ChunkFiveRegular', monospace;\">uncloseai.</span>";
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 = `
<div style="font-size: 12px; opacity: 0.9; font-weight: normal; line-height: 1.3;">
<div class="uncloseai-modal-subtitle">
<span style="font-family: 'ChunkFiveRegular', monospace;">uncloseai.</span> ${getUIText("hermesIntro")}<br>
<span style="opacity: 0.8;">${getUIText("discussingPage")}: ${pageTitle}</span>
<span class="uncloseai-modal-subtitle-dim">${getUIText("discussingPage")}: ${pageTitle}</span>
</div>
`;
@ -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 = `<em style="color: ${colors.mutedText};">Thinking...</em>`;
aiMsg.className = "ai-message";
aiMsg.innerHTML = `<em>Thinking...</em>`;
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);

View file

@ -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;
}

View file

@ -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 */
/* =================================================================== */

View file

@ -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 = "<em>Uploading and analyzing file...</em>";
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) {
<span style="font-weight: 500;">${nativeName}</span>
`;
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) {
<span style="color: ${colors.mutedText};">•</span>
<span style="color: ${colors.secondaryText};">${getUIText("translatingTo")}</span>
`;
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) {
<span style="color: ${colors.mutedText};">•</span>
<span style="font-weight: 500;">${nativeName}</span>
`;
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