ui(apps): shrink apps-section to visible-card count so few apps don't leave card-shaped gaps

The glass box was a CSS Grid with auto-fill columns of minmax(300px,
1fr), so it always painted across the full content area. With only 2
apps on a wide row the third/fourth column slots remained inside the
border as empty space — visually a card-shaped hole.

Drive the box's max-width off a --app-count CSS variable, capped at
(100% - 44px) so it can't escape the layout's symmetric 22px gutter.
margin: 22px auto keeps the horizontal padding symmetric in both the
capped (auto-centers the smaller box) and full-width (auto collapses
to 22+22) cases. --app-min (300/280 at the ≤1024 breakpoint) feeds
both the grid template and the cap formula so the responsive column
width stays a single source of truth.

apps-manager.js sets --app-count to the count of visible .app-card
elements after every render and after the sidebar search filter, so
filtering down to 2 hits also collapses the box. Floor of 1 keeps the
empty state usable.

Mobile (≤768) overrides max-width to none — single column already
fills, and the 10px gutter shouldn't be auto-centered.

Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-27 20:54:39 +01:00
parent 70415b0223
commit 0ba8e980ea
3 changed files with 34 additions and 4 deletions

View File

@ -3,14 +3,26 @@
/* App center cards, grid, tags, and detail view. Extracted from style.css. */
.apps-section {
--app-min: 300px;
--app-gap: 20px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin: 22px;
grid-template-columns: repeat(auto-fill, minmax(var(--app-min), 1fr));
gap: var(--app-gap);
margin: 22px auto;
padding: 22px;
background: rgba(var(--text-rgb), 0.025);
border: 1px solid var(--border-subtle);
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); the 100%-44px
cap keeps the same 22px symmetric gap from the layout edges when
there are enough cards to fill the row. Default 99 = no cap until
JS reports a real count. */
max-width: min(
calc(100% - 44px),
calc(var(--app-count, 99) * var(--app-min) + (var(--app-count, 99) - 1) * var(--app-gap) + 44px)
);
}
/* Override grid styling when showing loading content */
@ -392,6 +404,9 @@
margin: 10px;
padding: 12px;
gap: 12px;
/* Mobile is always a single column let it fill width regardless of
how many cards there are (no auto-centering, no card-count cap). */
max-width: none;
}
/* Install screen action buttons stack full-width below the console. */

View File

@ -1352,7 +1352,7 @@ html[data-theme="nebula"]::after {
/* Responsive Design */
@media (max-width: 1024px) {
.apps-section {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
--app-min: 280px;
}
}

View File

@ -538,6 +538,7 @@ class AppsManager {
container.appendChild(card);
});
this.populateInlineServiceButtons();
this.updateAppsCount();
// Re-apply any active sidebar search so changing category
// doesn't reveal apps that should be filtered out.
if (this.appsSearchQuery) this.filterAppsByQuery(this.appsSearchQuery);
@ -546,6 +547,19 @@ class AppsManager {
});
}
// Sync --app-count on .apps-section to the number of currently-visible
// cards so the CSS max-width cap shrinks the glass box to exactly the
// cards it holds. Driven from render and from the sidebar search filter.
updateAppsCount() {
const container = document.getElementById('apps-section');
if (!container) return;
let visible = 0;
container.querySelectorAll('.app-card').forEach(card => {
if (card.style.display !== 'none') visible++;
});
container.style.setProperty('--app-count', Math.max(visible, 1));
}
// Client-side substring filter wired to the sidebar search box.
// Cards carry data-search (built in createAppCard) so this stays
// a single querySelectorAll + display toggle.
@ -560,6 +574,7 @@ class AppsManager {
const hay = card.dataset.search || '';
card.style.display = (!q || hay.includes(q)) ? '' : 'none';
});
this.updateAppsCount();
}
clearAppsSearch() {