diff --git a/containers/libreportal/frontend/js/components/app/apps-manager.js b/containers/libreportal/frontend/js/components/app/apps-manager.js index e270d72..299dd2c 100755 --- a/containers/libreportal/frontend/js/components/app/apps-manager.js +++ b/containers/libreportal/frontend/js/components/app/apps-manager.js @@ -552,12 +552,17 @@ class AppsManager { }); } - // 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). + // Sync --app-count on .apps-section. The CSS formula shrinks the glass + // box around the visible cards when there are too few to fill a row + // (so 1-2 cards on a 3-col viewport don't leave a card-shaped hole). + // When the visible count already meets the natural full-width column + // count, we pass a large sentinel (99) so the formula overshoots the + // 100%-44px parent cap and the box reaches the layout edge — otherwise + // a row with "exactly enough" cards would leave a visible gap between + // the box's right edge and the parent. Cards themselves are fixed + // --app-min width via the grid template, so widths line up across + // categories regardless of which branch fires. Driven from render, + // sidebar search filter, and window resize. updateAppsCount() { const container = document.getElementById('apps-section'); if (!container) return; @@ -565,7 +570,20 @@ class AppsManager { container.querySelectorAll('.app-card').forEach(card => { if (card.style.display !== 'none') visible++; }); - container.style.setProperty('--app-count', Math.max(visible, 1)); + visible = Math.max(visible, 1); + + 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); } // Client-side substring filter wired to the sidebar search box.