diff --git a/src/screens/CategoryGridScreen.tsx b/src/screens/CategoryGridScreen.tsx
index 62af8d3..a210471 100644
--- a/src/screens/CategoryGridScreen.tsx
+++ b/src/screens/CategoryGridScreen.tsx
@@ -124,9 +124,9 @@ export function CategoryGridScreen({ title, items, groups, isLoading = false, lo
onClick={handlePosterClick}
onScrollActivity={handleGridScroll}
onNearBottom={onLoadMore}
+ isLoadingMore={isLoadingMore}
/>
)}
- {isLoadingMore &&
{t('auto.loading')}
}
{/* Right: detail panel */}
@@ -245,7 +245,7 @@ function PanelIconBtn({ title, icon, onClick }: { title: string; icon: React.Rea
-function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClick, onScrollActivity, onNearBottom }: {
+function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClick, onScrollActivity, onNearBottom, isLoadingMore = false }: {
items: Meta[];
selectedId: string | null;
posterPrefs: PosterPrefs;
@@ -253,6 +253,7 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic
onClick: (m: Meta) => void;
onScrollActivity: () => void;
onNearBottom?: () => void;
+ isLoadingMore?: boolean;
}) {
const scrollRef = useRef(null);
const rafRef = useRef(null);
@@ -282,7 +283,9 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic
? Math.max(GRID_MIN_COLUMN_WIDTH, (availableWidth - GRID_GAP_X * (columns - 1)) / columns)
: GRID_MIN_COLUMN_WIDTH;
const rowStep = itemHeight + GRID_GAP_Y;
- const rowCount = Math.ceil(items.length / columns);
+ const placeholderCount = isLoadingMore ? columns : 0;
+ const slotCount = items.length + placeholderCount;
+ const rowCount = Math.ceil(slotCount / columns);
const totalHeight = GRID_PADDING_TOP + GRID_PADDING_BOTTOM + Math.max(0, rowCount * itemHeight + Math.max(0, rowCount - 1) * GRID_GAP_Y);
const startRow = Math.max(0, Math.floor((viewport.scrollTop - GRID_PADDING_TOP) / rowStep) - GRID_OVERSCAN_ROWS);
const endRow = Math.min(rowCount, Math.ceil((viewport.scrollTop + viewport.height - GRID_PADDING_TOP) / rowStep) + GRID_OVERSCAN_ROWS);
@@ -294,11 +297,13 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic
}, [viewport.scrollTop, viewport.height, totalHeight, rowStep, onNearBottom]);
const visible: Array<{ item: Meta; row: number; col: number }> = [];
+ const placeholders: Array<{ row: number; col: number }> = [];
for (let row = startRow; row < endRow; row++) {
for (let col = 0; col < columns; col++) {
- const item = items[row * columns + col];
- if (!item) continue;
- visible.push({ item, row, col });
+ const index = row * columns + col;
+ const item = items[index];
+ if (item) visible.push({ item, row, col });
+ else if (index < slotCount) placeholders.push({ row, col });
}
}
@@ -330,6 +335,13 @@ function VirtualizedPosterGrid({ items, selectedId, posterPrefs, onHover, onClic
);
})}
+ {placeholders.map(({ row, col }) => {
+ const left = GRID_PADDING_X + col * (columnWidth + GRID_GAP_X) + Math.max(0, (columnWidth - posterPrefs.width) / 2);
+ const top = GRID_PADDING_TOP + row * rowStep;
+ return (
+
+ );
+ })}
);
@@ -395,7 +407,6 @@ const S: Record = {
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
- position: 'relative',
},
header: {
display: 'flex',
@@ -444,20 +455,6 @@ const S: Record = {
contain: 'strict',
willChange: 'scroll-position',
},
- loadingMoreBar: {
- position: 'absolute',
- bottom: '1rem',
- left: '50%',
- transform: 'translateX(-50%)',
- background: 'rgba(20,22,30,0.9)',
- border: '1px solid rgba(255,255,255,0.1)',
- borderRadius: '1.25rem',
- padding: '0.4375rem 1.125rem',
- color: 'rgba(255,255,255,0.7)',
- fontSize: '0.8125rem',
- fontWeight: 600,
- pointerEvents: 'none',
- },
right: {
width: '18.75rem', flexShrink: 0,
background: '#0C0D18',