// VirtualGrid's column-count policy, extracted as a pure function so the viewport contract's UPPER // end is unit-testable without a layout engine (the lower end, the 768-wide tablet floor, needs no policy — // the grid simply drops to the columns that fit). // // An auto-fill grid answers "more room" with more columns, never bigger ones, so it always settles // just above its minimum column width no matter how wide the screen is. Measured at 3440px, // the feed laid out 11 columns of 264px — a diluted feed is as bad a failure of the viewport contract // as a broken one, so `maxCols` stops the multiplication and lets the leftover width flow into the // items instead. The cap can only bind ABOVE the width at which that many columns already fit, so the // 1024px floor and everything near it is unchanged. How far up that reaches is per view: the feed's // small cards (`feedView.cardsSmall`, 11) are already capped at 2560 with the filter rail collapsed — // 12 would fit there — while the regular cards stay uncapped up to 2560. /** The grid's gutter in px; the column maths has to net it out on both sides of the division. */ export const GRID_GAP = 16; /** Columns that fit `width` (never more than `maxCols`), or 1 for the single-column layout * (`minCol == null`). */ export function columnsFor( minCol: number | null, width: number, maxCols: number | null = null, ): number { if (minCol == null) return 1; const fit = Math.max(1, Math.floor((width + GRID_GAP) / (minCol + GRID_GAP))); return maxCols == null ? fit : Math.min(fit, maxCols); } /** The px width one item gets once `columnsFor` has chosen the count — i.e. where the width saved by * the cap actually goes. */ export function itemWidthFor(width: number, cols: number): number { return Math.floor((width - GRID_GAP * (cols - 1)) / cols); }