diff --git a/src/apps/epic/static/apps/epic/room.js b/src/apps/epic/static/apps/epic/room.js index 56d4081..4fdac38 100644 --- a/src/apps/epic/static/apps/epic/room.js +++ b/src/apps/epic/static/apps/epic/room.js @@ -17,7 +17,8 @@ } else { scaleTable(); } - window.addEventListener('resize', scaleTable); + window.addEventListener('resize', scaleTable); + window.addEventListener('resize:end', scaleTable); }()); (function () { @@ -83,8 +84,20 @@ } } - window.addEventListener('load', sizeSigModal); - window.addEventListener('resize', sizeSigModal); + window.addEventListener('load', sizeSigModal); + window.addEventListener('resize', sizeSigModal); + window.addEventListener('resize:end', sizeSigModal); +}()); + +// Dispatch a custom 'resize:end' event 500 ms after the last 'resize' fires. +// scaleTable, sizeSigModal, and Tray._reposition all subscribe to it so they +// re-measure with settled viewport dimensions after rapid resize sequences. +(function () { + var t; + window.addEventListener('resize', function () { + clearTimeout(t); + t = setTimeout(function () { window.dispatchEvent(new Event('resize:end')); }, 500); + }); }()); (function () { diff --git a/src/apps/epic/static/apps/epic/tray.js b/src/apps/epic/static/apps/epic/tray.js index 595f0d1..febcc5b 100644 --- a/src/apps/epic/static/apps/epic/tray.js +++ b/src/apps/epic/static/apps/epic/tray.js @@ -290,6 +290,42 @@ var Tray = (function () { } } + // Force-close and reposition to settled bounds. Called on both 'resize' + // (snap without transition to avoid flicker during continuous events) and + // 'resize:end' (re-measures after the viewport has stopped moving). + function _reposition() { + _cancelPendingHide(); + _open = false; + if (_btn) _btn.classList.remove('open'); + if (_wrap) _wrap.classList.remove('wobble', 'snap', 'tray-dragging'); + + if (_isLandscape()) { + // Ensure tray is visible before measuring bounds. + if (_tray) _tray.style.display = 'grid'; + if (_wrap) { _wrap.style.left = ''; _wrap.style.bottom = ''; _wrap.style.width = ''; } + _computeBounds(); + _computeCellSize(); + if (_wrap) { + _wrap.classList.add('tray-dragging'); + _wrap.style.top = _maxTop + 'px'; + void _wrap.offsetWidth; // flush reflow so position lands before transition restored + _wrap.classList.remove('tray-dragging'); + } + } else { + if (_tray) _tray.style.display = 'none'; + if (_wrap) { _wrap.style.top = ''; _wrap.style.height = ''; } + _computeBounds(); + _applyVerticalBounds(); + _computeCellSize(); + if (_wrap) { + _wrap.classList.add('tray-dragging'); + _wrap.style.left = _maxLeft + 'px'; + void _wrap.offsetWidth; // flush reflow + _wrap.classList.remove('tray-dragging'); + } + } + } + function init() { _wrap = document.getElementById('id_tray_wrap'); _btn = document.getElementById('id_tray_btn'); @@ -403,42 +439,8 @@ var Tray = (function () { }; _btn.addEventListener('click', _onBtnClick); - window.addEventListener('resize', function () { - // Always close on resize: bounds change invalidates current position. - // Cancel any in-flight close animation, then force-close state. - _cancelPendingHide(); - _open = false; - if (_btn) _btn.classList.remove('open'); - if (_wrap) _wrap.classList.remove('wobble', 'snap', 'tray-dragging'); - - if (_isLandscape()) { - // Ensure tray is visible before measuring bounds. - if (_tray) _tray.style.display = 'grid'; - if (_wrap) { _wrap.style.left = ''; _wrap.style.bottom = ''; _wrap.style.width = ''; } - _computeBounds(); - _computeCellSize(); - // Snap to closed without transition (resize fires continuously). - if (_wrap) { - _wrap.classList.add('tray-dragging'); - _wrap.style.top = _maxTop + 'px'; - void _wrap.offsetWidth; // flush reflow so position lands before transition restored - _wrap.classList.remove('tray-dragging'); - } - } else { - if (_tray) _tray.style.display = 'none'; - if (_wrap) { _wrap.style.top = ''; _wrap.style.height = ''; } - _computeBounds(); - _applyVerticalBounds(); - _computeCellSize(); - // Snap to closed without transition. - if (_wrap) { - _wrap.classList.add('tray-dragging'); - _wrap.style.left = _maxLeft + 'px'; - void _wrap.offsetWidth; // flush reflow - _wrap.classList.remove('tray-dragging'); - } - } - }); + window.addEventListener('resize', _reposition); + window.addEventListener('resize:end', _reposition); } // reset() — restores module state; used by Jasmine afterEach diff --git a/src/static_src/scss/_base.scss b/src/static_src/scss/_base.scss index 6d9c8c9..27576cf 100644 --- a/src/static_src/scss/_base.scss +++ b/src/static_src/scss/_base.scss @@ -326,6 +326,7 @@ body { border-left: 0.1rem solid rgba(var(--secUser), 0.3); padding: 1rem 0; gap: 0; + z-index: 100; #id_footer_nav { flex-direction: column-reverse; diff --git a/src/static_src/scss/_room.scss b/src/static_src/scss/_room.scss index 0d6ee11..f3e657a 100644 --- a/src/static_src/scss/_room.scss +++ b/src/static_src/scss/_room.scss @@ -40,6 +40,27 @@ html:has(.gate-backdrop) { overflow: hidden; } +// Aperture fill — solid --duoUser layer that covers the game table (.room-page). +// Uses position:absolute so it's clipped to .room-page bounds (overflow:hidden), +// naturally staying below the h2 title + navbar/footer in both orientations. +// Sits at z-90: below blur backdrops (z-100) which render on top via backdrop-filter. +// Fades in/out via opacity transition when a backdrop class is present. +#id_aperture_fill { + position: absolute; + inset: 0; + background: rgba(var(--duoUser), 1); + z-index: 90; + pointer-events: none; + opacity: 0; + transition: opacity 0.15s ease; +} + +html:has(.gate-backdrop) #id_aperture_fill, +html:has(.sig-backdrop) #id_aperture_fill, +html:has(.role-select-backdrop) #id_aperture_fill { + opacity: 1; +} + .gate-backdrop { position: fixed; inset: 0; @@ -781,8 +802,8 @@ $card-h: 60px; // Landscape mobile — aggressively scale down to fit short viewport @media (orientation: landscape) and (max-width: 1440px) { // Sink navbar below gate/role-select overlays when a modal is open. - // Landscape navbar z-index is 300 (_base.scss); gate-backdrop/overlay are - // 100/120, so the sidebar bleeds over the modal without this override. + // Landscape navbar z-index is 100 (_base.scss); gate-backdrop/overlay are + // 100/120 — same level causes paint-order ties so we drop it to 50. html:has(.gate-backdrop) body .container .navbar, html:has(.role-select-backdrop) body .container .navbar { z-index: 50; @@ -959,7 +980,7 @@ html:has(.sig-backdrop) { .sig-card { aspect-ratio: 5 / 8; - border-radius: 3px; + border-radius: 0.4rem; background: rgba(var(--priUser), 0.97); border: 1px solid rgba(var(--secUser), 0.3); position: relative; @@ -1061,7 +1082,7 @@ html:has(.sig-backdrop) { .sig-overlay { padding-left: 4rem; } .sig-modal { max-width: none; } .sig-deck-grid { - grid-template-columns: repeat(9, 1fr); + grid-template-columns: repeat(9, minmax(0, 90px)); margin: 0; } } diff --git a/src/templates/apps/gameboard/room.html b/src/templates/apps/gameboard/room.html index 3a08f54..9ea6335 100644 --- a/src/templates/apps/gameboard/room.html +++ b/src/templates/apps/gameboard/room.html @@ -7,6 +7,7 @@ {% block content %}