Fix window maximization and suggestion button visibility issues
All checks were successful
BotUI CI / build (push) Successful in 2m43s

- Fix window title hidden behind minibar when maximized
  - Adjust window top position to account for 28px minibar height
  - Adjust window height calculation to account for minibar
  - Set maximized window z-index to 9998 (below minibar's 9999)

- Remove transparency from chat window
  - Change window-header background from rgba to solid white
  - Remove backdrop-filter blur effect
  - Add explicit opacity rules to prevent transparency

- Hide background content when window is maximized
  - Add 'window-maximized' class to body when window is maximized
  - Add CSS rules to hide sidebar, desktop icons, and other background elements

- Hide initial suggestion buttons when dynamic suggestions displayed
  - Add 'has-suggestions' class to footer when suggestions are rendered
  - Add CSS rule to hide .quick-action-chip buttons when footer has 'has-suggestions' class
  - Update CSS version parameter to force cache reload
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-03-02 18:05:47 -03:00
parent 3e81991e8b
commit 48d773c0b3
4 changed files with 431 additions and 163 deletions

View file

@ -84,21 +84,21 @@
.chat-layout { .chat-layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
max-width: 800px; width: 100vw;
margin: 0 auto; height: 100vh;
padding: 80px 20px 20px 20px; margin: 0;
padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
/* Connection Status - use shared styles from app.css */ /* Connection Status - use shared styles from app.css */
@keyframes pulse { @keyframes pulse {
0%, 0%,
100% { 100% {
opacity: 1; opacity: 1;
@ -116,7 +116,7 @@
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding: 20px 0; padding: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
@ -305,7 +305,7 @@
/* Footer */ /* Footer */
footer { footer {
padding: 16px 0; padding: 16px 20px;
border-top: 1px solid var(--border, var(--border-color, #2a2a2a)); border-top: 1px solid var(--border, var(--border-color, #2a2a2a));
background: transparent; background: transparent;
position: relative; position: relative;
@ -605,6 +605,11 @@ footer {
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.2); box-shadow: 0 2px 6px rgba(59, 130, 246, 0.2);
} }
/* Hide initial suggestion buttons when dynamic suggestions are displayed */
footer.has-suggestions .quick-action-chip {
display: none !important;
}
/* Input Container */ /* Input Container */
.input-container, .input-container,
form.input-container { form.input-container {
@ -620,7 +625,12 @@ form.input-container {
.input-container:focus-within { .input-container:focus-within {
border-color: var(--chat-color1, var(--accent, #3b82f6)); border-color: var(--chat-color1, var(--accent, #3b82f6));
box-shadow: 0 0 0 3px color-mix(in srgb, var(--chat-color1, var(--accent, #3b82f6)) 20%, transparent); box-shadow: 0 0 0 3px
color-mix(
in srgb,
var(--chat-color1, var(--accent, #3b82f6)) 20%,
transparent
);
} }
#messageInput { #messageInput {
@ -641,7 +651,6 @@ form.input-container {
} }
@keyframes cursor-blink { @keyframes cursor-blink {
0%, 0%,
50% { 50% {
caret-color: var(--accent, #3b82f6); caret-color: var(--accent, #3b82f6);
@ -761,7 +770,7 @@ form.input-container {
/* Responsive */ /* Responsive */
@media (max-width: 768px) { @media (max-width: 768px) {
.chat-layout { .chat-layout {
padding: 0 12px; padding: 0;
} }
.message-content { .message-content {
@ -1280,7 +1289,6 @@ form.input-container {
} }
@keyframes thinkingBounce { @keyframes thinkingBounce {
0%, 0%,
80%, 80%,
100% { 100% {
@ -1361,7 +1369,7 @@ form.input-container {
} }
.message.bot .message-content::before { .message.bot .message-content::before {
content: ''; content: "";
position: absolute; position: absolute;
inset: -2px; inset: -2px;
background: var(--accent-glow, rgba(59, 130, 246, 0.1)); background: var(--accent-glow, rgba(59, 130, 246, 0.1));
@ -1410,7 +1418,6 @@ form.input-container {
} }
@keyframes bounce { @keyframes bounce {
0%, 0%,
100% { 100% {
transform: translateX(-50%) translateY(0); transform: translateX(-50%) translateY(0);
@ -1449,7 +1456,6 @@ form.input-container {
} }
@keyframes typing { @keyframes typing {
0%, 0%,
60%, 60%,
100% { 100% {
@ -1459,4 +1465,4 @@ form.input-container {
30% { 30% {
transform: translateY(-8px); transform: translateY(-8px);
} }
} }

View file

@ -1,9 +1,13 @@
<link rel="stylesheet" href="/suite/chat/chat.css?v=3" /> <link rel="stylesheet" href="/suite/chat/chat.css?v=4" />
<link rel="stylesheet" href="/suite/css/markdown-message.css" /> <link rel="stylesheet" href="/suite/css/markdown-message.css" />
<div class="chat-layout" id="chat-app"> <div class="chat-layout" id="chat-app">
<!-- Connection Status --> <!-- Connection Status -->
<div class="connection-status connecting" id="connectionStatus" style="display: none;"> <div
class="connection-status connecting"
id="connectionStatus"
style="display: none"
>
<span class="connection-status-dot"></span> <span class="connection-status-dot"></span>
<span class="connection-text">Connecting...</span> <span class="connection-text">Connecting...</span>
</div> </div>
@ -14,21 +18,47 @@
<div class="suggestions-container" id="suggestions"></div> <div class="suggestions-container" id="suggestions"></div>
<div class="mention-dropdown" id="mentionDropdown"> <div class="mention-dropdown" id="mentionDropdown">
<div class="mention-header"> <div class="mention-header">
<span class="mention-title" data-i18n="chat-mention-title">Reference Entity</span> <span class="mention-title" data-i18n="chat-mention-title"
>Reference Entity</span
>
</div> </div>
<div class="mention-results" id="mentionResults"></div> <div class="mention-results" id="mentionResults"></div>
</div> </div>
<form class="input-container" id="chatForm"> <form class="input-container" id="chatForm">
<input name="content" id="messageInput" type="text" placeholder="Message... (type @ to mention)" <input
data-i18n-placeholder="chat-placeholder" autofocus autocomplete="off" /> name="content"
<button type="submit" id="sendBtn" title="Send" data-i18n-title="chat-send"> id="messageInput"
type="text"
placeholder="Message... (type @ to mention)"
data-i18n-placeholder="chat-placeholder"
autofocus
autocomplete="off"
/>
<button
type="submit"
id="sendBtn"
title="Send"
data-i18n-title="chat-send"
>
</button> </button>
</form> </form>
</footer> </footer>
<button class="scroll-to-bottom" id="scrollToBottom" title="Scroll to bottom"> <button
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="scroll-to-bottom"
stroke-linecap="round" stroke-linejoin="round"> id="scrollToBottom"
title="Scroll to bottom"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline> <polyline points="6 9 12 15 18 9"></polyline>
</svg> </svg>
</button> </button>
@ -42,7 +72,11 @@
<div class="entity-card-title"></div> <div class="entity-card-title"></div>
<div class="entity-card-details"></div> <div class="entity-card-details"></div>
<div class="entity-card-actions"> <div class="entity-card-actions">
<button class="entity-card-btn" data-action="view" data-i18n="action-view"> <button
class="entity-card-btn"
data-action="view"
data-i18n="action-view"
>
View View
</button> </button>
</div> </div>
@ -178,7 +212,9 @@
if (!messages || !scrollBtn) return; if (!messages || !scrollBtn) return;
var isNearBottom = var isNearBottom =
messages.scrollHeight - messages.scrollTop - messages.clientHeight < messages.scrollHeight -
messages.scrollTop -
messages.clientHeight <
100; 100;
if (isNearBottom) { if (isNearBottom) {
@ -355,9 +391,9 @@
function fetchEntityDetails(type, name) { function fetchEntityDetails(type, name) {
return fetch( return fetch(
"/api/search/entity?type=" + "/api/search/entity?type=" +
encodeURIComponent(type) + encodeURIComponent(type) +
"&name=" + "&name=" +
encodeURIComponent(name), encodeURIComponent(name),
) )
.then(function (r) { .then(function (r) {
return r.json(); return r.json();
@ -443,9 +479,9 @@
function fetchEntitiesOfType(type, searchTerm) { function fetchEntitiesOfType(type, searchTerm) {
fetch( fetch(
"/api/search/entities?type=" + "/api/search/entities?type=" +
encodeURIComponent(type) + encodeURIComponent(type) +
"&q=" + "&q=" +
encodeURIComponent(searchTerm || ""), encodeURIComponent(searchTerm || ""),
) )
.then(function (r) { .then(function (r) {
return r.json(); return r.json();
@ -505,8 +541,8 @@
var subtitle = item.subtitle var subtitle = item.subtitle
? '<span class="mention-item-subtitle">' + ? '<span class="mention-item-subtitle">' +
escapeHtml(item.subtitle) + escapeHtml(item.subtitle) +
"</span>" "</span>"
: ""; : "";
var hint = item.isTypeHint var hint = item.isTypeHint
? '<span class="mention-item-hint">Type : to search</span>' ? '<span class="mention-item-hint">Type : to search</span>'
@ -727,7 +763,11 @@
isStreaming = false; isStreaming = false;
// Render suggestions when message is complete // Render suggestions when message is complete
if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) { if (
data.suggestions &&
Array.isArray(data.suggestions) &&
data.suggestions.length > 0
) {
renderSuggestions(data.suggestions); renderSuggestions(data.suggestions);
} }
} else { } else {
@ -755,11 +795,23 @@
return; return;
} }
// Get the footer element
var footer = suggestionsEl.closest("footer");
// Clear existing suggestions // Clear existing suggestions
suggestionsEl.innerHTML = ""; suggestionsEl.innerHTML = "";
console.log("Rendering " + suggestions.length + " suggestions"); console.log("Rendering " + suggestions.length + " suggestions");
// Add or remove CSS class based on whether suggestions are displayed
if (footer) {
if (suggestions.length > 0) {
footer.classList.add("has-suggestions");
} else {
footer.classList.remove("has-suggestions");
}
}
suggestions.forEach(function (suggestion) { suggestions.forEach(function (suggestion) {
var chip = document.createElement("button"); var chip = document.createElement("button");
chip.className = "suggestion-chip"; chip.className = "suggestion-chip";
@ -772,9 +824,10 @@
// Check if there's an action to parse // Check if there's an action to parse
if (sugg.action) { if (sugg.action) {
try { try {
var action = typeof sugg.action === "string" var action =
? JSON.parse(sugg.action) typeof sugg.action === "string"
: sugg.action; ? JSON.parse(sugg.action)
: sugg.action;
console.log("Parsed action:", action); console.log("Parsed action:", action);
@ -783,14 +836,20 @@
// The backend will recognize this as a tool request // The backend will recognize this as a tool request
window.sendMessage(sugg.text); window.sendMessage(sugg.text);
} else if (action.type === "send_message") { } else if (action.type === "send_message") {
window.sendMessage(action.message || sugg.text); window.sendMessage(
action.message || sugg.text,
);
} else if (action.type === "select_context") { } else if (action.type === "select_context") {
window.sendMessage(action.context); window.sendMessage(action.context);
} else { } else {
window.sendMessage(sugg.text); window.sendMessage(sugg.text);
} }
} catch (e) { } catch (e) {
console.error("Failed to parse action:", e, "falling back to text"); console.error(
"Failed to parse action:",
e,
"falling back to text",
);
window.sendMessage(sugg.text); window.sendMessage(sugg.text);
} }
} else { } else {
@ -852,7 +911,7 @@
currentBotId: currentBotId, currentBotId: currentBotId,
currentUserId: currentUserId, currentUserId: currentUserId,
currentSessionId: currentSessionId, currentSessionId: currentSessionId,
currentBotName: currentBotName currentBotName: currentBotName,
}; };
}; };
@ -876,7 +935,7 @@
ws = new WebSocket(url); ws = new WebSocket(url);
// Add connection timeout to detect silent failures // Add connection timeout to detect silent failures
var connectionTimeout = setTimeout(function() { var connectionTimeout = setTimeout(function () {
if (ws.readyState !== WebSocket.OPEN) { if (ws.readyState !== WebSocket.OPEN) {
console.error("WebSocket connection timeout"); console.error("WebSocket connection timeout");
ws.close(); ws.close();
@ -947,8 +1006,13 @@
updateConnectionStatus("connecting"); updateConnectionStatus("connecting");
setTimeout(connectWebSocket, 1000 * reconnectAttempts); setTimeout(connectWebSocket, 1000 * reconnectAttempts);
} else { } else {
console.error("Max reconnection attempts reached. Stopping reconnection."); console.error(
notify("Could not reconnect to chat server after multiple attempts", "error"); "Max reconnection attempts reached. Stopping reconnection.",
);
notify(
"Could not reconnect to chat server after multiple attempts",
"error",
);
} }
}; };
@ -960,8 +1024,8 @@
// Apply theme data from WebSocket events // Apply theme data from WebSocket events
function getContrastYIQ(hexcolor) { function getContrastYIQ(hexcolor) {
if (!hexcolor) return '#ffffff'; if (!hexcolor) return "#ffffff";
// Handle named colors and variables by letting the browser resolve them // Handle named colors and variables by letting the browser resolve them
var temp = document.createElement("div"); var temp = document.createElement("div");
temp.style.color = hexcolor; temp.style.color = hexcolor;
@ -969,16 +1033,16 @@
document.body.appendChild(temp); document.body.appendChild(temp);
var style = window.getComputedStyle(temp).color; var style = window.getComputedStyle(temp).color;
document.body.removeChild(temp); document.body.removeChild(temp);
var rgb = style.match(/\d+/g); var rgb = style.match(/\d+/g);
if (!rgb || rgb.length < 3) return '#ffffff'; if (!rgb || rgb.length < 3) return "#ffffff";
var r = parseInt(rgb[0]); var r = parseInt(rgb[0]);
var g = parseInt(rgb[1]); var g = parseInt(rgb[1]);
var b = parseInt(rgb[2]); var b = parseInt(rgb[2]);
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; var yiq = (r * 299 + g * 587 + b * 114) / 1000;
return (yiq >= 128) ? '#000000' : '#ffffff'; return yiq >= 128 ? "#000000" : "#ffffff";
} }
function applyThemeData(themeData) { function applyThemeData(themeData) {
@ -987,13 +1051,23 @@
var color1 = themeData.color1 || themeData.data?.color1 || "black"; var color1 = themeData.color1 || themeData.data?.color1 || "black";
var color2 = themeData.color2 || themeData.data?.color2 || "white"; var color2 = themeData.color2 || themeData.data?.color2 || "white";
var logo = themeData.logo_url || themeData.data?.logo_url || ""; var logo = themeData.logo_url || themeData.data?.logo_url || "";
var title = themeData.title || themeData.data?.title || window.__INITIAL_BOT_NAME__ || "Chat"; var title =
themeData.title ||
themeData.data?.title ||
window.__INITIAL_BOT_NAME__ ||
"Chat";
// Set CSS variables for colors on document element // Set CSS variables for colors on document element
document.documentElement.style.setProperty("--chat-color1", color1); document.documentElement.style.setProperty("--chat-color1", color1);
document.documentElement.style.setProperty("--chat-color2", color2); document.documentElement.style.setProperty("--chat-color2", color2);
document.documentElement.style.setProperty("--suggestion-color", color1); document.documentElement.style.setProperty(
document.documentElement.style.setProperty("--suggestion-bg", color2); "--suggestion-color",
color1,
);
document.documentElement.style.setProperty(
"--suggestion-bg",
color2,
);
// Also set on root for better cascading // Also set on root for better cascading
document.documentElement.style.setProperty("--color1", color1); document.documentElement.style.setProperty("--color1", color1);
@ -1003,10 +1077,21 @@
document.documentElement.style.setProperty("--primary", color1); document.documentElement.style.setProperty("--primary", color1);
document.documentElement.style.setProperty("--accent", color1); document.documentElement.style.setProperty("--accent", color1);
document.documentElement.style.setProperty("--chat-fg1", getContrastYIQ(color1)); document.documentElement.style.setProperty(
document.documentElement.style.setProperty("--chat-fg2", getContrastYIQ(color2)); "--chat-fg1",
getContrastYIQ(color1),
);
document.documentElement.style.setProperty(
"--chat-fg2",
getContrastYIQ(color2),
);
console.log("Theme applied:", { color1: color1, color2: color2, logo: logo, title: title }); console.log("Theme applied:", {
color1: color1,
color2: color2,
logo: logo,
title: title,
});
} }
// Load bot config and apply colors/logo // Load bot config and apply colors/logo
@ -1023,47 +1108,98 @@
// Get the theme manager's theme for this bot to check if user selected a different theme // Get the theme manager's theme for this bot to check if user selected a different theme
var botId = botName.toLowerCase(); var botId = botName.toLowerCase();
var botThemeKey = "gb-theme-" + botId; var botThemeKey = "gb-theme-" + botId;
var botTheme = window.ThemeManager ? ( var botTheme = window.ThemeManager
// Get bot-specific theme from theme manager's mapping ? // Get bot-specific theme from theme manager's mapping
(window.ThemeManager.getAvailableThemes && (window.ThemeManager.getAvailableThemes &&
window.ThemeManager.getAvailableThemes().find(t => t.id === botId)) || window.ThemeManager.getAvailableThemes().find(
// Fallback to localStorage (t) => t.id === botId,
localStorage.getItem(botThemeKey) )) ||
) : localStorage.getItem(botThemeKey); // Fallback to localStorage
localStorage.getItem(botThemeKey)
: localStorage.getItem(botThemeKey);
// Check if bot config has a theme-base setting // Check if bot config has a theme-base setting
var configThemeBase = config.theme_base || config["theme-base"] || "light"; var configThemeBase =
config.theme_base || config["theme-base"] || "light";
// Only use bot config colors if: // Only use bot config colors if:
// 1. No theme has been explicitly selected by user (localStorage empty or default) // 1. No theme has been explicitly selected by user (localStorage empty or default)
// 2. AND the bot config's theme-base matches the current theme // 2. AND the bot config's theme-base matches the current theme
var localStorageTheme = localStorage.getItem(botThemeKey); var localStorageTheme = localStorage.getItem(botThemeKey);
var useBotConfigColors = !localStorageTheme || var useBotConfigColors =
!localStorageTheme ||
localStorageTheme === "default" || localStorageTheme === "default" ||
localStorageTheme === configThemeBase; localStorageTheme === configThemeBase;
// Apply colors from config (API returns snake_case) // Apply colors from config (API returns snake_case)
var color1 = config.theme_color1 || config["theme-color1"] || config["Theme Color"] || "#3b82f6"; var color1 =
var color2 = config.theme_color2 || config["theme-color2"] || "#f5deb3"; config.theme_color1 ||
var title = config.theme_title || config["theme-title"] || botName; config["theme-color1"] ||
config["Theme Color"] ||
"#3b82f6";
var color2 =
config.theme_color2 ||
config["theme-color2"] ||
"#f5deb3";
var title =
config.theme_title || config["theme-title"] || botName;
var logo = config.theme_logo || config["theme-logo"] || ""; var logo = config.theme_logo || config["theme-logo"] || "";
// Only set bot config colors if user hasn't selected a different theme // Only set bot config colors if user hasn't selected a different theme
if (useBotConfigColors) { if (useBotConfigColors) {
document.documentElement.setAttribute("data-has-bot-colors", "true"); document.documentElement.setAttribute(
document.documentElement.style.setProperty("--chat-color1", color1); "data-has-bot-colors",
document.documentElement.style.setProperty("--chat-color2", color2); "true",
document.documentElement.style.setProperty("--suggestion-color", color1); );
document.documentElement.style.setProperty("--suggestion-bg", color2); document.documentElement.style.setProperty(
document.documentElement.style.setProperty("--color1", color1); "--chat-color1",
document.documentElement.style.setProperty("--color2", color2); color1,
document.documentElement.style.setProperty("--primary", color1); );
document.documentElement.style.setProperty("--accent", color1); document.documentElement.style.setProperty(
document.documentElement.style.setProperty("--chat-fg1", getContrastYIQ(color1)); "--chat-color2",
document.documentElement.style.setProperty("--chat-fg2", getContrastYIQ(color2)); color2,
console.log("Bot config colors applied:", { color1: color1, color2: color2 }); );
document.documentElement.style.setProperty(
"--suggestion-color",
color1,
);
document.documentElement.style.setProperty(
"--suggestion-bg",
color2,
);
document.documentElement.style.setProperty(
"--color1",
color1,
);
document.documentElement.style.setProperty(
"--color2",
color2,
);
document.documentElement.style.setProperty(
"--primary",
color1,
);
document.documentElement.style.setProperty(
"--accent",
color1,
);
document.documentElement.style.setProperty(
"--chat-fg1",
getContrastYIQ(color1),
);
document.documentElement.style.setProperty(
"--chat-fg2",
getContrastYIQ(color2),
);
console.log("Bot config colors applied:", {
color1: color1,
color2: color2,
});
} else { } else {
console.log("Bot config colors skipped - user selected custom theme:", localStorageTheme); console.log(
"Bot config colors skipped - user selected custom theme:",
localStorageTheme,
);
} }
// Update logo if provided // Update logo if provided
@ -1081,7 +1217,12 @@
} }
} }
console.log("Bot config loaded:", { color1: color1, color2: color2, title: title, logo: logo }); console.log("Bot config loaded:", {
color1: color1,
color2: color2,
title: title,
logo: logo,
});
}) })
.catch(function (e) { .catch(function (e) {
console.log("Could not load bot config:", e); console.log("Could not load bot config:", e);
@ -1117,8 +1258,12 @@
.catch(function (e) { .catch(function (e) {
console.error("Auth failed:", e); console.error("Auth failed:", e);
// Proceed with anonymous connection - WebSocket handler supports it // Proceed with anonymous connection - WebSocket handler supports it
currentUserId = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(); currentUserId = crypto.randomUUID
currentSessionId = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(); ? crypto.randomUUID()
: Date.now().toString();
currentSessionId = crypto.randomUUID
? crypto.randomUUID()
: Date.now().toString();
currentBotId = botName; currentBotId = botName;
currentBotName = botName; currentBotName = botName;
console.log("Anonymous chat:", { console.log("Anonymous chat:", {
@ -1205,4 +1350,4 @@
console.log("Chat module initialized with @ mentions support"); console.log("Chat module initialized with @ mentions support");
})(); })();
</script> </script>

View file

@ -1,36 +1,37 @@
.app-icon {
.app-icon { background: var(--surface-hover, #ffffff);
background: var(--surface-hover, #ffffff); border: 1px solid var(--border, #e5e7eb);
border: 1px solid var(--border, #e5e7eb); box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.05);
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.05); /* removed border */
/* removed border */ /* removed border bottom */
/* removed border bottom */ }
}
.workspace-bg {
.workspace-bg { background-color: var(--bg, #fafdfa);
background-color: var(--bg, #fafdfa); }
}
.workspace-grid {
.workspace-grid { background-image:
background-image: linear-gradient(to right, var(--border, #f0fdf4) 1px, transparent 1px), linear-gradient(to bottom, var(--border, #f0fdf4) 1px, transparent 1px); linear-gradient(to right, var(--border, #f0fdf4) 1px, transparent 1px),
background-size: 40px 40px; linear-gradient(to bottom, var(--border, #f0fdf4) 1px, transparent 1px);
} background-size: 40px 40px;
}
/* Custom scrollbar for terminal */
::-webkit-scrollbar { /* Custom scrollbar for terminal */
width: 8px; ::-webkit-scrollbar {
height: 8px; width: 8px;
} height: 8px;
::-webkit-scrollbar-track { }
background: transparent; ::-webkit-scrollbar-track {
} background: transparent;
::-webkit-scrollbar-thumb { }
background: var(--surface-active, #333); ::-webkit-scrollbar-thumb {
border-radius: 4px; background: var(--surface-active, #333);
} border-radius: 4px;
::-webkit-scrollbar-thumb:hover { }
background: var(--border-hover, #555); ::-webkit-scrollbar-thumb:hover {
} background: var(--border-hover, #555);
}
/* Window Manager Core Styles (replacing missing Tailwind classes) */ /* Window Manager Core Styles (replacing missing Tailwind classes) */
.window-element { .window-element {
@ -49,8 +50,7 @@
.window-header { .window-header {
height: 40px; height: 40px;
background-color: var(--surface, rgba(255, 255, 255, 0.95)); background-color: var(--surface, #ffffff);
backdrop-filter: blur(4px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@ -58,10 +58,12 @@
border-bottom: 1px solid var(--border, #e5e7eb); border-bottom: 1px solid var(--border, #e5e7eb);
user-select: none; user-select: none;
cursor: move; cursor: move;
position: relative;
z-index: 10;
} }
.window-header .font-mono { .window-header .font-mono {
font-family: 'Fira Code', monospace; font-family: "Fira Code", monospace;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
color: var(--accent, #16a34a); /* brand-600 */ color: var(--accent, #16a34a); /* brand-600 */
@ -99,47 +101,117 @@
padding: 16px; padding: 16px;
} }
.w-\[700px\] {
width: 700px;
}
.h-\[500px\] {
height: 500px;
}
.bg-white {
background-color: #fff;
}
.rounded-lg {
border-radius: 0.5rem;
}
.shadow-2xl {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.border {
border-width: 1px;
border-style: solid;
}
.border-gray-200 {
border-color: #e5e7eb;
}
.overflow-hidden {
overflow: hidden;
}
.absolute {
position: absolute;
}
.w-\[700px\] { width: 700px; } .h-10 {
.h-\[500px\] { height: 500px; } height: 2.5rem;
.bg-white { background-color: #fff; } }
.rounded-lg { border-radius: 0.5rem; } .bg-white\/95 {
.shadow-2xl { box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); } background-color: var(--surface, rgba(255, 255, 255, 0.95));
.flex { display: flex; } }
.flex-col { flex-direction: column; } .backdrop-blur {
.border { border-width: 1px; border-style: solid; } backdrop-filter: blur(8px);
.border-gray-200 { border-color: #e5e7eb; } }
.overflow-hidden { overflow: hidden; } .items-center {
.absolute { position: absolute; } align-items: center;
}
.justify-between {
.h-10 { height: 2.5rem; } justify-content: space-between;
.bg-white\/95 { background-color: var(--surface, rgba(255, 255, 255, 0.95)); } }
.backdrop-blur { backdrop-filter: blur(8px); } .px-4 {
.items-center { align-items: center; } padding-left: 1rem;
.justify-between { justify-content: space-between; } padding-right: 1rem;
.px-4 { padding-left: 1rem; padding-right: 1rem; } }
.border-b { border-bottom-width: 1px; } .border-b {
.select-none { user-select: none; } border-bottom-width: 1px;
.cursor-move { cursor: move; } }
.font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } .select-none {
.text-xs { font-size: 0.75rem; line-height: 1rem; } user-select: none;
.font-bold { font-weight: 700; } }
.text-brand-600 { color: var(--accent, #84d669); } .cursor-move {
.tracking-wide { letter-spacing: 0.025em; } cursor: move;
.space-x-3 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.75rem * var(--tw-space-x-reverse)); margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); } }
.text-gray-400 { color: var(--text-muted, #9ca3af); } .font-mono {
.hover\:text-gray-600:hover { color: var(--text, #4b5563); } font-family:
.hover\:text-red-500:hover { color: var(--error, #ef4444); } ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
.relative { position: relative; } "Liberation Mono", "Courier New", monospace;
.flex-1 { flex: 1 1 0%; } }
.overflow-y-auto { overflow-y: auto; } .text-xs {
.bg-\[\#fafdfa\] { background-color: var(--bg, #fafdfa); } font-size: 0.75rem;
line-height: 1rem;
}
.font-bold {
font-weight: 700;
}
.text-brand-600 {
color: var(--accent, #84d669);
}
.tracking-wide {
letter-spacing: 0.025em;
}
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
}
.text-gray-400 {
color: var(--text-muted, #9ca3af);
}
.hover\:text-gray-600:hover {
color: var(--text, #4b5563);
}
.hover\:text-red-500:hover {
color: var(--error, #ef4444);
}
.relative {
position: relative;
}
.flex-1 {
flex: 1 1 0%;
}
.overflow-y-auto {
overflow-y: auto;
}
.bg-\[\#fafdfa\] {
background-color: var(--bg, #fafdfa);
}
.window-header { .window-header {
height: 40px; height: 40px;
background-color: var(--surface, rgba(255, 255, 255, 0.95)); background-color: var(--surface, #ffffff);
border-bottom: 1px solid var(--border, #e5e7eb); border-bottom: 1px solid var(--border, #e5e7eb);
display: flex; display: flex;
align-items: center; align-items: center;
@ -147,9 +219,11 @@
padding: 0 16px; padding: 0 16px;
user-select: none; user-select: none;
cursor: move; cursor: move;
position: relative;
z-index: 10;
} }
.window-header .font-mono { .window-header .font-mono {
font-family: 'Fira Code', monospace; font-family: "Fira Code", monospace;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
color: var(--accent, #16a34a); color: var(--accent, #16a34a);
@ -178,3 +252,34 @@
position: relative; position: relative;
background-color: var(--bg, #ffffff); background-color: var(--bg, #ffffff);
} }
/* Hide background content when window is maximized */
body.window-maximized .sidebar,
body.window-maximized .sidebar-item,
body.window-maximized .desktop-icon,
body.window-maximized .panel-grid,
body.window-maximized .workspace-grid,
body.window-maximized .bg-grid,
body.window-maximized .bg-svg {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
/* Ensure maximized window is below minibar (28px height) */
body.window-maximized .window-element {
z-index: 9998 !important; /* Lower than minibar's 9999 */
top: 28px !important; /* Below the minibar */
}
/* Ensure window body is opaque */
.window-body {
background-color: var(--bg, #fafdfa) !important;
opacity: 1 !important;
}
/* Ensure window element is fully opaque */
.window-element {
background-color: var(--surface, #ffffff) !important;
opacity: 1 !important;
}

View file

@ -196,6 +196,12 @@ if (typeof window.WindowManager === "undefined") {
windowEl.style.top = windowObj.previousState.top; windowEl.style.top = windowObj.previousState.top;
windowEl.style.left = windowObj.previousState.left; windowEl.style.left = windowObj.previousState.left;
windowObj.isMaximized = false; windowObj.isMaximized = false;
// Check if any other windows are still maximized
const anyMaximized = this.openWindows.some((w) => w.isMaximized);
if (!anyMaximized) {
document.body.classList.remove("window-maximized");
}
} else { } else {
// Maximize // Maximize
windowObj.previousState = { windowObj.previousState = {
@ -210,11 +216,17 @@ if (typeof window.WindowManager === "undefined") {
? document.getElementById("taskbar").offsetHeight ? document.getElementById("taskbar").offsetHeight
: 0; : 0;
// Adjust for minibar height (fixed 28px at top)
const minibarHeight = 28;
windowEl.style.width = "100%"; windowEl.style.width = "100%";
windowEl.style.height = `calc(100% - ${taskbarHeight}px)`; windowEl.style.height = `calc(100% - ${taskbarHeight}px - ${minibarHeight}px)`;
windowEl.style.top = "0px"; windowEl.style.top = `${minibarHeight}px`;
windowEl.style.left = "0px"; windowEl.style.left = "0px";
windowObj.isMaximized = true; windowObj.isMaximized = true;
// Add CSS class to body to hide background content
document.body.classList.add("window-maximized");
} }
this.focus(id); this.focus(id);
} }