PR-C1: adaptive video grid sizing (foundation) #2
2 changed files with 25 additions and 8 deletions
|
|
@ -122,14 +122,14 @@ const buildUniformRow = (
|
|||
containerWidth: number,
|
||||
containerHeight: number,
|
||||
rowCount: number,
|
||||
minAspect: number,
|
||||
): TileRow => {
|
||||
const gap = TILE_GAP
|
||||
const availableHeight = containerHeight - (rowCount - 1) * gap
|
||||
const rowHeight = availableHeight / rowCount
|
||||
const tileWidth = (containerWidth - (columnCount - 1) * gap) / columnCount
|
||||
const fillAspect = tileWidth / rowHeight
|
||||
// Cap at 16:9 to avoid horizontally stretching video (review feedback)
|
||||
const aspectRatio = Math.max(TILE_ASPECT_PORTRAIT, Math.min(TILE_ASPECT_LANDSCAPE, fillAspect))
|
||||
const aspectRatio = Math.max(minAspect, Math.min(TILE_ASPECT_LANDSCAPE, fillAspect))
|
||||
const tileHeight = tileWidth / aspectRatio
|
||||
return {columnCount, tileWidth, tileHeight, aspectRatio}
|
||||
}
|
||||
|
|
@ -151,6 +151,7 @@ const buildMixedCandidate = (
|
|||
columnCount: number,
|
||||
containerWidth: number,
|
||||
containerHeight: number,
|
||||
minAspect: number,
|
||||
): AdaptiveTileGrid | undefined => {
|
||||
const gap = TILE_GAP
|
||||
const fullRowCount = Math.floor(tileCount / columnCount)
|
||||
|
|
@ -163,10 +164,9 @@ const buildMixedCandidate = (
|
|||
const tileWidthLast = (containerWidth - (remainder - 1) * gap) / remainder
|
||||
if (tileWidthLast <= 0) return undefined
|
||||
|
||||
// Find a common height that fits both row types and container
|
||||
const maxPossibleHeight = Math.min(
|
||||
tileWidthFull / TILE_ASPECT_PORTRAIT,
|
||||
tileWidthLast / TILE_ASPECT_PORTRAIT,
|
||||
tileWidthFull / minAspect,
|
||||
tileWidthLast / minAspect,
|
||||
(containerHeight - (totalRowCount - 1) * gap) / totalRowCount,
|
||||
)
|
||||
|
||||
|
|
@ -234,6 +234,11 @@ export const computeAdaptiveGrid = (
|
|||
): AdaptiveTileGrid | undefined => {
|
||||
if (tileCount <= 0 || containerWidth <= 0 || containerHeight <= 0) return undefined
|
||||
|
||||
// On landscape viewports, bound tiles at 1:1 (square) so they don't
|
||||
// become tall and narrow. On portrait viewports, allow 3:4 so more
|
||||
// tiles fit on a phone screen.
|
||||
const minAspect = containerWidth > containerHeight ? 1 : TILE_ASPECT_PORTRAIT
|
||||
|
||||
let best: AdaptiveTileGrid | undefined
|
||||
let bestScore: LayoutScore | undefined
|
||||
|
||||
|
|
@ -246,7 +251,13 @@ export const computeAdaptiveGrid = (
|
|||
|
||||
if (remainder === 0) {
|
||||
// Uniform layout — all rows identical
|
||||
const row = buildUniformRow(columnCount, containerWidth, containerHeight, totalRowCount)
|
||||
const row = buildUniformRow(
|
||||
columnCount,
|
||||
containerWidth,
|
||||
containerHeight,
|
||||
totalRowCount,
|
||||
minAspect,
|
||||
)
|
||||
const totalHeight = totalRowCount * row.tileHeight + (totalRowCount - 1) * TILE_GAP
|
||||
const totalWidth = columnCount * row.tileWidth + (columnCount - 1) * TILE_GAP
|
||||
const rows: TileRow[] = []
|
||||
|
|
@ -261,7 +272,13 @@ export const computeAdaptiveGrid = (
|
|||
}
|
||||
} else {
|
||||
// Mixed layout — last row has fewer tiles, make them wider
|
||||
candidate = buildMixedCandidate(tileCount, columnCount, containerWidth, containerHeight)
|
||||
candidate = buildMixedCandidate(
|
||||
tileCount,
|
||||
columnCount,
|
||||
containerWidth,
|
||||
containerHeight,
|
||||
minAspect,
|
||||
)
|
||||
}
|
||||
|
||||
if (!candidate) continue
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@
|
|||
<div
|
||||
bind:clientWidth={gridWidth}
|
||||
bind:clientHeight={gridHeight}
|
||||
class="min-h-0 flex-1 overflow-y-auto">
|
||||
class="min-h-0 flex-1 overflow-y-auto [scrollbar-gutter:stable]">
|
||||
{#if tileGrid}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
{#each tileGrid.rows as row, rowIndex}
|
||||
|
|
|
|||
Loading…
Reference in a new issue