feat: complete mobile-first hermes modal redesign

- Full screen on mobile, centered modal on desktop
- Simple flex layout instead of complex grid
- Modern chat bubble design
- Auto-resizing input with proper mobile keyboard handling
- Minimal UI - only essential controls
- Better touch targets and mobile interactions
This commit is contained in:
Russell Ballestrini 2025-07-02 07:01:17 -04:00
parent e972cbfd0a
commit 3b2f1acf86

319
src/ui.js
View file

@ -467,6 +467,323 @@ function setupFloatingButtonHandler(floatingButton) {
};
}
// New mobile-first Hermes modal
async function openHermesModalNew() {
const modal = document.createElement("dialog");
modal.id = "hermes-modal";
// Mobile-first: full screen on mobile, centered on desktop
const isMobile = window.innerWidth <= 768;
modal.style.cssText = \`
position: fixed;
margin: 0;
padding: 0;
border: none;
background: white;
z-index: 2000;
\${isMobile ? \`
top: 0;
left: 0;
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
border-radius: 0;
\` : \`
width: 90vw;
max-width: 800px;
height: 90vh;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
\`}
\`;
// Simple container with flex layout
const container = document.createElement("div");
container.style.cssText = \`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
\`;
// 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 rgba(0,0,0,0.1);
\`;
const title = document.createElement("h2");
title.textContent = "🤖 Hermes AI";
title.style.cssText = \`
margin: 0;
font-size: \${isMobile ? '18px' : '20px'};
font-weight: 600;
\`;
const closeBtn = document.createElement("button");
closeBtn.textContent = "✕";
closeBtn.style.cssText = \`
background: rgba(255,255,255,0.2);
border: none;
color: white;
font-size: 20px;
width: 36px;
height: 36px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
\`;
closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255,255,255,0.3)';
closeBtn.onmouseout = () => closeBtn.style.background = 'rgba(255,255,255,0.2)';
closeBtn.onclick = () => {
modal.close();
document.body.removeChild(modal);
hermesModalOpen = false;
};
header.appendChild(title);
header.appendChild(closeBtn);
// Chat area
const chatArea = document.createElement("div");
chatArea.style.cssText = \`
flex: 1;
overflow-y: auto;
padding: 16px;
background: #f8f9fa;
-webkit-overflow-scrolling: touch;
\`;
const chatBox = document.createElement("div");
chatBox.id = "modal-chat-box";
chatBox.style.cssText = \`
max-width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
\`;
chatArea.appendChild(chatBox);
// Quick action buttons (minimal on mobile)
if (!isMobile) {
const controls = document.createElement("div");
controls.style.cssText = \`
flex-shrink: 0;
padding: 8px 16px;
background: white;
border-top: 1px solid #e9ecef;
border-bottom: 1px solid #e9ecef;
display: flex;
gap: 8px;
overflow-x: auto;
\`;
const actions = [
{ text: "📖 Read", action: () => window.readPageWithHermes() },
{ text: "🔊 TTS", action: () => window.openTTSModal() },
{ text: "🌐 Translate", action: () => window.openTranslateModal() }
];
actions.forEach(({ text, action }) => {
const btn = document.createElement("button");
btn.textContent = text;
btn.style.cssText = \`
padding: 8px 16px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 20px;
cursor: pointer;
white-space: nowrap;
font-size: 14px;
transition: all 0.2s;
\`;
btn.onmouseover = () => {
btn.style.background = '#e9ecef';
btn.style.borderColor = '#adb5bd';
};
btn.onmouseout = () => {
btn.style.background = '#f8f9fa';
btn.style.borderColor = '#dee2e6';
};
btn.onclick = action;
controls.appendChild(btn);
});
container.appendChild(header);
container.appendChild(chatArea);
container.appendChild(controls);
} else {
container.appendChild(header);
container.appendChild(chatArea);
}
// Input area
const inputArea = document.createElement("div");
inputArea.style.cssText = \`
flex-shrink: 0;
padding: 16px;
background: white;
display: flex;
gap: 12px;
align-items: flex-end;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
\`;
const input = document.createElement("textarea");
input.id = "modal-user-input";
input.placeholder = "Ask about this page...";
input.style.cssText = \`
flex: 1;
min-height: 44px;
max-height: 120px;
padding: 12px;
border: 1px solid #dee2e6;
border-radius: 24px;
resize: none;
font-size: 16px;
font-family: inherit;
line-height: 1.4;
outline: none;
transition: border-color 0.2s;
\`;
input.onfocus = () => input.style.borderColor = '#667eea';
input.onblur = () => input.style.borderColor = '#dee2e6';
// Auto-resize textarea
input.oninput = () => {
input.style.height = 'auto';
input.style.height = Math.min(input.scrollHeight, 120) + 'px';
};
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)';
// Handle message sending
const sendMessage = async () => {
const message = input.value.trim();
if (!message) return;
input.value = '';
input.style.height = '44px';
// Add user message
const userMsg = document.createElement("div");
userMsg.style.cssText = \`
align-self: flex-end;
background: #667eea;
color: white;
padding: 12px 16px;
border-radius: 18px 18px 4px 18px;
max-width: 80%;
word-wrap: break-word;
\`;
userMsg.textContent = message;
chatBox.appendChild(userMsg);
// Add AI response placeholder
const aiMsg = document.createElement("div");
aiMsg.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);
\`;
aiMsg.innerHTML = '<em style="color: #6c757d;">Thinking...</em>';
chatBox.appendChild(aiMsg);
chatArea.scrollTop = chatArea.scrollHeight;
try {
let response = '';
for await (const chunk of sendMessage(message)) {
response += chunk;
aiMsg.innerHTML = marked.parse(response);
}
} catch (error) {
aiMsg.innerHTML = \`<span style="color: #dc3545;">Error: \${error.message}</span>\`;
}
chatArea.scrollTop = chatArea.scrollHeight;
};
sendBtn.onclick = sendMessage;
input.onkeydown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
inputArea.appendChild(input);
inputArea.appendChild(sendBtn);
container.appendChild(inputArea);
modal.appendChild(container);
document.body.appendChild(modal);
modal.showModal();
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.close();
document.body.removeChild(modal);
hermesModalOpen = false;
}
});
input.focus();
// Add intro message
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.innerHTML = \`👋 Hi! I'm Hermes AI. I can help you understand this page, answer questions, or assist with various tasks. How can I help you today?\`;
chatBox.appendChild(introMsg);
}
// Function to toggle Hermes modal
export async function toggleHermesModal() {
const existingModal = document.getElementById("hermes-modal");
@ -474,7 +791,7 @@ export async function toggleHermesModal() {
document.body.removeChild(existingModal);
hermesModalOpen = false;
} else {
await openHermesModal();
await openHermesModalNew();
hermesModalOpen = true;
}
}