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>
User's empirical fix: on a 1280-class viewport (sidebar 220, content
~1010), --app-min 300 made the grid pick 3 cols because
floor((1010+20)/(300+20)) = 3, which left a 4-card category landing
as 3+1 with the orphan-row gap that's been the running visual
complaint. Bumping --app-min to 328 changes the floor to
floor((1010+20)/(328+20)) = 2, so the same 4-card category becomes
2+2 with no orphan.
Wider monitors are unaffected — a 1056px content area still fits 3
tracks of 328 (3*328 + 2*20 = 1024 ≤ 1056), and 1700px+ content
still fits 4. The cards-per-row count only drops on the narrow band
where 300 would otherwise have squeezed a third just-too-tight
column in.
Signed-off-by: librelad <librelad@digitalangels.vip>
The orphan-1 rebalance (4-on-3 → 2x2) cost too much: it dropped a
4-card category from 3 cards per row to 2 across the board, and would
do the same for any N where N % maxCols == 1. User feedback: 3
densely-packed cards with a small orphan-row gap reads better than
2 wider cards in a 2x2 layout — denser rows feel more compact and
let the eye scan more apps at once.
Back to the post-d4b7731 state: fixed-width tracks (auto-fill,
--app-min) so card widths line up across categories, plus the
sentinel that sets --app-count to 99 when visible cards meet the
natural full-width column count so the box reaches the layout edge.
The 4-on-3 case is now 3+1 again — the lone card on row 2 has empty
cells to its right, accepted as the lesser of two evils.
If the orphan ever becomes a real visual issue, the next move would
be JS-rendered last row (own sub-grid sized to its item count) rather
than reducing the column count globally.
Signed-off-by: librelad <librelad@digitalangels.vip>
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>
The auto-fill minmax(300px, 1fr) template stretched cards to fill the
glass box, so a 2-card category landed at ~301px each (the box
shrunk-and-stretched to a hair over 2*300) while a 3-card category
(box now full-width) landed at ~323px each. Cards visibly didn't
align between categories — the user spotted the 22px difference.
Switching the grid template to fixed-width tracks
(repeat(auto-fill, var(--app-min))) means cards are always exactly
--app-min (300px / 280px at ≤1024) regardless of how many are
visible. Card positions and widths line up across every category.
The natural-columns sentinel from the previous pass is no longer
load-bearing — with fixed-width cards, "full width" at high N gives
no extra card-width benefit, only trailing space inside the box.
updateAppsCount drops the measurement step and just sets the visible
count, letting the formula shrink the box around the cards.
Signed-off-by: librelad <librelad@digitalangels.vip>
Two follow-ups to the dynamic-width change:
1. The box was centred (margin: 22px auto), which moved cards out of
their original left position whenever the cap kicked in. Revert to
margin: 22px so the cards keep their left X — the box just shortens
on the right when there are few visible cards.
2. The formula assumed content-box, but style.css:4 sets
* { box-sizing: border-box } globally. With border-box max-width is
the outer width, so a 2-card cap of 664px gave content = 664 - 44
(padding) - 2 (border) = 618, just under the 620 needed to keep
2 columns at minmax(300px, 1fr) with gap 20 — grid silently dropped
to 1 column and the cards stacked. Formula now adds 46px (padding
+ border) plus 2px of sub-pixel buffer, so 2 cards have 622px of
content and reliably stay on one row.
Signed-off-by: librelad <librelad@digitalangels.vip>
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>
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.
Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>