tray apparatus scales w. fluid rem; sig-select 9×2 middling breakpoint — $handle-exposed was 48px fixed while #id_tray_btn is 3rem, so on big-rem viewports (clamp(14px, 2.4vmin, 22px) → up to 22px on tall screens, btn=66) the btn's flex parent (#id_tray_handle) shrank the btn from 66×66 → 48×66 via default flex-shrink:1 in portrait (elongated tall ellipse), and in landscape the btn overflowed the 48px-tall handle vertically (extending 9px past viewport top in closed state); fix: $handle-exposed: 3rem matches the btn so it fills the exposed area at every rem; $handle-rect-h: 4.5rem (was 72px) gives the visible rail thickness a touch of breathing room around the btn at every scale; landscape rules in the same partial that hard-coded 48px / 72px (#id_tray_handle { height: 48px }, #id_tray_grip { bottom: calc(48px/2 - 0.125rem); width: 72px }) now reference the variables so they track in sync — tray.js _computeBounds() swapped from _btn.offsetWidth/Height_handle.offsetWidth/Height for the same reason: even with the SCSS fix, measuring the btn would re-introduce the offset when btn and handle drift (which they shouldn't now, but the handle is the layout-defining element so measure it directly); id_kit_btn added as fallback for id_gear_btn (which no longer renders on the room page) so the open-state landscape wrap height anchors to the bottom-right kit btn instead of the full viewport — id_tray_handle cached on the module via _handle ref alongside _btn and cleared in reset() ; sig-select grid jumped straight from 6 cols (narrow landscape) → 18 cols × 3rem at min-width: 900px, but 18×3rem + 7rem modal margins needs ~1376px to clear at rem=22 so the cards spilled off the sides on common 1280-wide laptops + the previous-era 9×2 middling layout had simply been dropped; new cascade in _card-deck.scss mirrors the comment's documented intent: 6 cols default landscape (row layout, stage beside grid) → 9 cols × 3rem at min-width: 900px (column layout, stage above grid) → 18 cols × 3rem at min-width: 1400px → 18 × 5rem at min-width: 1800px (unchanged) — verified in Claudezilla across iphone-14 portrait (rem=14, btn=42 square, handle right edge at viewport right), 816×826 portrait near-landscape (rem=19.6, btn=58.75 square no longer elongated), 1149×751 landscape mid (rem=18, btn=54 square at viewport top, 9×2 grid), 1789×1111 desktop XL (rem=22, btn=66 square at viewport top, 18×1 grid)
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-17 23:21:02 -04:00
parent f9cd08a510
commit ace8612099
3 changed files with 54 additions and 28 deletions

View File

@@ -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') {

View File

@@ -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; }

View File

@@ -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 {