- layout.tsx: load Lora (serif) + Inter (sans) via next/font/google,
self-hosted at build time, exposed as --font-serif / --font-sans
CSS variables on <html>. No CDN fetch, no FOIT, no GDPR concern.
- tailwind.css: --font-heading and --font-body now reference the
next/font variables with Georgia / -apple-system fallbacks for the
brief window before the self-hosted face is ready. --font-mono
upgraded to IBM Plex Mono → ui-monospace → Courier New fallback
(DoubleTicker chips already specify IBM Plex Mono directly).
- ArticleFeed: drop the FEATURED filter chip (duplicates the
Staff Editorial section above), add publish date alongside the
byline on every card, lift the homepage feed cap from 200 → 5000.
- api/public/posts: lift MAX_LIMIT to 10000 so client-side
pagination + infinite-scroll see the full archive.
The Featured-story card's right edge wasn't lining up with any of the
ad banners below or beside it on the homepage. Two root causes:
1. Pages I redesigned (/about, /about/contact, /about/team,
/about/advertise, /show-coverage/[slug]) used `max-w-6xl px-6`
(1152px / 24px-pad). Every other page uses `max-w-container
px-4` (1200px / 16px-pad). Pages didn't line up with the
header leaderboard or with each other.
2. ArticleFeed used a flexible 12-col grid where lg:col-span-4
ends up ~280px wide (depending on container) — so the 300x250
image inside the sidebar didn't extend to the column's right
edge, leaving a visual gap relative to the full-width Featured
card above.
Fix:
- All redesigned pages now use `max-w-container mx-auto px-4`.
- ArticleFeed switches to `lg:grid-cols-[1fr,300px]` so the sidebar
column is *exactly* 300px wide. The 300x250 ads fill that column
flush, so their right edge meets the container's right padding
edge — same x-position as the Featured card's right edge above.
Also linkified the "Relevant Media Properties" copyright string in
Footer.tsx → opens https://www.relevantmediaproperties.com/ in a new
tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Latest section was showing "View Original »" for imported posts and
opening every click in a new tab, even though the /api/public/posts
endpoint doesn't expose an externalUrl — every link actually went to
the internal /articles/<slug> page. Two changes:
- Link text is now "Read More..." for every article in the feed.
- target="_blank" only applies when article.externalUrl is actually
populated (currently never, but keeps the door open for cross-site
syndication later). Internal /articles/<slug> links now open in the
same tab as the user expects.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The article-row metadata strip rendered three things side-by-side:
{category} | <SourceBadge source>
For imported WP posts whose `category` column was the placeholder string
"legacy", this produced a meaningless "LEGACY | IMPORTED" row on the
homepage. Now:
- Hide the category text entirely when value is "legacy" (any case);
it falls back to nothing while the AI-derived category propagates
in a separate data pass.
- Drop the vertical pipe separator.
- Remove the yellow "IMPORTED" SourceBadge — the article's category
is the user-relevant signal, not its data-source provenance.
The SourceBadge function is now unused and removed. The source-filter
control in the advanced filters panel is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 <lg breakpoints). This adds a 300x250 ad block after every 4th
article in <ArticleFeed>, 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) <noreply@anthropic.com>
Replace the four accent-family hex codes site-wide with the teal palette
(keeps the dark theme; only swaps the accent family, not the dark base):
#3b82f6 (accent primary CTA) → #5B7C8D (teal) [839×]
#2563eb (accent-dark / hover) → #4A6473 (darker teal) [44×]
#60a5fa (info link, lighter) → #8FB0C3 (mid teal) [68×]
#1e3a5f (accent-muted bg) → #2F4F5F (navy) [44×]
tailwind.config.js tokens updated to match (accent/accent-dark/accent-muted).
Sweep also covers tailwind.css. 108 files touched. The dark base (#0d0d0d,
#111, #161616, #1a1a1a etc.) is intentionally untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploy-1 build failed because NewsPageClient now calls useSearchParams.
- src/app/news/page.tsx: wrap NewsPageClient in <Suspense> so static
generation can bail out to client rendering cleanly.
P0-6 (Featured carousel arrows):
- FeaturedBento: brighten arrow buttons (text-[#ccc] over bg-[#1a1a1a]
with #3a3a3a border; previously near-invisible #666 over the same
background), enlarge to w-8 h-8, hover fills to blue. Wrap the
arrow group with role/aria + Left/Right keyboard nav.
P0-7 (728x90 reposition):
- Header: move the leaderboard block from above the main nav to AFTER
the sticky nav, so it sits between the nav and The Beat ticker.
P0-8 (remove Latest search box):
- ArticleFeed: drop the in-section search input + clear button. The
top-right header search is now the sole search UI. searchQuery state
remains since the active-filter chip code still references it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Banner spec from Ryan:
- 728x90 header (every page): pool is now LiveU only; rotates when more
728x90 entries are added to ads.ts.
- 728x90 below main menu on article pages: removed (was the article-top
AdSlot stub).
- 728x90 footer (every page): replaces the old 300x250 rocket.new
placeholder in Footer.tsx. Rotates from the same ADS_728X90 pool.
- 300x250 sidebar: now renders EVERY entry in ADS_300X250 stacked
vertically, shuffled per page-view. Pool: AJA, Zixi, Telycam, LiveU,
Lectrosonics, Opengear, Studio Hero, Magewell. Adding more entries to
ads.ts auto-grows the stack.
- 300x600 fixed slot: Blackmagic (Adsanity ad-210571), top of sidebar,
never rotated.
Implementation:
- ads.ts: Ad interface now allows either src (local image) or adsanity_id
(iframe). New rotateAll() returns ALL ads of a size shuffled; pickAds
kept for ArticleBody bounded slots.
- AdImage.tsx: dropped the legacy ADSANITY_CAMPAIGN_MAP label fallback —
ads.ts is now the single source of truth, so Telycam (no adsanity_id)
renders its local GIF instead of the iframe.
- SidebarAdStack.tsx: rotateAll(300x250) instead of pickAds with a
count cap. count prop removed; callers updated.
- NewsArticleDetailClient.tsx: removed article-top 728x90 stub.
- Footer.tsx: 728x90 AdImage in place of the 300x250 placeholder.
- LeaderboardAd.tsx: deleted (was unused after the earlier home-page
cleanup).
- PENDING_ads.md: rewritten — Adsanity IDs to verify (Studio Hero,
Blackmagic) and optional local-file upgrades.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the placeholder Unsplash banner array with the live AdSanity
inventory served from /legacy/ads/ (same 27 active creatives currently
on broadcastbeat.com).
New:
- src/lib/ads.ts: ad inventory (1 fixed Blackmagic 300x600, 22 rotating
300x250s, 4 leaderboard 728x90s) + Fisher-Yates shuffle + pickAds()
helper that draws N unique ads excluding any caller-supplied src set.
- src/components/AdImage.tsx: renders one ad as <a target=_blank rel=
sponsored noopener noreferrer><AppImage>.
- src/components/SidebarAdStack.tsx: Blackmagic 300x600 fixed on top,
then count rotating 300x250s. Memoizes the rotation per render so a
refresh re-shuffles. Accepts excludeSrcs to coordinate with other
slots on the same page.
- src/components/ArticleBody.tsx: splits article HTML at </p>
boundaries, splices a 300x250 ad after every Nth paragraph (default
4). Same exclude-set pattern.
Wiring:
- ArticleFeed.tsx: remove the inline sidebarAds array (was 5 Unsplash
placeholders + 1 LiveU 300x250). Sidebar now <SidebarAdStack count=5/>.
- NewsArticleDetailClient.tsx: sidebar 300x600 + 300x250 AdSlot
placeholders replaced with <SidebarAdStack count=4/>. Article body
innerHTML replaced with <ArticleBody html=... everyN=4/> so
300x250 ads slot in between paragraphs.
Same banners as the live broadcastbeat.com (sourced from the existing
WP AdSanity install we cached locally during Phase 2). Each <a> opens
in a new tab and uses rel='sponsored noopener noreferrer'.
Drops the rendered date span from:
- ArticleFeed.tsx (homepage feed)
- news/page.tsx (news list — also drops the orphan separator)
- news/[slug]/NewsArticleDetailClient.tsx (article header + related-articles row, drops paired separators)
- AISuggestedArticles.tsx (sidebar — keeps readTime alone)
JSON-LD datePublished/publishedTime in news/[slug]/page.tsx and
articles/[slug]/page.tsx is unchanged so search engines still see the
publication date.