From d31d9ce212d2d7fb5d21cb8f16847436d92a78ea Mon Sep 17 00:00:00 2001 From: "Ryan Salazar (via Claude)" Date: Sat, 16 May 2026 01:07:32 +0000 Subject: [PATCH] feed: intersperse mobile-only 300x250 ads every 4 articles Mobile readers previously saw zero ads above the bottom of the feed (header leaderboard is hidden md:block, sidebar stack pushes below the feed at , wrapped in md:hidden so tablet/desktop are untouched (they already get SidebarAdStack to the right of the feed). - Ad pool: rotateAll('300x250') from src/lib/ads.ts (DB-backed, falls back to the 7-ad hardcoded pool) - Cycles through the pool if the feed is longer than the pool - Skips the insert after the very last article - Labelled "Sponsored", divide-y separator inherited from parent Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/home-page/components/ArticleFeed.tsx | 34 ++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/app/home-page/components/ArticleFeed.tsx b/src/app/home-page/components/ArticleFeed.tsx index 4bda80a..577a409 100644 --- a/src/app/home-page/components/ArticleFeed.tsx +++ b/src/app/home-page/components/ArticleFeed.tsx @@ -1,10 +1,14 @@ "use client"; -import React, { useState, useRef, useId, useEffect, useCallback } from "react"; +import React, { Fragment, useState, useRef, useId, useEffect, useCallback, useMemo } from "react"; import AppImage from "@/components/ui/AppImage"; import SidebarAdStack from "@/components/SidebarAdStack"; +import AdImage from "@/components/AdImage"; +import { rotateAll, type Ad } from "@/lib/ads"; import { PersonIcon, ChevronLeftIcon, ChevronRightIcon, SearchIcon, CloseIcon } from "@/components/ui/Icons"; import Link from "next/link"; +const MOBILE_AD_EVERY_N = 4; + interface ArticleItem { title: string; @@ -86,6 +90,10 @@ export default function ArticleFeed() { const sentinelRef = useRef(null); const feedId = useId(); + // Shuffled 300x250 pool for mobile in-feed inserts (md:hidden). + // Tablet/desktop already get the SidebarAdStack to the right of the feed. + const mobileInfeedAds = useMemo(() => rotateAll("300x250"), []); + // Load preference from localStorage useEffect(() => { try { @@ -550,8 +558,18 @@ export default function ArticleFeed() { : `/articles/${article?.slug}`; const linkProps = isImported ? { target: "_blank", rel: "noopener noreferrer" } : {}; + // Mobile in-feed ad after every Nth article (skip if last item). + const isAdSlot = + (i + 1) % MOBILE_AD_EVERY_N === 0 && + i + 1 < displayedArticles.length && + mobileInfeedAds.length > 0; + const inFeedAd = isAdSlot + ? mobileInfeedAds[Math.floor((i + 1) / MOBILE_AD_EVERY_N - 1) % mobileInfeedAds.length] + : null; + return ( -
+ +
+ {inFeedAd && ( +
+ + Sponsored + + +
+ )} +
); })}