an app to share curated trails sidetrail.app
atproto nextjs react rsc
50
fork

Configure Feed

Select the types of activity you want to include in your feed.

masonry

+28 -5
+1
app/FloatingAvatar.tsx
··· 58 58 target="_blank" 59 59 rel="noopener noreferrer" 60 60 className="FloatingAvatar-link" 61 + tabIndex={-1} 61 62 > 62 63 <img 63 64 src={src}
+8 -4
app/HomeTrailsList.css
··· 1 1 .HomeTrailsList-grid { 2 2 max-width: 800px; 3 3 margin: 0 auto; 4 - display: grid; 5 - grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); 6 - gap: 1.5rem; 4 + column-count: 2; 5 + column-gap: 1.5rem; 7 6 padding: 0 1rem; 8 7 min-height: 1px; 9 8 } 10 9 10 + .HomeTrailsList-grid > * { 11 + break-inside: avoid; 12 + margin-bottom: 1.5rem; 13 + } 14 + 11 15 @media (max-width: 768px) { 12 16 .HomeTrailsList-grid { 13 - grid-template-columns: 1fr; 17 + column-count: 1; 14 18 } 15 19 }
+19 -1
app/HomeTrailsList.tsx
··· 7 7 trails: TrailCardData[]; 8 8 }; 9 9 10 + // Reorder items for CSS columns masonry: [1,2,3,4,5,6] -> [1,3,5,2,4,6] 11 + // CSS columns fills top-to-bottom, so left column gets first half of DOM. 12 + // We put odd-ranked items first (left col), then even-ranked (right col). 13 + // Visual order reads left-to-right: 1,2 / 3,4 / 5,6 14 + // Tab order follows DOM: 1 -> 3 -> 5 -> 2 -> 4 -> 6 (column-wise) 15 + function interleaveForMasonry<T>(items: T[]): T[] { 16 + const result: T[] = []; 17 + for (let i = 0; i < items.length; i += 2) { 18 + result.push(items[i]); 19 + } 20 + for (let i = 1; i < items.length; i += 2) { 21 + result.push(items[i]); 22 + } 23 + return result; 24 + } 25 + 10 26 export function HomeTrailsList({ trails }: Props) { 11 27 if (trails.length === 0) { 12 28 return ( ··· 14 30 ); 15 31 } 16 32 33 + const reordered = interleaveForMasonry(trails); 34 + 17 35 return ( 18 36 <div className="HomeTrailsList-grid"> 19 - {trails.map((trail) => ( 37 + {reordered.map((trail) => ( 20 38 <TrailCard 21 39 key={trail.uri} 22 40 uri={trail.uri}