wip-banner-system: full banner pool, stack all 300x250s, footer 728x90
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>
This commit is contained in:
123
src/lib/ads.ts
123
src/lib/ads.ts
@@ -1,32 +1,92 @@
|
||||
// Ad creatives with verified image files in public/legacy/ads/.
|
||||
// Only ads with a real downloaded image are included — entries without
|
||||
// a recovered file have been moved to PENDING.md for Ryan to upload.
|
||||
// Missing: Matrox 728x90, Blackmagic 300x600, and 20 of 22 300x250s.
|
||||
// Banner control panel: this file is the source of truth for which ads
|
||||
// render on broadcastbeat.com. Adding a new entry here automatically
|
||||
// rotates it into the appropriate zone — no other code changes needed.
|
||||
//
|
||||
// Each Ad must provide EITHER:
|
||||
// - src + click_url + alt → local image, click goes to click_url
|
||||
// - adsanity_id → Adsanity iframe (Adsanity tracks the click)
|
||||
|
||||
export interface Ad {
|
||||
src: string;
|
||||
alt: string;
|
||||
label: string;
|
||||
size: "300x250" | "300x600" | "728x90";
|
||||
click_url: string;
|
||||
src?: string;
|
||||
alt?: string;
|
||||
click_url?: string;
|
||||
adsanity_id?: string;
|
||||
}
|
||||
|
||||
/** Rotating leaderboard pool — 3 verified real images. */
|
||||
/** 728x90 leaderboard pool — header (top of every page) and footer. */
|
||||
export const ADS_728X90: Ad[] = [
|
||||
{ src: "/legacy/ads/Broadcast-Beat-Ad-March-2023_2f1904b5.png", alt: "Tower Products", label: "Tower", size: "728x90", click_url: "https://bit.ly/3rd4oBS" },
|
||||
{ src: "/legacy/ads/11503_URXP41D_Audio_728X90_OnlineBanner_V1_StaticBackup_40K_f92207ce.jpg", alt: "Sony Pro UWP-D27", label: "Sony", size: "728x90", click_url: "https://pro.sony/ue_US/products/broadcastaudiouwpdseriesmicpackages/uwp-d27" },
|
||||
{ src: "/legacy/ads/728-x-90-pxEN_3ce6f379.gif", alt: "LiveU 728x90", label: "LiveU", size: "728x90", click_url: "https://bit.ly/4ghXVeq" },
|
||||
{
|
||||
label: "LiveU",
|
||||
size: "728x90",
|
||||
src: "/legacy/ads/728-x-90-pxEN_3ce6f379.gif",
|
||||
alt: "LiveU 728x90",
|
||||
click_url: "https://bit.ly/4ghXVeq",
|
||||
},
|
||||
];
|
||||
|
||||
/** Rotating 300×250 sidebar pool — 2 verified real images. */
|
||||
/**
|
||||
* 300x250 sidebar pool. ALL entries render on every page — SidebarAdStack
|
||||
* stacks them vertically and shuffles the order per page-view so each banner
|
||||
* cycles through positions. Add new entries here to grow the stack.
|
||||
*/
|
||||
export const ADS_300X250: Ad[] = [
|
||||
{ src: "/legacy/ads/PAYG-300x250-1_31957672.gif", alt: "LiveU pay-as-you-go", label: "LiveU", size: "300x250", click_url: "https://bit.ly/4ghXVeq" },
|
||||
{ src: "/legacy/ads/Telycam_BroadcastBeat_300x250_MixOne-ExploreXE_with_NAB2026_2f30157f.gif", alt: "Telycam Mix One ExploreXE NAB 2026", label: "Telycam", size: "300x250", click_url: "https://telycam.com/" },
|
||||
{
|
||||
label: "AJA Video",
|
||||
size: "300x250",
|
||||
adsanity_id: "ad-164285",
|
||||
},
|
||||
{
|
||||
label: "Zixi",
|
||||
size: "300x250",
|
||||
adsanity_id: "ad-156737",
|
||||
},
|
||||
{
|
||||
label: "Telycam",
|
||||
size: "300x250",
|
||||
src: "/legacy/ads/Telycam_BroadcastBeat_300x250_MixOne-ExploreXE_with_NAB2026_2f30157f.gif",
|
||||
alt: "Telycam Mix One ExploreXE NAB 2026",
|
||||
click_url: "https://telycam.com/",
|
||||
},
|
||||
{
|
||||
label: "LiveU",
|
||||
size: "300x250",
|
||||
src: "/legacy/ads/PAYG-300x250-1_31957672.gif",
|
||||
alt: "LiveU pay-as-you-go",
|
||||
click_url: "https://bit.ly/4ghXVeq",
|
||||
},
|
||||
{
|
||||
label: "Lectrosonics",
|
||||
size: "300x250",
|
||||
adsanity_id: "ad-156727",
|
||||
},
|
||||
{
|
||||
label: "Opengear",
|
||||
size: "300x250",
|
||||
adsanity_id: "ad-200174",
|
||||
},
|
||||
{
|
||||
label: "Studio Hero",
|
||||
size: "300x250",
|
||||
adsanity_id: "ad-162714",
|
||||
},
|
||||
{
|
||||
label: "Magewell",
|
||||
size: "300x250",
|
||||
adsanity_id: "ad-154296",
|
||||
},
|
||||
];
|
||||
|
||||
// 300x600 fixed sidebar: Blackmagic image not recovered from archive.
|
||||
// SidebarAdStack skips this slot when null.
|
||||
export const FIXED_300X600: Ad | null = null;
|
||||
/**
|
||||
* 300x600 fixed sidebar slot. Rendered at the TOP of SidebarAdStack and is
|
||||
* NEVER rotated. Set to null to omit. To replace, swap this single entry.
|
||||
*/
|
||||
export const FIXED_300X600: Ad | null = {
|
||||
label: "Blackmagic",
|
||||
size: "300x600",
|
||||
adsanity_id: "ad-210571",
|
||||
};
|
||||
|
||||
/** Fisher-Yates shuffle, returns a new array. */
|
||||
export function shuffle<T>(arr: T[]): T[] {
|
||||
@@ -39,19 +99,30 @@ export function shuffle<T>(arr: T[]): T[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw N unique ads of the requested size for a single page render.
|
||||
* Pass `exclude` (set of `src` strings already used elsewhere on the same page)
|
||||
* to guarantee no duplicates across slots.
|
||||
* Return ALL ads of the requested size in a shuffled order. Use this when
|
||||
* every banner must render (e.g. the sidebar 300x250 stack); the shuffle
|
||||
* ensures each banner cycles through positions across page-views.
|
||||
*/
|
||||
export function rotateAll(size: Ad["size"], exclude: Set<string> = new Set()): Ad[] {
|
||||
const pool =
|
||||
size === "300x250" ? ADS_300X250
|
||||
: size === "728x90" ? ADS_728X90
|
||||
: size === "300x600" ? (FIXED_300X600 ? [FIXED_300X600] : [])
|
||||
: [];
|
||||
const key = (a: Ad) => a.src ?? a.adsanity_id ?? a.label;
|
||||
return shuffle(pool.filter((a) => !exclude.has(key(a))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw N ads of the requested size, shuffled. Used by ArticleBody for
|
||||
* a bounded number of in-article slots. For zones that must show every
|
||||
* banner, prefer rotateAll().
|
||||
*/
|
||||
export function pickAds(
|
||||
size: Ad["size"],
|
||||
n: number,
|
||||
exclude: Set<string> = new Set(),
|
||||
): Ad[] {
|
||||
const pool =
|
||||
size === "300x250" ? ADS_300X250
|
||||
: size === "728x90" ? ADS_728X90
|
||||
: [];
|
||||
const filtered = pool.filter((a) => !exclude.has(a.src));
|
||||
return shuffle(filtered).slice(0, Math.min(n, filtered.length));
|
||||
const all = rotateAll(size, exclude);
|
||||
return all.slice(0, Math.min(n, all.length));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user