feat: cog menu for layout modes, mobile viewport fix, better separation

Header UX:
- Replace 5 icon buttons with single cog (gear) dropdown menu
- Cog menu shows all layout options with icons + labels
- Help and Close buttons stay visible outside the cog
- Active mode highlighted in green

Mobile fixes:
- Fix viewport overflow (100vw + max-width: 100vw)
- Stronger shadow (0 -4px 20px) for clear CV/chat separation
- Rounded corners (12px) on top for recognizable chat window
- Hide desktop-only modes (side panel, floating, full) from cog on mobile
- max-height: 50vh ensures CV always visible above

Dark mode:
- Cog menu styled for dark backgrounds
This commit is contained in:
juanatsap
2026-04-09 18:39:51 +01:00
parent ef25a9e233
commit 0b672447f6
2 changed files with 147 additions and 77 deletions
+104 -57
View File
@@ -224,12 +224,12 @@
font-size: 1.1rem; font-size: 1.1rem;
} }
/* Header actions — icon buttons with tooltips */ /* Header actions — cog menu + help + close */
.chat-header-actions { .chat-header-actions {
margin-left: auto; margin-left: auto;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1px; gap: 2px;
} }
.chat-mode-btn { .chat-mode-btn {
@@ -244,7 +244,6 @@
justify-content: center; justify-content: center;
border-radius: 4px; border-radius: 4px;
transition: all 0.15s; transition: all 0.15s;
position: relative;
} }
.chat-mode-btn:hover { .chat-mode-btn:hover {
@@ -252,36 +251,60 @@
background: rgba(255,255,255,0.15); background: rgba(255,255,255,0.15);
} }
.chat-mode-btn.active { /* Cog dropdown wrapper */
color: #fff; .chat-cog-wrapper {
background: rgba(255,255,255,0.2); position: relative;
} }
/* Native tooltip via title attr — enhanced with CSS for consistent look */ .chat-cog-menu {
.chat-mode-btn[title]:hover::after { display: none;
content: attr(title);
position: absolute; position: absolute;
top: calc(100% + 6px); top: calc(100% + 6px);
left: 50%; right: 0;
transform: translateX(-50%); background: var(--paper-bg, #fff);
background: var(--black-bar, #2b2b2b); border: 1px solid var(--border-light, #e0e0e0);
color: #fff; border-radius: 8px;
padding: 4px 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2);
border-radius: 4px; padding: 4px;
font-size: 0.65rem; z-index: 1002;
font-family: 'Source Sans Pro', sans-serif; min-width: 140px;
font-weight: 400;
white-space: nowrap;
z-index: 1001;
pointer-events: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
} }
.chat-header-divider { .chat-cog-menu.chat-cog-open {
width: 1px; display: flex;
height: 16px; flex-direction: column;
background: rgba(255,255,255,0.25); }
margin: 0 3px;
.chat-cog-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border: none;
background: none;
border-radius: 5px;
font-size: 0.72rem;
font-family: 'Source Sans Pro', sans-serif;
color: var(--text-secondary, #333);
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.chat-cog-item:hover {
background: var(--accent-green, #27ae60);
color: #fff;
}
.chat-cog-item.active {
background: rgba(39, 174, 96, 0.12);
color: var(--accent-green, #27ae60);
font-weight: 600;
}
.chat-cog-item iconify-icon {
font-size: 0.9rem;
flex-shrink: 0;
} }
/* ========================================================================== /* ==========================================================================
@@ -751,6 +774,20 @@
border-bottom-color: #4eca81; border-bottom-color: #4eca81;
} }
[data-color-theme="dark"] .chat-cog-menu {
background: #333;
border-color: #555;
}
[data-color-theme="dark"] .chat-cog-item {
color: #ddd;
}
[data-color-theme="dark"] .chat-cog-item.active {
background: rgba(78, 202, 129, 0.15);
color: #4eca81;
}
/* Auto dark (system preference) */ /* Auto dark (system preference) */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
[data-color-theme="auto"] .chat-panel { [data-color-theme="auto"] .chat-panel {
@@ -787,6 +824,20 @@
color: #4eca81; color: #4eca81;
border-bottom-color: #4eca81; border-bottom-color: #4eca81;
} }
[data-color-theme="auto"] .chat-cog-menu {
background: #333;
border-color: #555;
}
[data-color-theme="auto"] .chat-cog-item {
color: #ddd;
}
[data-color-theme="auto"] .chat-cog-item.active {
background: rgba(78, 202, 129, 0.15);
color: #4eca81;
}
} }
/* ========================================================================== /* ==========================================================================
@@ -794,14 +845,17 @@
========================================================================== */ ========================================================================== */
@media (max-width: 480px) { @media (max-width: 480px) {
/* Default compact: bottom sheet */ /* Default compact: bottom sheet with clear separation */
.chat-panel { .chat-panel {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
width: 100%; width: 100vw;
max-height: 55vh; max-width: 100vw;
border-radius: 8px 8px 0 0; max-height: 50vh;
border-radius: 12px 12px 0 0;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.25);
overflow: hidden;
} }
.chat-toggle-btn { .chat-toggle-btn {
@@ -810,31 +864,28 @@
} }
.chat-messages { .chat-messages {
max-height: 180px; max-height: 160px;
} }
/* Hide desktop-only layout modes on mobile */ /* Hide desktop-only cog items on mobile */
.chat-mode-btn[data-mode="chat-half"], .chat-cog-item[data-mode="chat-half"],
.chat-mode-btn[data-mode="chat-float"], .chat-cog-item[data-mode="chat-float"],
.chat-mode-btn[data-mode="chat-full"] { .chat-cog-item[data-mode="chat-full"] {
display: none; display: none;
} }
/* Show mobile split button */
.chat-mode-btn[data-mode="chat-split"] {
display: flex;
}
/* Mobile split: CV on top, chat on bottom half */ /* Mobile split: CV on top, chat on bottom half */
.chat-panel.chat-split { .chat-panel.chat-split {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
top: auto; top: auto;
width: 100%; width: 100vw;
max-width: 100vw;
height: 50vh; height: 50vh;
max-height: 50vh; max-height: 50vh;
border-radius: 8px 8px 0 0; border-radius: 12px 12px 0 0;
box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.3);
border-top: 2px solid var(--accent-green, #27ae60); border-top: 2px solid var(--accent-green, #27ae60);
} }
@@ -851,12 +902,14 @@
left: 0; left: 0;
right: 0; right: 0;
top: auto; top: auto;
width: 100%; width: 100vw;
max-width: 100vw;
height: auto; height: auto;
max-height: 55vh; max-height: 50vh;
border-radius: 8px 8px 0 0; border-radius: 12px 12px 0 0;
border: 1px solid var(--border-light, #e0e0e0); border: none;
resize: none; resize: none;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.25);
} }
/* Wave position follows mobile button */ /* Wave position follows mobile button */
@@ -865,11 +918,6 @@
right: calc(1rem + 38px); right: calc(1rem + 38px);
} }
/* Tooltips: prevent overflow on mobile */
.chat-mode-btn[title]:hover::after {
display: none;
}
/* Tighter header on mobile */ /* Tighter header on mobile */
.chat-header { .chat-header {
padding: 8px 10px; padding: 8px 10px;
@@ -880,11 +928,10 @@
font-size: 0.85rem; font-size: 0.85rem;
padding: 3px; padding: 3px;
} }
}
/* Hide mobile split button on desktop */ /* Cog menu on mobile */
@media (min-width: 481px) { .chat-cog-menu {
.chat-mode-btn[data-mode="chat-split"] { right: -8px;
display: none; min-width: 120px;
} }
} }
+43 -20
View File
@@ -17,29 +17,40 @@
<iconify-icon icon="mdi:robot-happy-outline"></iconify-icon> <iconify-icon icon="mdi:robot-happy-outline"></iconify-icon>
<span>{{if eq .Lang "es"}}Asistente del CV{{else}}CV Assistant{{end}}</span> <span>{{if eq .Lang "es"}}Asistente del CV{{else}}CV Assistant{{end}}</span>
<div class="chat-header-actions"> <div class="chat-header-actions">
<button class="chat-mode-btn active" data-mode="" title="{{if eq .Lang "es"}}Compacto{{else}}Compact{{end}}" onclick="setChatSize('')"> <!-- Cog menu for layout modes -->
<iconify-icon icon="mdi:message-outline"></iconify-icon> <div class="chat-cog-wrapper">
</button> <button class="chat-mode-btn" title="{{if eq .Lang "es"}}Opciones{{else}}Options{{end}}" onclick="toggleChatCog()">
<button class="chat-mode-btn" data-mode="chat-split" title="{{if eq .Lang "es"}}Mitad de pantalla{{else}}Half screen{{end}}" onclick="setChatSize('chat-split')"> <iconify-icon icon="mdi:cog"></iconify-icon>
<iconify-icon icon="mdi:arrow-split-horizontal"></iconify-icon> </button>
</button> <div id="chat-cog-menu" class="chat-cog-menu">
<button class="chat-mode-btn" data-mode="chat-half" title="{{if eq .Lang "es"}}Panel lateral{{else}}Side panel{{end}}" onclick="setChatSize('chat-half')"> <button class="chat-cog-item active" data-mode="" onclick="setChatSize(''); closeChatCog()">
<iconify-icon icon="mdi:page-layout-sidebar-right"></iconify-icon> <iconify-icon icon="mdi:message-outline"></iconify-icon>
</button> {{if eq .Lang "es"}}Compacto{{else}}Compact{{end}}
<button class="chat-mode-btn" data-mode="chat-float" title="{{if eq .Lang "es"}}Flotante arrastra para mover{{else}}Floating drag to move{{end}}" onclick="setChatSize('chat-float')"> </button>
<iconify-icon icon="mdi:cursor-move"></iconify-icon> <button class="chat-cog-item" data-mode="chat-split" onclick="setChatSize('chat-split'); closeChatCog()">
</button> <iconify-icon icon="mdi:arrow-split-horizontal"></iconify-icon>
<button class="chat-mode-btn" data-mode="chat-full" title="{{if eq .Lang "es"}}Pantalla completa{{else}}Full screen{{end}}" onclick="setChatSize('chat-full')"> {{if eq .Lang "es"}}Mitad{{else}}Half screen{{end}}
<iconify-icon icon="mdi:arrow-expand-all"></iconify-icon> </button>
</button> <button class="chat-cog-item" data-mode="chat-half" onclick="setChatSize('chat-half'); closeChatCog()">
<span class="chat-header-divider"></span> <iconify-icon icon="mdi:page-layout-sidebar-right"></iconify-icon>
{{if eq .Lang "es"}}Lateral{{else}}Side panel{{end}}
</button>
<button class="chat-cog-item" data-mode="chat-float" onclick="setChatSize('chat-float'); closeChatCog()">
<iconify-icon icon="mdi:cursor-move"></iconify-icon>
{{if eq .Lang "es"}}Flotante{{else}}Floating{{end}}
</button>
<button class="chat-cog-item" data-mode="chat-full" onclick="setChatSize('chat-full'); closeChatCog()">
<iconify-icon icon="mdi:arrow-expand-all"></iconify-icon>
{{if eq .Lang "es"}}Completo{{else}}Full screen{{end}}
</button>
</div>
</div>
<button class="chat-mode-btn" <button class="chat-mode-btn"
title="{{if eq .Lang "es"}}Ayuda{{else}}Help{{end}}" title="{{if eq .Lang "es"}}Ayuda{{else}}Help{{end}}"
commandfor="chat-help-modal" commandfor="chat-help-modal"
command="show-modal"> command="show-modal">
<iconify-icon icon="mdi:help-circle-outline"></iconify-icon> <iconify-icon icon="mdi:help-circle-outline"></iconify-icon>
</button> </button>
<span class="chat-header-divider"></span>
<button class="chat-mode-btn" title="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}" onclick="toggleChatPanel()"> <button class="chat-mode-btn" title="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}" onclick="toggleChatPanel()">
<iconify-icon icon="mdi:close"></iconify-icon> <iconify-icon icon="mdi:close"></iconify-icon>
</button> </button>
@@ -142,6 +153,18 @@ function toggleChatPanel() {
} }
} }
// Cog menu toggle
function toggleChatCog() {
document.getElementById('chat-cog-menu').classList.toggle('chat-cog-open');
}
function closeChatCog() {
document.getElementById('chat-cog-menu').classList.remove('chat-cog-open');
}
// Close cog when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.chat-cog-wrapper')) closeChatCog();
});
// Show user message bubble immediately in chat // Show user message bubble immediately in chat
var chatBubblePending = false; var chatBubblePending = false;
function appendUserBubble(text) { function appendUserBubble(text) {
@@ -228,9 +251,9 @@ function setChatSize(size) {
panel.style.width = ''; panel.style.width = '';
panel.style.height = ''; panel.style.height = '';
if (size) panel.classList.add(size); if (size) panel.classList.add(size);
// Update active button // Update active item in cog menu
document.querySelectorAll('.chat-mode-btn[data-mode]').forEach(function(btn) { document.querySelectorAll('.chat-cog-item[data-mode]').forEach(function(item) {
btn.classList.toggle('active', btn.getAttribute('data-mode') === size); item.classList.toggle('active', item.getAttribute('data-mode') === size);
}); });
// Enable/disable drag // Enable/disable drag
if (size === 'chat-float') { if (size === 'chat-float') {