diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index a2c436a..106a469 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -232,7 +233,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 1e4dc09..934860a 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -232,7 +233,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index 6270f86..907cc9a 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -269,7 +270,7 @@ .stats-split-row{grid-template-columns:1fr} } - + diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index 0d799db..7cc0e0e 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -232,7 +233,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 787f0fa..44c8d40 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -3815,15 +3815,31 @@ body.hub-page-ai #page-ai { @media (max-width: 720px) { body.hub-page-ai .app-shell { padding-bottom: max(8px, env(safe-area-inset-bottom)); - height: 100dvh; - max-height: 100dvh; + height: var(--hub-vvh, 100dvh); + max-height: var(--hub-vvh, 100dvh); overflow: hidden; width: 100%; max-width: none; + box-sizing: border-box; + will-change: transform, height; } body.hub-page-ai { overflow: hidden; + background: var(--bg); + } + + body.hub-page-ai.hub-ai-keyboard-open .app-header, + body.hub-page-ai.hub-ai-keyboard-open #page-ai .page-head { + display: none; + } + + body.hub-page-ai.hub-ai-keyboard-open .ai-mobile-tabs { + margin-bottom: 4px; + } + + body.hub-page-ai.hub-ai-keyboard-open .ai-panel-head { + padding-bottom: 0; } body.hub-page-ai #page-ai { @@ -3896,23 +3912,29 @@ body.hub-page-ai #page-ai { body.hub-page-ai .ai-chat-panel { padding-bottom: 0; + display: flex; + flex-direction: column; + overflow: hidden; } body.hub-page-ai .ai-chat-messages { + flex: 1 1 auto; + min-height: 0; padding-bottom: 8px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; } body.hub-page-ai .ai-chat-form { - position: sticky; - bottom: 0; + position: relative; + flex-shrink: 0; z-index: 3; width: 100%; margin: 0; padding: 10px 0 max(10px, env(safe-area-inset-bottom)); - background: color-mix(in srgb, var(--panel) 92%, transparent); - backdrop-filter: blur(10px); + background: var(--panel); border-top: 1px solid var(--border-soft); - box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.28); + box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.18); } body.hub-page-ai .ai-chat-form textarea { diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 9586382..6471561 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -652,6 +652,7 @@ el.classList.toggle("hidden", el.id !== pageId); }); document.body.classList.toggle("hub-page-ai", page === "ai"); + syncHubAiMobileViewport(); if (page === "monitor") startMonitorPoll(); else stopMonitorPoll(); if (page === "settings") loadSettingsUI(); @@ -1005,8 +1006,73 @@ applyAiMobileTab(); } + let syncHubAiMobileViewport = () => {}; + + function initHubAiMobileViewport() { + const mq = window.matchMedia("(max-width: 720px)"); + const shell = document.querySelector(".app-shell"); + const chatInput = document.getElementById("ai-chat-input"); + if (!shell || !window.visualViewport) { + syncHubAiMobileViewport = () => {}; + return; + } + + const scrollChatToEnd = () => { + const box = document.getElementById("ai-chat-messages"); + if (box) requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; }); + }; + + syncHubAiMobileViewport = () => { + const onAi = document.body.classList.contains("hub-page-ai"); + if (!onAi || !mq.matches) { + shell.style.removeProperty("height"); + shell.style.removeProperty("max-height"); + shell.style.removeProperty("width"); + shell.style.removeProperty("transform"); + document.documentElement.style.removeProperty("--hub-vvh"); + document.body.classList.remove("hub-ai-keyboard-open"); + return; + } + const vv = window.visualViewport; + const h = Math.max(240, Math.round(vv.height)); + const top = Math.round(vv.offsetTop || 0); + const left = Math.round(vv.offsetLeft || 0); + document.documentElement.style.setProperty("--hub-vvh", `${h}px`); + shell.style.height = `${h}px`; + shell.style.maxHeight = `${h}px`; + shell.style.width = `${Math.round(vv.width)}px`; + shell.style.transform = + top > 0 || left > 0 ? `translate(${left}px, ${top}px)` : ""; + const keyboardLikely = + top > 0 || h < window.innerHeight * 0.82 || document.activeElement === chatInput; + document.body.classList.toggle("hub-ai-keyboard-open", keyboardLikely); + }; + + window.visualViewport.addEventListener("resize", syncHubAiMobileViewport); + window.visualViewport.addEventListener("scroll", syncHubAiMobileViewport); + window.addEventListener("resize", syncHubAiMobileViewport); + window.addEventListener("orientationchange", () => { + setTimeout(syncHubAiMobileViewport, 80); + }); + + if (chatInput) { + chatInput.addEventListener("focus", () => { + syncHubAiMobileViewport(); + scrollChatToEnd(); + setTimeout(syncHubAiMobileViewport, 50); + setTimeout(syncHubAiMobileViewport, 280); + }); + chatInput.addEventListener("blur", () => { + setTimeout(syncHubAiMobileViewport, 80); + setTimeout(syncHubAiMobileViewport, 320); + }); + } + syncHubAiMobileViewport(); + } + function initMobileLayout() { initAiMobileTabs(); + initHubAiMobileViewport(); let resizeTimer = null; let wasMobile = isMobileLayout(); window.addEventListener("resize", () => { diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 1f09803..6723e18 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -3,7 +3,7 @@ - + @@ -15,7 +15,7 @@ - + @@ -424,6 +424,6 @@ - + diff --git a/static/instance_theme.css b/static/instance_theme.css index 2b1667c..56e3dd9 100644 --- a/static/instance_theme.css +++ b/static/instance_theme.css @@ -27,14 +27,31 @@ gap: 8px; } + .container { + max-width: 100% !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; + overflow: visible !important; + } + .top-nav { - display: flex; - flex-wrap: nowrap; - overflow-x: auto; + display: flex !important; + flex-wrap: nowrap !important; + justify-content: flex-start !important; + align-items: stretch; + overflow-x: auto !important; + overflow-y: hidden; + width: 100%; + max-width: 100%; -webkit-overflow-scrolling: touch; + overscroll-behavior-x: contain; scrollbar-width: none; - gap: 6px; - padding-bottom: 2px; + gap: 6px !important; + margin-bottom: 12px !important; + padding: 2px 2px 6px; + scroll-padding-inline: 10px; + touch-action: pan-x; } .top-nav::-webkit-scrollbar { diff --git a/static/instance_theme.js b/static/instance_theme.js index fd42da5..6c5846a 100644 --- a/static/instance_theme.js +++ b/static/instance_theme.js @@ -251,6 +251,32 @@ }); } + function initMobileTopNav() { + const mq = window.matchMedia("(max-width: 720px)"); + + function scrollActiveTab(nav) { + const active = nav.querySelector("a.active"); + if (!active) return; + requestAnimationFrame(() => { + try { + active.scrollIntoView({ inline: "center", block: "nearest", behavior: "instant" }); + } catch (_) { + active.scrollIntoView(false); + } + }); + } + + function apply() { + if (!mq.matches) return; + document.querySelectorAll(".top-nav").forEach(scrollActiveTab); + } + + apply(); + mq.addEventListener("change", apply); + window.addEventListener("resize", apply); + window.addEventListener("orientationchange", apply); + } + function initFromHubMessage(data) { if (!data || data.type !== "hub-theme-sync") return; if (!isHubLinked()) return; @@ -285,6 +311,7 @@ const onReady = () => { initToggleUI(); + initMobileTopNav(); syncInlineStyles(get()); patchHubNavLinks(get()); observeDynamicLists();