diff --git a/containers/libreportal/frontend/css/apps.css b/containers/libreportal/frontend/css/apps.css index 17d30c5..fc0caa2 100644 --- a/containers/libreportal/frontend/css/apps.css +++ b/containers/libreportal/frontend/css/apps.css @@ -6,7 +6,12 @@ --app-min: 300px; --app-gap: 20px; display: grid; - grid-template-columns: repeat(auto-fill, minmax(var(--app-min), 1fr)); + /* Fixed-width tracks (not minmax/1fr) so cards stay exactly --app-min + wide regardless of how many are visible — a 2-card category lines up + with a 3-card category at the same X positions. 1fr stretching used + to rubber-band card widths between categories as the box width + changed. */ + grid-template-columns: repeat(auto-fill, var(--app-min)); gap: var(--app-gap); margin: 22px; padding: 22px; @@ -15,14 +20,11 @@ border-radius: 16px; /* Shrink the glass box to exactly the visible-card count so a row with two apps doesn't leave a card-shaped hole on the right. --app-count - is set from apps-manager.js (render + search filter). Box is - left-aligned (margin: 22px, not auto) so cards stay where they were - before the cap was introduced — the box just shortens on the right. - The 100%-44px cap honours the same 22px gutter at full width. The - formula is the outer width under border-box (the global default from + is set from apps-manager.js (render + search filter); the 100%-44px + cap keeps the same 22px gutter when the formula would otherwise + exceed parent width. Outer width under border-box (global default, style.css:4): N*min + (N-1)*gap + 44px padding + 2px border + 2px - buffer for sub-pixel rounding so 2 cards reliably stay on one row. - Default 99 = no cap until JS reports a real count. */ + buffer for sub-pixel rounding. Default 99 = no cap until JS reports. */ max-width: min( calc(100% - 44px), calc(var(--app-count, 99) * var(--app-min) + (var(--app-count, 99) - 1) * var(--app-gap) + 48px) diff --git a/containers/libreportal/frontend/js/components/app/apps-manager.js b/containers/libreportal/frontend/js/components/app/apps-manager.js index 61217e6..e270d72 100755 --- a/containers/libreportal/frontend/js/components/app/apps-manager.js +++ b/containers/libreportal/frontend/js/components/app/apps-manager.js @@ -552,11 +552,12 @@ class AppsManager { }); } - // Sync --app-count on .apps-section so the CSS max-width formula either - // shrinks the glass box around the visible cards (avoiding a card-shaped - // hole on the right) or — when the visible cards would already fill the - // row at full width — disengages the cap so the box runs edge-to-edge. - // Driven from render, sidebar search filter, and window resize. + // Sync --app-count on .apps-section so the CSS max-width formula shrinks + // the glass box around the visible cards (avoiding a card-shaped hole on + // the right). Cards themselves are fixed-width via the grid template, so + // card widths line up across categories — no "natural cols" measurement + // needed any more. Driven from render, sidebar search filter, and resize + // (the 100%-44px parent cap still depends on viewport width). updateAppsCount() { const container = document.getElementById('apps-section'); if (!container) return; @@ -564,27 +565,7 @@ class AppsManager { container.querySelectorAll('.app-card').forEach(card => { if (card.style.display !== 'none') visible++; }); - visible = Math.max(visible, 1); - - // How many columns the grid would naturally lay out at full width. - // If the visible count already meets that, suppress the cap (huge - // sentinel) so the formula gives way to the 100%-44px parent cap and - // the box reaches the layout edge. Without this the "exactly fills - // the row" case (e.g. 3 cards on a 3-col viewport) sits a few pixels - // shy of the edge while N+1 cards jumps straight to full width — - // visually inconsistent. - const style = getComputedStyle(container); - const minCol = parseFloat(style.getPropertyValue('--app-min')) || 300; - const gap = parseFloat(style.getPropertyValue('--app-gap')) || 20; - // Section eats 90px of parent's inner width before any card lands: - // 22px margin + 22px padding + 1px border, doubled. - const parent = container.parentElement; - const inside = parent ? Math.max(0, parent.clientWidth - 90) : 0; - const naturalCols = inside > 0 - ? Math.max(1, Math.floor((inside + gap) / (minCol + gap))) - : visible; - const effective = visible >= naturalCols ? 99 : visible; - container.style.setProperty('--app-count', effective); + container.style.setProperty('--app-count', Math.max(visible, 1)); } // Client-side substring filter wired to the sidebar search box.