diff --git a/src/apps/epic/static/apps/epic/tray.js b/src/apps/epic/static/apps/epic/tray.js index ec0bb26..2d3a2ad 100644 --- a/src/apps/epic/static/apps/epic/tray.js +++ b/src/apps/epic/static/apps/epic/tray.js @@ -9,10 +9,11 @@ var Tray = (function () { var _dragStartTop = null; var _dragHandled = false; - var _wrap = null; - var _btn = null; - var _tray = null; - var _grid = null; + var _wrap = null; + var _handle = null; + var _btn = null; + var _tray = null; + var _grid = null; // Role code → scrawl SVG name mapping for tray card display. var _ROLE_SCRAWL = { @@ -99,12 +100,20 @@ var Tray = (function () { // meets the gear button when open. Tray is flex:1 and fills the rest. // Open: wrap top = 0 (pinned to viewport top). // Closed: wrap top = -(gearBtnTop - handleH) = tray fully above viewport. - var gearBtn = document.getElementById('id_gear_btn'); + // Anchor: id_gear_btn historically; id_kit_btn is the live fallback so + // the open-state handle bottom lands at the bottom-right anchor instead + // of overlapping it (no id_gear_btn renders on the room page today). + var gearBtn = document.getElementById('id_gear_btn') + || document.getElementById('id_kit_btn'); var gearBtnTop = window.innerHeight; if (gearBtn) { gearBtnTop = Math.round(gearBtn.getBoundingClientRect().top); } - var handleH = (_btn && _btn.offsetHeight) || 48; + // handleH = #id_tray_handle's height (48px CSS), NOT _btn.offsetHeight. + // The 3rem btn can be larger than the 48px handle on big-rem viewports + // (clamp(14px, 2.4vmin, 22px) → btn=66 at rem=22); using btn here would + // push the handle's bottom that far below y=0 in closed state. + var handleH = (_handle && _handle.offsetHeight) || 48; // Pin wrap height so handle bottom = gear btn top when open. if (_wrap) _wrap.style.height = gearBtnTop + 'px'; @@ -116,7 +125,11 @@ var Tray = (function () { _maxTop = -(gearBtnTop - handleH); } else { // Portrait: wrap width = full viewport; handle parks at right edge. - var handleW = _btn.offsetWidth || 48; + // handleW = #id_tray_handle's width (48px CSS), NOT _btn.offsetWidth — + // same rationale as landscape: btn can be wider than handle on big-rem + // viewports, which would leave a visible gap between the handle's right + // edge and the viewport right edge in closed state. + var handleW = (_handle && _handle.offsetWidth) || 48; if (_wrap) _wrap.style.width = window.innerWidth + 'px'; _minLeft = 0; _maxLeft = window.innerWidth - handleW; @@ -398,10 +411,11 @@ var Tray = (function () { } function init() { - _wrap = document.getElementById('id_tray_wrap'); - _btn = document.getElementById('id_tray_btn'); - _tray = document.getElementById('id_tray'); - _grid = document.getElementById('id_tray_grid'); + _wrap = document.getElementById('id_tray_wrap'); + _handle = document.getElementById('id_tray_handle'); + _btn = document.getElementById('id_tray_btn'); + _tray = document.getElementById('id_tray'); + _grid = document.getElementById('id_tray_grid'); _roleIconsUrl = (_grid && _grid.dataset.roleIconsUrl) || null; if (!_btn) return; @@ -572,10 +586,11 @@ var Tray = (function () { delete el.dataset.role; }); } - _wrap = null; - _btn = null; - _tray = null; - _grid = null; + _wrap = null; + _handle = null; + _btn = null; + _tray = null; + _grid = null; } if (document.readyState === 'loading') { diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss index 3ef4db2..0c3905c 100644 --- a/src/static_src/scss/_card-deck.scss +++ b/src/static_src/scss/_card-deck.scss @@ -868,10 +868,12 @@ html:has(.sig-backdrop) { } // ─── Sig select: landscape overrides ───────────────────────────────────────── -// Landscape base: 9×2 grid of 3rem cards. At ≥992px (wide enough for 18 cards -// at 3rem + 4rem left + ~4rem right): collapse to a single 18×1 row so the -// stage preview gets maximum vertical real-estate. -// padding-left clears the fixed left navbar (JS sets right/bottom but not left). +// Cascade (each step is a SUPERSET of the prior): +// narrow landscape → 6 cols × 2.5rem, row layout (stage beside grid) +// ≥ 900px → 9×2 grid of 3rem cards, column layout (stage above) +// ≥ 1400px → 18×1 row of 3rem cards (wide enough that 18×3rem +// + ~7rem modal margins clears even at rem=22) +// ≥ 1800px → 18×1 row of 5rem cards + doubled sidebar padding // Grid margins reset to 0 — overlay padding handles all edge clearance. @media (orientation: landscape) { @@ -892,7 +894,7 @@ html:has(.sig-backdrop) { } @media (orientation: landscape) and (min-width: 900px) { - // Wide landscape: revert to stacked layout (stage top, 18-card row grid bottom). + // Middling landscape: stacked layout (stage top, 9×2 grid bottom). .sig-modal { flex-direction: column; align-items: stretch; @@ -903,11 +905,19 @@ html:has(.sig-backdrop) { margin-left: 3rem; } .sig-deck-grid { - grid-template-columns: repeat(18, 3rem); + grid-template-columns: repeat(9, 3rem); align-self: center; } } +@media (orientation: landscape) and (min-width: 1400px) { + // Wide landscape: 18-card single-row grid. 18×3rem + ~7rem modal margins + // clears the viewport here even at the fluid-rem ceiling (rem=22 → ~1376px). + .sig-deck-grid { + grid-template-columns: repeat(18, 3rem); + } +} + @media (orientation: landscape) and (min-width: 1800px) { // Sig overlay: clear doubled sidebars (8rem each instead of 4rem/6rem) .sig-overlay { padding-left: 8rem; padding-right: 8rem; } diff --git a/src/static_src/scss/_tray.scss b/src/static_src/scss/_tray.scss index a1655e3..9bf345b 100644 --- a/src/static_src/scss/_tray.scss +++ b/src/static_src/scss/_tray.scss @@ -20,8 +20,9 @@ $tray-w: 280px; $handle-rect-w: 10000px; -$handle-rect-h: 72px; -$handle-exposed: 48px; +$handle-rect-h: 4.5rem; // visible rail thickness — scales w. rem; ≈ btn (3rem) + breathing room +$handle-exposed: 3rem; // matches #id_tray_btn so the btn fills the exposed handle area + // (previously 48px → flex-shrink squished the btn on rem > 16px viewports) $handle-r: 1rem; $tray-bevel: 0.3rem; // inner bevel ring; grid must sit inside this @@ -304,20 +305,20 @@ $tray-bevel: 0.3rem; // inner bevel ring; grid must sit inside this } #id_tray_handle { - width: auto; // full width of wrap - height: 48px; // $handle-exposed — same exposed dimension as portrait + width: auto; // full width of wrap + height: $handle-exposed; // same exposed dimension as portrait — scales w. rem } #id_tray_grip { // Rotate 90°: centred horizontally, extends vertically. // bottom mirrors portrait's left: grip starts at handle centre and extends // toward the tray (upward in column-reverse layout). - bottom: calc(48px / 2 - 0.125rem); // $handle-exposed / 2 from handle bottom + bottom: calc(#{$handle-exposed} / 2 - 0.125rem); // $handle-exposed / 2 from handle bottom top: auto; left: 50%; transform: translateX(-50%); - width: 72px; // $handle-rect-h — narrow visible dimension - height: 10000px; // $handle-rect-w — extends upward into tray area + width: $handle-rect-h; // narrow visible dimension — scales w. rem + height: $handle-rect-w; // extends upward into tray area } #id_tray {