From 40c747a837213d4313fc69c6a3153f13da27b92b Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Sun, 5 Apr 2026 16:54:03 -0400 Subject: [PATCH] landscape navbar centering: reset portrait margin-right on .container-fluid + margin-left on .navbar-brand so sidebar contents align to horizontal centre; showGuard gains invertY option for modal-grid callers (role-select cards fly away from centre); gameboard.js showPortals gains viewport-half detection so game-kit tooltips show below when tokens are in upper half (landscape clip fix); position-strip top: 0; tighten gear-btn btn-abandon selector to #id_room_menu scope Co-Authored-By: Claude Sonnet 4.6 --- src/apps/epic/static/apps/epic/role-select.js | 3 ++- .../gameboard/static/apps/gameboard/gameboard.js | 14 ++++++++++++-- src/functional_tests/test_gatekeeper.py | 2 +- src/static_src/scss/_base.scss | 2 ++ src/static_src/scss/_room.scss | 2 +- src/templates/core/base.html | 11 +++++++---- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/apps/epic/static/apps/epic/role-select.js b/src/apps/epic/static/apps/epic/role-select.js index dff301e..011a704 100644 --- a/src/apps/epic/static/apps/epic/role-select.js +++ b/src/apps/epic/static/apps/epic/role-select.js @@ -185,7 +185,8 @@ var RoleSelect = (function () { function () { // dismiss (NVM / outside click) card.classList.remove("guard-active"); card.classList.remove("flipped"); - } + }, + { invertY: true } // modal grid: tooltip flies away from centre (upper→above, lower→below) ); }); diff --git a/src/apps/gameboard/static/apps/gameboard/gameboard.js b/src/apps/gameboard/static/apps/gameboard/gameboard.js index 77e7c1b..5d735cc 100644 --- a/src/apps/gameboard/static/apps/gameboard/gameboard.js +++ b/src/apps/gameboard/static/apps/gameboard/gameboard.js @@ -139,8 +139,18 @@ function initGameKitTooltips() { const rawLeft = tokenRect.left + tokenRect.width / 2; const clampedLeft = Math.max(halfW + 8, Math.min(rawLeft, window.innerWidth - halfW - 8)); portal.style.left = Math.round(clampedLeft) + 'px'; - portal.style.top = Math.round(tokenRect.top) + 'px'; - portal.style.transform = `translate(-50%, calc(-100% - 0.5rem - ${miniHeight}px))`; + + // Show above when token is in lower viewport half; below when in upper half + // (avoids clipping when game-kit tokens sit near the top in landscape mode). + const tokenCenterY = tokenRect.top + tokenRect.height / 2; + const showBelow = tokenCenterY < window.innerHeight / 2; + if (showBelow) { + portal.style.top = Math.round(tokenRect.bottom) + 'px'; + portal.style.transform = 'translate(-50%, 0.5rem)'; + } else { + portal.style.top = Math.round(tokenRect.top) + 'px'; + portal.style.transform = `translate(-50%, calc(-100% - 0.5rem - ${miniHeight}px))`; + } if (isEquippable) { const mainRect = portal.getBoundingClientRect(); diff --git a/src/functional_tests/test_gatekeeper.py b/src/functional_tests/test_gatekeeper.py index 66f9e21..d27e84f 100644 --- a/src/functional_tests/test_gatekeeper.py +++ b/src/functional_tests/test_gatekeeper.py @@ -248,7 +248,7 @@ class GatekeeperTest(FunctionalTest): self.browser.find_element(By.CSS_SELECTOR, ".gear-btn").click() self.wait_for( - lambda: self.browser.find_element(By.CSS_SELECTOR, ".btn-abandon") + lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_room_menu .btn-abandon") ).click() self.confirm_guard() diff --git a/src/static_src/scss/_base.scss b/src/static_src/scss/_base.scss index 02f8363..f45645a 100644 --- a/src/static_src/scss/_base.scss +++ b/src/static_src/scss/_base.scss @@ -222,6 +222,7 @@ body { justify-content: space-between; gap: 1rem; padding: 0 0.25rem; + margin: 0; // reset portrait margin-right: 0.5rem so container fills full sidebar width > #id_cont_game { flex-shrink: 0; order: -1; } // cont-game above brand @@ -246,6 +247,7 @@ body { .navbar-brand { order: 1; // brand at bottom width: 100%; + margin-left: 0; // reset portrait margin-left: 1rem display: flex; justify-content: center; } diff --git a/src/static_src/scss/_room.scss b/src/static_src/scss/_room.scss index abe26b4..38e484f 100644 --- a/src/static_src/scss/_room.scss +++ b/src/static_src/scss/_room.scss @@ -320,7 +320,7 @@ html:has(.gate-backdrop) .position-strip .gate-slot button { pointer-events: aut .position-strip { position: absolute; - top: 1.5rem; + top: 0; left: 0; right: 0; z-index: 130; diff --git a/src/templates/core/base.html b/src/templates/core/base.html index e8a85dd..b6f6df2 100644 --- a/src/templates/core/base.html +++ b/src/templates/core/base.html @@ -73,8 +73,9 @@ var _cb = null; var _onDismiss = null; - function show(anchor, message, callback, onDismiss) { + function show(anchor, message, callback, onDismiss, options) { if (!portal) return; + options = options || {}; _cb = callback; _onDismiss = onDismiss || null; portal.querySelector('.guard-message').innerHTML = message; @@ -85,12 +86,14 @@ var cleft = Math.max(pw / 2 + 8, Math.min(rawLeft, window.innerWidth - pw / 2 - 8)); portal.style.left = Math.round(cleft) + 'px'; var cardCenterY = rect.top + rect.height / 2; - if (cardCenterY < window.innerHeight / 2) { - // button in upper half → show below + // Default: upper half → below (avoids viewport top edge for navbar/fixed buttons). + // invertY: upper half → above (for modal grids where tooltip should fly away from centre). + var showBelow = (cardCenterY < window.innerHeight / 2); + if (options.invertY) showBelow = !showBelow; + if (showBelow) { portal.style.top = Math.round(rect.bottom) + 'px'; portal.style.transform = 'translate(-50%, 0.5rem)'; } else { - // button in lower half → show above portal.style.top = Math.round(rect.top) + 'px'; portal.style.transform = 'translate(-50%, calc(-100% - 0.5rem))'; }