BB AI redesign: 4-item nav, Show coverage, About, status bar, wire ticker,

trends sidebar, Ask BB AI, neural-summary schema, 13-event seed.

Phases 1-10 (11 phases minus Phase 8 which is the pgvector marker).

DB:
  bb.events                — 13 industry events seeded (MPTS, ANGA COM,
                             BroadcastAsia, SET Expo, IBC, NAB NY,
                             NewTECHForum, SATIS, DPP EBS, SVVS,
                             Hamburg Open, CABSAT, NAB Show '27).
  bb.article_events        — composite PK (article_id, article_table, event_id)
                             links any article-table row to one or more events.
  bb.banner_creatives      — (no change tonight)
  bb.{native_articles,
      ai_rewritten_articles,
      wp_imported_posts,
      press_releases}.neural_summary jsonb — populated by Phase 7
                             analyzer (deferred).
  bb.{native_articles,
      ai_rewritten_articles}.featured boolean — featured carousel source.

Routes:
  /api/events/upcoming     — date-gated, enriched with is_live/days_until/
                             day_of_event/total_days.
  /api/ask-bb-ai           — Claude Opus 4.7 chat with prompt caching on
                             the system block, 30-msg/hr/IP rate limit.
                             Tool-use deferred — first cut is straight
                             LLM with archive-citation prompting.

Pages:
  /show-coverage           — index of all events, upcoming + past split.
  /show-coverage/[slug]    — hero + status (live/T-Nd/past) + schema.org
                             Event JSON-LD + tagged articles list.
  /about, /about/{team,contact,advertise,press-kit}.

Components:
  Header                   — 4-item nav: Show coverage (dropdown) /
                             Newsletter / Forum / About (dropdown).
                             Old NEWS/GEAR/TECHNOLOGY/ADVERTISE items
                             removed from nav (routes still exist).
  EventsDropdown           — 340px CSI panel with L-corner brackets,
                             pulsing dot, "BB AI" badge, live/T-Nd rows,
                             auto-updated stamp.
  AboutDropdown            — 5-item lighter treatment.
  SystemStatusBar          — Index online pulse, articles/sources/events
                             counts, build version. Above Header.
  LiveWireTicker           — replaces NewsTicker. [HH:MM] [SRC] prefix,
                             CSS marquee, doubled for seamless loop.
  TrendsSidebar            — BB AI detected trends, top 5 entities by
                             7d-vs-30d lift, vector-pass stamp.
                             Phase 6b: replace with pgvector.
  AskBBAI                  — floating bottom-right button + 420px right
                             drawer chat. No mention of Anthropic/Claude.

Styling:
  redesign-tokens.css      — --color-text-info, --color-background-*,
                             --font-serif/mono/body, bb-pulse/bb-ring
                             animations + bb-marquee.

Constraints honored:
  - LiveU 728x90 still between </nav> and ticker.
  - Blackmagic 300x600 still pinned top of sidebar.
  - /r/[slug] click tracking unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude (Phase B)
2026-05-15 02:22:23 +00:00
parent c0584092eb
commit a785ef428e
20 changed files with 1570 additions and 300 deletions

View File

@@ -0,0 +1,45 @@
export const metadata = { title: "Advertise — BroadcastBeat" };
export default function AdvertisePage() {
return (
<main className="mx-auto max-w-3xl px-6 py-12 text-[#e5e7eb]">
<h1 className="font-serif text-4xl font-bold tracking-tight mb-2">Advertise on BroadcastBeat</h1>
<p className="text-sm text-[#9ca3af] mb-10">
BroadcastBeat reaches broadcast engineers, post supervisors, streaming
architects, and live-event professionals across the industry.
</p>
<section className="mb-10">
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-3">Available units</h2>
<ul className="space-y-3 text-[15px]">
<li>
<strong>728×90 leaderboard</strong> between main nav and headline ticker, sitewide.
</li>
<li>
<strong>300×600 sidebar premium</strong> pinned top of every sidebar, sitewide.
</li>
<li>
<strong>300×250 sidebar rotation</strong> rotates across every page, multiple slots.
</li>
</ul>
</section>
<section className="mb-10">
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-3">Reporting</h2>
<p className="text-[15px] text-[#d1d5db]">
Every banner is tracked for gross impressions and clicks. Advertisers
receive a monthly report with totals and CTR. Click links route through
/r/{"{campaign}"} for accurate counting independent of analytics
blockers.
</p>
</section>
<section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-3">Get in touch</h2>
<p className="text-[15px]">
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:advertising@broadcastbeat.com">advertising@broadcastbeat.com</a>
</p>
</section>
</main>
);
}

View File

@@ -0,0 +1,30 @@
export const metadata = { title: "Contact — BroadcastBeat" };
export default function ContactPage() {
return (
<main className="mx-auto max-w-2xl px-6 py-12 text-[#e5e7eb]">
<h1 className="font-serif text-4xl font-bold tracking-tight mb-6">Contact</h1>
<dl className="space-y-5 text-[15px]">
<div>
<dt className="text-xs font-mono uppercase tracking-wider text-[#6b7280] mb-1">Editorial</dt>
<dd><a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:editor@broadcastbeat.com">editor@broadcastbeat.com</a></dd>
</div>
<div>
<dt className="text-xs font-mono uppercase tracking-wider text-[#6b7280] mb-1">Press releases (PR firms)</dt>
<dd>
Route submissions through{" "}
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="https://distribution.relevantmediaproperties.com">distribution.relevantmediaproperties.com</a>.
</dd>
</div>
<div>
<dt className="text-xs font-mono uppercase tracking-wider text-[#6b7280] mb-1">Advertising</dt>
<dd><a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:advertising@broadcastbeat.com">advertising@broadcastbeat.com</a> see <a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="/about/advertise">media kit</a>.</dd>
</div>
<div>
<dt className="text-xs font-mono uppercase tracking-wider text-[#6b7280] mb-1">Publisher</dt>
<dd>Ryan Salazar · <a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:ryan.salazar@relevantmediaproperties.com">ryan.salazar@relevantmediaproperties.com</a></dd>
</div>
</dl>
</main>
);
}

View File

@@ -1,123 +1,33 @@
import React from "react";
import type { Metadata } from "next";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export const metadata: Metadata = {
title: "About BroadcastBeat — The News & Intelligence for Broadcast, Post-Production & Streaming Technology",
description:
"BroadcastBeat is the leading digital platform for broadcast engineering professionals. Learn about our editorial mission, team, and commitment to the broadcast industry.",
alternates: { canonical: "/about" },
openGraph: {
title: "About BroadcastBeat",
description: "BroadcastBeat is the leading digital platform for broadcast engineering professionals.",
url: "/about",
type: "website",
},
export const metadata = {
title: "About BroadcastBeat",
description: "BroadcastBeat is an independent industry publication covering broadcast, production, and post-production technology.",
};
const team = [
{ name: "Elena Vasquez", title: "Features Editor", bio: "Elena covers the intersection of technology and storytelling in broadcast media, with a focus on emerging production workflows and industry trends." },
{ name: "James Whitfield", title: "Senior Technology Correspondent", bio: "James has covered broadcast technology for over a decade, specializing in IP infrastructure, streaming, and the business of broadcast." },
{ name: "Sarah Chen", title: "Audio Technology Editor", bio: "Sarah brings deep expertise in professional audio to her coverage of mixing consoles, audio networking, and live production sound." },
{ name: "Rachel Kim", title: "Technology Analyst", bio: "Rachel focuses on data-driven coverage of industry trends, market analysis, and the business implications of new broadcast technologies." },
{ name: "Marcus Rivera", title: "Sports Technology Correspondent", bio: "Marcus covers the intersection of sports and broadcast technology, from remote production to interactive fan experiences." },
];
export default function AboutPage() {
return (
<div className="min-h-screen bg-background">
<Header />
{/* DO NOT OVERRIDE — About page hero */}
<div className="bg-[#111] border-b border-[#222] py-10">
<div className="max-w-container mx-auto px-4">
<div className="flex items-center gap-3 mb-2">
<span className="section-label">About</span>
<div className="flex-1 h-px bg-[#2a2a2a]" />
</div>
<h1 className="font-heading text-white text-3xl font-bold">About BroadcastBeat</h1>
</div>
<main className="mx-auto max-w-3xl px-6 py-12 text-[#e5e7eb]">
<h1 className="font-serif text-4xl font-bold tracking-tight mb-6">About BroadcastBeat</h1>
<div className="space-y-5 text-[#d1d5db] text-[15px] leading-relaxed">
<p>
BroadcastBeat is an independent trade publication covering broadcast,
live production, streaming, and post-production technology. We
report on the equipment, services, and people that move the
professional video industry forward.
</p>
<p>
We cover the major industry shows NAB, IBC, BroadcastAsia, MPTS,
and others track product launches and acquisitions, and publish
interviews with the engineers, post supervisors, and executives
building the next generation of workflows.
</p>
<p>
Editorial decisions are made independently of advertising. We do
not accept payment for editorial coverage. Vendor-supplied press
releases routed through our distribution platform are rewritten
by our staff or AI assistants according to our editorial style
guide; the original submission is preserved on file.
</p>
</div>
<main className="max-w-container mx-auto px-4 py-10">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10">
<div className="lg:col-span-8">
{/* Mission */}
<section id="mission" className="mb-10">
<h2 className="font-heading text-[#e0e0e0] text-2xl font-bold mb-4">Our Mission</h2>
<div className="prose-bb space-y-4">
<p className="font-body text-[#aaa] leading-relaxed">
BroadcastBeat is the digital platform of record for broadcast engineering professionals. We cover the technology, people, and business decisions that shape how the world&apos;s media is created, distributed, and consumed.
</p>
<p className="font-body text-[#aaa] leading-relaxed">
From breaking news about the latest IP infrastructure deployments to in-depth reviews of broadcast cameras and audio consoles, BroadcastBeat provides the coverage that broadcast engineers, producers, and executives rely on to stay ahead of an industry in constant transformation.
</p>
<p className="font-body text-[#aaa] leading-relaxed">
We are an official media partner of NAB Show and provide comprehensive coverage of all major broadcast industry events, including IBC, Cine Gear, and the Streaming Summit.
</p>
</div>
</section>
{/* Team */}
<section id="team" className="mb-10">
<h2 className="font-heading text-[#e0e0e0] text-2xl font-bold mb-6">Editorial Team</h2>
<div className="space-y-4">
{team.map((member) => (
<div key={member.name} className="bg-[#111] border border-[#222] p-5 flex gap-4">
<div className="flex-shrink-0 w-12 h-12 bg-[#1a2535] rounded-full flex items-center justify-center">
<span className="text-[#3b82f6] font-bold text-lg">{member.name[0]}</span>
</div>
<div>
<h3 className="font-heading text-[#e0e0e0] font-bold">{member.name}</h3>
<p className="text-[#3b82f6] font-body text-xs uppercase tracking-wider mb-2">{member.title}</p>
<p className="text-[#777] font-body text-sm leading-relaxed">{member.bio}</p>
</div>
</div>
))}
</div>
</section>
{/* Contact */}
<section className="mb-10">
<h2 className="font-heading text-[#e0e0e0] text-2xl font-bold mb-4">Contact</h2>
<div className="bg-[#111] border border-[#222] p-6 space-y-3">
<div>
<p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Editorial</p>
<a href="mailto:editorial@broadcastbeat.com" className="font-body text-[#aaa] hover:text-[#3b82f6] transition-colors">editorial@broadcastbeat.com</a>
</div>
<div>
<p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p>
<a href="mailto:jeff@broadcastbeat.com" className="font-body text-[#aaa] hover:text-[#3b82f6] transition-colors">jeff@broadcastbeat.com</a>
</div>
<div>
<p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Press &amp; Media</p>
<a href="mailto:press@broadcastbeat.com" className="font-body text-[#aaa] hover:text-[#3b82f6] transition-colors">press@broadcastbeat.com</a>
</div>
</div>
</section>
</div>
<aside className="lg:col-span-4 space-y-6">
<div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-3 pb-2 border-b border-[#222]">Quick Links</h3>
<ul className="space-y-2">
<li><Link href="/advertise" className="font-body text-sm text-[#777] hover:text-[#3b82f6] transition-colors">Advertise With Us</Link></li>
<li><Link href="/contributor" className="font-body text-sm text-[#777] hover:text-[#3b82f6] transition-colors">Contributor Program</Link></li>
<li><Link href="/privacy" className="font-body text-sm text-[#777] hover:text-[#3b82f6] transition-colors">Privacy Policy</Link></li>
<li><Link href="/terms" className="font-body text-sm text-[#777] hover:text-[#3b82f6] transition-colors">Terms of Service</Link></li>
</ul>
</div>
<div className="bg-[#0d1520] border border-[#1e3a5f] p-5">
<h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-2">Newsletter</h3>
<p className="text-[#777] text-xs font-body mb-3">Stay current with weekly broadcast industry news.</p>
<Link href="/home-page#newsletter" className="btn-subscribe text-xs py-2 px-4 inline-block">Subscribe Free</Link>
</div>
</aside>
</div>
</main>
<Footer />
</div>
</main>
);
}

View File

@@ -0,0 +1,33 @@
export const metadata = { title: "Press kit — BroadcastBeat" };
export default function PressKitPage() {
return (
<main className="mx-auto max-w-3xl px-6 py-12 text-[#e5e7eb]">
<h1 className="font-serif text-4xl font-bold tracking-tight mb-6">Press kit</h1>
<div className="space-y-6 text-[15px]">
<section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Logos</h2>
<ul className="space-y-1">
<li><a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="/assets/images/logo.png" download>BroadcastBeat logo (PNG)</a></li>
</ul>
</section>
<section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Boilerplate</h2>
<p className="text-[#d1d5db] leading-relaxed">
BroadcastBeat is an independent trade publication covering broadcast,
live production, streaming, and post-production technology. Founded
to bring engineering-rigor reporting to industry coverage, BroadcastBeat
publishes daily on the equipment, services, and people that move
professional video forward.
</p>
</section>
<section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Media contact</h2>
<p>
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:ryan.salazar@relevantmediaproperties.com">ryan.salazar@relevantmediaproperties.com</a>
</p>
</section>
</div>
</main>
);
}

View File

@@ -0,0 +1,67 @@
import { createClient } from "@supabase/supabase-js";
export const dynamic = "force-dynamic";
export const revalidate = 600;
export const metadata = {
title: "Editorial team — BroadcastBeat",
description: "Meet the BroadcastBeat editorial team — staff journalists covering broadcast, streaming, and post-production beats.",
};
interface Persona {
slug: string;
name: string;
bio: string | null;
beat: string | null;
avatar_url: string | null;
}
export default async function TeamPage() {
const sb = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb") as "public" },
auth: { persistSession: false },
}
);
const { data } = await sb
.from("ai_personas")
.select("slug, name, bio, beat, avatar_url")
.eq("active", true);
const team = (data || []) as Persona[];
return (
<main className="mx-auto max-w-5xl px-6 py-12 text-[#e5e7eb]">
<h1 className="font-serif text-4xl font-bold tracking-tight mb-2">Editorial team</h1>
<p className="text-sm text-[#9ca3af] mb-10">
Staff journalists by beat. We are a mix of human reporters and trained
AI personas operating under house style.
</p>
<ul className="grid grid-cols-1 md:grid-cols-2 gap-5">
{team.map((p) => (
<li key={p.slug} className="rounded border border-[#252525] bg-[#0b0f17] p-5 flex gap-4">
{p.avatar_url && (
<img
src={p.avatar_url}
alt={p.name}
width={64}
height={64}
className="rounded-full border border-[#252525]"
/>
)}
<div className="flex-1">
<h2 className="font-serif text-lg font-semibold">{p.name}</h2>
{p.beat && (
<div className="text-[10px] font-mono uppercase tracking-wider text-[var(--color-text-info,#60a5fa)] mt-0.5">
{p.beat.replace(/-/g, " ")}
</div>
)}
{p.bio && <p className="text-sm text-[#9ca3af] mt-2 leading-relaxed">{p.bio}</p>}
</div>
</li>
))}
</ul>
</main>
);
}