fix(apps): balance column count so 4-on-3-col wraps to 2x2 instead of leaving an orphan card
Screenshot showed a 4-card category laying out as 3+1 (three cards on row 1, Wireguard Easy alone on row 2 with two card-shaped empty cells on its right). Fixed-width tracks + auto-fill kept the cards aligned across categories but couldn't avoid the orphan — pure CSS grid has no way to collapse partial-row trailing cells when the column above them is filled. apps-manager.js now picks --app-cols deliberately: the natural column count for the viewport, reduced by one when the last row would otherwise be exactly one orphan card. 4 cards on a 3-col viewport becomes 2x2; 5 cards stays at 3+2; 6 stays at 3+3+0; 7 drops to 2-col so the last row gets a partner (still has one orphan at the very end since 7 is prime, but never below 2 cols — a single column stack reads worse than an orphan). CSS swap: grid-template-columns now consumes the new --app-cols custom property and uses minmax(--app-min, 1fr) so cards stretch within their tracks (the orphan-prevention dance means widths can vary across categories now — tradeoff for never having internal gaps). 1-card view still shrinks the box via the existing formula so a lone card isn't stretched across the full row. Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
49d06ec693
commit
35c06a90a5
@ -6,25 +6,25 @@
|
|||||||
--app-min: 300px;
|
--app-min: 300px;
|
||||||
--app-gap: 20px;
|
--app-gap: 20px;
|
||||||
display: grid;
|
display: grid;
|
||||||
/* Fixed-width tracks (not minmax/1fr) so cards stay exactly --app-min
|
/* JS sets --app-cols to a balanced track count: as many as fit the
|
||||||
wide regardless of how many are visible — a 2-card category lines up
|
row at min-width, but reduced by 1 if the natural count would
|
||||||
with a 3-card category at the same X positions. 1fr stretching used
|
orphan a single card on the last row (e.g. 4 cards on a 3-col
|
||||||
to rubber-band card widths between categories as the box width
|
viewport becomes a 2x2 grid instead of 3+1). Falls back to
|
||||||
changed. */
|
auto-fill while JS hasn't run yet. Cards stretch via 1fr so the
|
||||||
grid-template-columns: repeat(auto-fill, var(--app-min));
|
last row never leaves a card-shaped hole — accepting some width
|
||||||
|
variation across categories in exchange for no internal gaps. */
|
||||||
|
grid-template-columns: repeat(var(--app-cols, auto-fill), minmax(var(--app-min), 1fr));
|
||||||
gap: var(--app-gap);
|
gap: var(--app-gap);
|
||||||
margin: 22px;
|
margin: 22px;
|
||||||
padding: 22px;
|
padding: 22px;
|
||||||
background: rgba(var(--text-rgb), 0.025);
|
background: rgba(var(--text-rgb), 0.025);
|
||||||
border: 1px solid var(--border-subtle);
|
border: 1px solid var(--border-subtle);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
/* Shrink the glass box to exactly the visible-card count so a row with
|
/* Box reaches the layout edge for 2+ visible cards (sentinel from
|
||||||
two apps doesn't leave a card-shaped hole on the right. --app-count
|
apps-manager.js); for a single card the formula caps the box to
|
||||||
is set from apps-manager.js (render + search filter); the 100%-44px
|
one card's worth instead of stretching it across the full row.
|
||||||
cap keeps the same 22px gutter when the formula would otherwise
|
Outer under border-box (global, style.css:4): N*min + (N-1)*gap +
|
||||||
exceed parent width. Outer width under border-box (global default,
|
44px padding + 2px border + 2px sub-pixel buffer. */
|
||||||
style.css:4): N*min + (N-1)*gap + 44px padding + 2px border + 2px
|
|
||||||
buffer for sub-pixel rounding. Default 99 = no cap until JS reports. */
|
|
||||||
max-width: min(
|
max-width: min(
|
||||||
calc(100% - 44px),
|
calc(100% - 44px),
|
||||||
calc(var(--app-count, 99) * var(--app-min) + (var(--app-count, 99) - 1) * var(--app-gap) + 48px)
|
calc(var(--app-count, 99) * var(--app-min) + (var(--app-count, 99) - 1) * var(--app-gap) + 48px)
|
||||||
|
|||||||
@ -552,17 +552,21 @@ class AppsManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync --app-count on .apps-section. The CSS formula shrinks the glass
|
// Sync --app-cols + --app-count on .apps-section so the grid lays out
|
||||||
// box around the visible cards when there are too few to fill a row
|
// without orphans on the last 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
|
// --app-cols picks a balanced column count: as many as the row fits
|
||||||
// count, we pass a large sentinel (99) so the formula overshoots the
|
// naturally, but reduced by 1 when the natural choice would leave a
|
||||||
// 100%-44px parent cap and the box reaches the layout edge — otherwise
|
// single card alone on the last row (e.g. 4 cards on a 3-col viewport
|
||||||
// a row with "exactly enough" cards would leave a visible gap between
|
// becomes 2x2 instead of 3+1). 5+2, 6+3, etc are accepted — only the
|
||||||
// the box's right edge and the parent. Cards themselves are fixed
|
// worst case (last row has exactly 1 orphan) is rebalanced.
|
||||||
// --app-min width via the grid template, so widths line up across
|
//
|
||||||
// categories regardless of which branch fires. Driven from render,
|
// --app-count drives the CSS max-width cap: 1 visible card shrinks
|
||||||
// sidebar search filter, and window resize.
|
// the box to one card's worth (stretching one card to 1000px+ looks
|
||||||
|
// bad); 2+ cards pass a sentinel (99) so the cap defers to the
|
||||||
|
// 100%-44px parent and the box runs edge-to-edge.
|
||||||
|
//
|
||||||
|
// Driven from render, sidebar search filter, and window resize.
|
||||||
updateAppsCount() {
|
updateAppsCount() {
|
||||||
const container = document.getElementById('apps-section');
|
const container = document.getElementById('apps-section');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@ -579,11 +583,22 @@ class AppsManager {
|
|||||||
// 22px margin + 22px padding + 1px border, doubled.
|
// 22px margin + 22px padding + 1px border, doubled.
|
||||||
const parent = container.parentElement;
|
const parent = container.parentElement;
|
||||||
const inside = parent ? Math.max(0, parent.clientWidth - 90) : 0;
|
const inside = parent ? Math.max(0, parent.clientWidth - 90) : 0;
|
||||||
const naturalCols = inside > 0
|
const maxCols = inside > 0
|
||||||
? Math.max(1, Math.floor((inside + gap) / (minCol + gap)))
|
? Math.max(1, Math.floor((inside + gap) / (minCol + gap)))
|
||||||
: visible;
|
: visible;
|
||||||
const effective = visible >= naturalCols ? 99 : visible;
|
|
||||||
container.style.setProperty('--app-count', effective);
|
let cols = Math.min(visible, maxCols);
|
||||||
|
// Avoid orphaning a single card on the last row — reduce columns
|
||||||
|
// by one so the layout becomes (cols-1)+(cols-1)+… with a fuller
|
||||||
|
// tail. Don't drop below 2; a 1-col stack looks worse than an
|
||||||
|
// orphan. Only handles the immediate "last row is 1" case;
|
||||||
|
// last-row-of-2 etc are accepted as good enough.
|
||||||
|
if (cols > 2 && visible > cols && visible % cols === 1) {
|
||||||
|
cols--;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.style.setProperty('--app-cols', cols);
|
||||||
|
container.style.setProperty('--app-count', visible === 1 ? 1 : 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client-side substring filter wired to the sidebar search box.
|
// Client-side substring filter wired to the sidebar search box.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user