fix(apps): stretch cards to fill the row width so the box stays full-width on any zoom level
Fixed-width tracks + cap formula kept the box pinned to "N cards at 328px" outer regardless of viewport size, so zooming out left a massive empty band between the box's right edge and the layout edge. The box was no longer "dynamic" in any real sense — it scaled with the card count, not with the available content. Switching grid-template-columns to repeat(auto-fit, minmax(--app-min, 1fr)) lets cards stretch to fill the row, and auto-fit collapses trailing empty tracks so a 2-card row in a 3-track-wide viewport doesn't leave a 328px hole at the end. Zoom in/out now just widens or narrows the cards; the box always reaches the layout edge. This drops the cross-category card-width uniformity that the earlier fixed-width pass introduced — a 2-card category now lays out as two wide cards while a 3-card category gets three narrower ones. That's mutually exclusive with "box always full width" without leaving holes, and the user has shifted priority to full-width-always. JS cleanup: dropped updateAppsCount + its window-resize listener + its callsites in renderApps/filterAppsByQuery — no more --app-count or column-count measurement needed when the grid handles everything natively. Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
0b32405f0a
commit
bfda700794
@ -3,41 +3,27 @@
|
||||
/* App center cards, grid, tags, and detail view. Extracted from style.css. */
|
||||
|
||||
.apps-section {
|
||||
/* Track min-width is the lever for "how many columns at this viewport".
|
||||
auto-fill picks floor((content + gap) / (--app-min + gap)) tracks, so
|
||||
bumping --app-min from 300 to 328 pushes typical 1280px-class laptops
|
||||
from 3 cols → 2 cols where the third column would have been the
|
||||
orphan that ran into a half-empty last row. Wider monitors still hit
|
||||
3+ cols (e.g. 1056px content fits 3 tracks of 328 with room to
|
||||
spare), so density on bigger screens is unchanged. */
|
||||
--app-min: 328px;
|
||||
--app-gap: 20px;
|
||||
display: grid;
|
||||
/* Fixed-width tracks 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. auto-fill packs as many tracks
|
||||
as the row fits at the current viewport. The partial-row orphan
|
||||
(e.g. 4 cards on a 3-col viewport → 3+1 with the 4th alone on row
|
||||
2) is an accepted tradeoff: we tried rebalancing to 2x2, but that
|
||||
dropped the cards-per-row count too aggressively for larger
|
||||
viewports — keeping rows dense beats avoiding the orphan. */
|
||||
grid-template-columns: repeat(auto-fill, var(--app-min));
|
||||
/* auto-fit + minmax(min, 1fr): tracks stretch to fill the row width
|
||||
so zooming out gives wider cards instead of a card-shaped hole on
|
||||
the right (each track is at least --app-min wide; the 1fr upper
|
||||
bound shares any remaining content width across them). auto-fit
|
||||
collapses trailing empty tracks so a 2-card row in a 3-track-wide
|
||||
viewport doesn't leave a fixed-width empty slot at the end. The
|
||||
tradeoff is that card widths vary between categories (a 2-card
|
||||
category gets fatter cards than a 3-card one) and between
|
||||
viewport widths — that's the price of "box always reaches the
|
||||
layout edge". The 22px margin handles the gutter from the parent;
|
||||
no explicit max-width cap needed. */
|
||||
grid-template-columns: repeat(auto-fit, minmax(var(--app-min), 1fr));
|
||||
gap: var(--app-gap);
|
||||
margin: 22px;
|
||||
padding: 22px;
|
||||
background: rgba(var(--text-rgb), 0.025);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 16px;
|
||||
/* Box reaches the layout edge when the visible cards already fill a
|
||||
natural row (sentinel from apps-manager.js sets --app-count to 99);
|
||||
for fewer cards the formula caps the box to exactly that many so
|
||||
no card-shaped hole sits to the right of a small category.
|
||||
Outer under border-box (global, style.css:4): N*min + (N-1)*gap +
|
||||
44px padding + 2px border + 2px sub-pixel buffer. */
|
||||
max-width: min(
|
||||
calc(100% - 44px),
|
||||
calc(var(--app-count, 99) * var(--app-min) + (var(--app-count, 99) - 1) * var(--app-gap) + 48px)
|
||||
);
|
||||
}
|
||||
|
||||
/* Override grid styling when showing loading content */
|
||||
|
||||
@ -39,11 +39,6 @@ class AppsManager {
|
||||
constructor() {
|
||||
this.cache = new Map();
|
||||
this.setupTaskCompletionListener();
|
||||
// The dynamic-width box decides cap-vs-full based on the parent's
|
||||
// current size, so re-run it when the window resizes (drags, snaps,
|
||||
// devtools open). updateAppsCount is a fast no-op when no
|
||||
// #apps-section is on screen.
|
||||
window.addEventListener('resize', () => this.updateAppsCount());
|
||||
}
|
||||
|
||||
setupTaskCompletionListener() {
|
||||
@ -543,7 +538,6 @@ 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);
|
||||
@ -552,34 +546,6 @@ class AppsManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Sync --app-count on .apps-section so the CSS max-width cap shrinks
|
||||
// the box around a few visible cards (no card-shaped hole on the
|
||||
// right for a half-empty row) but disengages when visible cards meet
|
||||
// the natural full-width column count (box reaches the parent edge).
|
||||
// Driven from render, sidebar search filter, and window resize.
|
||||
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++;
|
||||
});
|
||||
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.
|
||||
// Cards carry data-search (built in createAppCard) so this stays
|
||||
// a single querySelectorAll + display toggle.
|
||||
@ -594,7 +560,6 @@ class AppsManager {
|
||||
const hay = card.dataset.search || '';
|
||||
card.style.display = (!q || hay.includes(q)) ? '' : 'none';
|
||||
});
|
||||
this.updateAppsCount();
|
||||
}
|
||||
|
||||
clearAppsSearch() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user