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 {
display: flex;
flex-direction: column;
position: absolute;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
max-width: 800px;
margin: 0 auto;
padding: 80px 20px 20px 20px;
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Connection Status - use shared styles from app.css */
@keyframes pulse {
0%,
100% {
opacity: 1;
@ -116,7 +116,7 @@
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 20px 0;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
@ -305,7 +305,7 @@
/* Footer */
footer {
padding: 16px 0;
padding: 16px 20px;
border-top: 1px solid var(--border, var(--border-color, #2a2a2a));
background: transparent;
position: relative;
@ -605,6 +605,11 @@ footer {
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,
form.input-container {
@ -620,7 +625,12 @@ form.input-container {
.input-container:focus-within {
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 {
@ -641,7 +651,6 @@ form.input-container {
}
@keyframes cursor-blink {
0%,
50% {
caret-color: var(--accent, #3b82f6);
@ -761,7 +770,7 @@ form.input-container {
/* Responsive */
@media (max-width: 768px) {
.chat-layout {
padding: 0 12px;
padding: 0;
}
.message-content {
@ -1280,7 +1289,6 @@ form.input-container {
}
@keyframes thinkingBounce {
0%,
80%,
100% {
@ -1361,7 +1369,7 @@ form.input-container {
}
.message.bot .message-content::before {
content: '';
content: "";
position: absolute;
inset: -2px;
background: var(--accent-glow, rgba(59, 130, 246, 0.1));
@ -1410,7 +1418,6 @@ form.input-container {
}
@keyframes bounce {
0%,
100% {
transform: translateX(-50%) translateY(0);
@ -1449,7 +1456,6 @@ form.input-container {
}
@keyframes typing {
0%,
60%,
100% {

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" />
<div class="chat-layout" id="chat-app">
<!-- 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-text">Connecting...</span>
</div>
@ -14,21 +18,47 @@
<div class="suggestions-container" id="suggestions"></div>
<div class="mention-dropdown" id="mentionDropdown">
<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 class="mention-results" id="mentionResults"></div>
</div>
<form class="input-container" id="chatForm">
<input name="content" 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">
<input
name="content"
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>
</form>
</footer>
<button class="scroll-to-bottom" 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">
<button
class="scroll-to-bottom"
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>
</svg>
</button>
@ -42,7 +72,11 @@
<div class="entity-card-title"></div>
<div class="entity-card-details"></div>
<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
</button>
</div>
@ -178,7 +212,9 @@
if (!messages || !scrollBtn) return;
var isNearBottom =
messages.scrollHeight - messages.scrollTop - messages.clientHeight <
messages.scrollHeight -
messages.scrollTop -
messages.clientHeight <
100;
if (isNearBottom) {
@ -727,7 +763,11 @@
isStreaming = false;
// 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);
}
} else {
@ -755,11 +795,23 @@
return;
}
// Get the footer element
var footer = suggestionsEl.closest("footer");
// Clear existing suggestions
suggestionsEl.innerHTML = "";
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) {
var chip = document.createElement("button");
chip.className = "suggestion-chip";
@ -772,7 +824,8 @@
// Check if there's an action to parse
if (sugg.action) {
try {
var action = typeof sugg.action === "string"
var action =
typeof sugg.action === "string"
? JSON.parse(sugg.action)
: sugg.action;
@ -783,14 +836,20 @@
// The backend will recognize this as a tool request
window.sendMessage(sugg.text);
} else if (action.type === "send_message") {
window.sendMessage(action.message || sugg.text);
window.sendMessage(
action.message || sugg.text,
);
} else if (action.type === "select_context") {
window.sendMessage(action.context);
} else {
window.sendMessage(sugg.text);
}
} 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);
}
} else {
@ -852,7 +911,7 @@
currentBotId: currentBotId,
currentUserId: currentUserId,
currentSessionId: currentSessionId,
currentBotName: currentBotName
currentBotName: currentBotName,
};
};
@ -876,7 +935,7 @@
ws = new WebSocket(url);
// Add connection timeout to detect silent failures
var connectionTimeout = setTimeout(function() {
var connectionTimeout = setTimeout(function () {
if (ws.readyState !== WebSocket.OPEN) {
console.error("WebSocket connection timeout");
ws.close();
@ -947,8 +1006,13 @@
updateConnectionStatus("connecting");
setTimeout(connectWebSocket, 1000 * reconnectAttempts);
} else {
console.error("Max reconnection attempts reached. Stopping reconnection.");
notify("Could not reconnect to chat server after multiple attempts", "error");
console.error(
"Max reconnection attempts reached. Stopping reconnection.",
);
notify(
"Could not reconnect to chat server after multiple attempts",
"error",
);
}
};
@ -960,7 +1024,7 @@
// Apply theme data from WebSocket events
function getContrastYIQ(hexcolor) {
if (!hexcolor) return '#ffffff';
if (!hexcolor) return "#ffffff";
// Handle named colors and variables by letting the browser resolve them
var temp = document.createElement("div");
@ -971,14 +1035,14 @@
document.body.removeChild(temp);
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 g = parseInt(rgb[1]);
var b = parseInt(rgb[2]);
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
return (yiq >= 128) ? '#000000' : '#ffffff';
var yiq = (r * 299 + g * 587 + b * 114) / 1000;
return yiq >= 128 ? "#000000" : "#ffffff";
}
function applyThemeData(themeData) {
@ -987,13 +1051,23 @@
var color1 = themeData.color1 || themeData.data?.color1 || "black";
var color2 = themeData.color2 || themeData.data?.color2 || "white";
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
document.documentElement.style.setProperty("--chat-color1", color1);
document.documentElement.style.setProperty("--chat-color2", color2);
document.documentElement.style.setProperty("--suggestion-color", color1);
document.documentElement.style.setProperty("--suggestion-bg", color2);
document.documentElement.style.setProperty(
"--suggestion-color",
color1,
);
document.documentElement.style.setProperty(
"--suggestion-bg",
color2,
);
// Also set on root for better cascading
document.documentElement.style.setProperty("--color1", color1);
@ -1003,10 +1077,21 @@
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));
document.documentElement.style.setProperty(
"--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
@ -1023,47 +1108,98 @@
// Get the theme manager's theme for this bot to check if user selected a different theme
var botId = botName.toLowerCase();
var botThemeKey = "gb-theme-" + botId;
var botTheme = window.ThemeManager ? (
// Get bot-specific theme from theme manager's mapping
var botTheme = window.ThemeManager
? // Get bot-specific theme from theme manager's mapping
(window.ThemeManager.getAvailableThemes &&
window.ThemeManager.getAvailableThemes().find(t => t.id === botId)) ||
window.ThemeManager.getAvailableThemes().find(
(t) => t.id === botId,
)) ||
// Fallback to localStorage
localStorage.getItem(botThemeKey)
) : localStorage.getItem(botThemeKey);
: localStorage.getItem(botThemeKey);
// 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:
// 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
var localStorageTheme = localStorage.getItem(botThemeKey);
var useBotConfigColors = !localStorageTheme ||
var useBotConfigColors =
!localStorageTheme ||
localStorageTheme === "default" ||
localStorageTheme === configThemeBase;
// Apply colors from config (API returns snake_case)
var color1 = config.theme_color1 || 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 color1 =
config.theme_color1 ||
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"] || "";
// Only set bot config colors if user hasn't selected a different theme
if (useBotConfigColors) {
document.documentElement.setAttribute("data-has-bot-colors", "true");
document.documentElement.style.setProperty("--chat-color1", color1);
document.documentElement.style.setProperty("--chat-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 });
document.documentElement.setAttribute(
"data-has-bot-colors",
"true",
);
document.documentElement.style.setProperty(
"--chat-color1",
color1,
);
document.documentElement.style.setProperty(
"--chat-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 {
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
@ -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) {
console.log("Could not load bot config:", e);
@ -1117,8 +1258,12 @@
.catch(function (e) {
console.error("Auth failed:", e);
// Proceed with anonymous connection - WebSocket handler supports it
currentUserId = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString();
currentSessionId = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString();
currentUserId = crypto.randomUUID
? crypto.randomUUID()
: Date.now().toString();
currentSessionId = crypto.randomUUID
? crypto.randomUUID()
: Date.now().toString();
currentBotId = botName;
currentBotName = botName;
console.log("Anonymous chat:", {

View file

@ -1,36 +1,37 @@
.app-icon {
.app-icon {
background: var(--surface-hover, #ffffff);
border: 1px solid var(--border, #e5e7eb);
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.05);
/* removed border */
/* removed border bottom */
}
}
.workspace-bg {
.workspace-bg {
background-color: var(--bg, #fafdfa);
}
}
.workspace-grid {
background-image: linear-gradient(to right, var(--border, #f0fdf4) 1px, transparent 1px), linear-gradient(to bottom, var(--border, #f0fdf4) 1px, transparent 1px);
.workspace-grid {
background-image:
linear-gradient(to right, var(--border, #f0fdf4) 1px, transparent 1px),
linear-gradient(to bottom, var(--border, #f0fdf4) 1px, transparent 1px);
background-size: 40px 40px;
}
}
/* Custom scrollbar for terminal */
::-webkit-scrollbar {
/* Custom scrollbar for terminal */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
}
::-webkit-scrollbar-thumb {
background: var(--surface-active, #333);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-hover, #555);
}
}
/* Window Manager Core Styles (replacing missing Tailwind classes) */
.window-element {
@ -49,8 +50,7 @@
.window-header {
height: 40px;
background-color: var(--surface, rgba(255, 255, 255, 0.95));
backdrop-filter: blur(4px);
background-color: var(--surface, #ffffff);
display: flex;
align-items: center;
justify-content: space-between;
@ -58,10 +58,12 @@
border-bottom: 1px solid var(--border, #e5e7eb);
user-select: none;
cursor: move;
position: relative;
z-index: 10;
}
.window-header .font-mono {
font-family: 'Fira Code', monospace;
font-family: "Fira Code", monospace;
font-size: 12px;
font-weight: 700;
color: var(--accent, #16a34a); /* brand-600 */
@ -99,47 +101,117 @@
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-\[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; }
.h-10 { height: 2.5rem; }
.bg-white\/95 { background-color: var(--surface, rgba(255, 255, 255, 0.95)); }
.backdrop-blur { backdrop-filter: blur(8px); }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.border-b { border-bottom-width: 1px; }
.select-none { user-select: none; }
.cursor-move { cursor: move; }
.font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
.text-xs { 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); }
.h-10 {
height: 2.5rem;
}
.bg-white\/95 {
background-color: var(--surface, rgba(255, 255, 255, 0.95));
}
.backdrop-blur {
backdrop-filter: blur(8px);
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.border-b {
border-bottom-width: 1px;
}
.select-none {
user-select: none;
}
.cursor-move {
cursor: move;
}
.font-mono {
font-family:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
}
.text-xs {
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 {
height: 40px;
background-color: var(--surface, rgba(255, 255, 255, 0.95));
background-color: var(--surface, #ffffff);
border-bottom: 1px solid var(--border, #e5e7eb);
display: flex;
align-items: center;
@ -147,9 +219,11 @@
padding: 0 16px;
user-select: none;
cursor: move;
position: relative;
z-index: 10;
}
.window-header .font-mono {
font-family: 'Fira Code', monospace;
font-family: "Fira Code", monospace;
font-size: 12px;
font-weight: 700;
color: var(--accent, #16a34a);
@ -178,3 +252,34 @@
position: relative;
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.left = windowObj.previousState.left;
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 {
// Maximize
windowObj.previousState = {
@ -210,11 +216,17 @@ if (typeof window.WindowManager === "undefined") {
? document.getElementById("taskbar").offsetHeight
: 0;
// Adjust for minibar height (fixed 28px at top)
const minibarHeight = 28;
windowEl.style.width = "100%";
windowEl.style.height = `calc(100% - ${taskbarHeight}px)`;
windowEl.style.top = "0px";
windowEl.style.height = `calc(100% - ${taskbarHeight}px - ${minibarHeight}px)`;
windowEl.style.top = `${minibarHeight}px`;
windowEl.style.left = "0px";
windowObj.isMaximized = true;
// Add CSS class to body to hide background content
document.body.classList.add("window-maximized");
}
this.focus(id);
}