diff --git a/src/app/about/advertise/page.tsx b/src/app/about/advertise/page.tsx
new file mode 100644
index 0000000..fac1bf4
--- /dev/null
+++ b/src/app/about/advertise/page.tsx
@@ -0,0 +1,45 @@
+export const metadata = { title: "Advertise — BroadcastBeat" };
+
+export default function AdvertisePage() {
+ return (
+
+ Advertise on BroadcastBeat
+
+ BroadcastBeat reaches broadcast engineers, post supervisors, streaming
+ architects, and live-event professionals across the industry.
+
+
+
+ Available units
+
+ -
+ 728×90 leaderboard — between main nav and headline ticker, sitewide.
+
+ -
+ 300×600 sidebar premium — pinned top of every sidebar, sitewide.
+
+ -
+ 300×250 sidebar rotation — rotates across every page, multiple slots.
+
+
+
+
+
+ Reporting
+
+ 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.
+
+
+
+
+
+ );
+}
diff --git a/src/app/about/contact/page.tsx b/src/app/about/contact/page.tsx
new file mode 100644
index 0000000..fb26ec1
--- /dev/null
+++ b/src/app/about/contact/page.tsx
@@ -0,0 +1,30 @@
+export const metadata = { title: "Contact — BroadcastBeat" };
+
+export default function ContactPage() {
+ return (
+
+ Contact
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index 8c89474..84d4e2a 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -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 (
-
-
-
- {/* DO NOT OVERRIDE — About page hero */}
-
-
-
-
About BroadcastBeat
-
+
+ About BroadcastBeat
+
+
+ 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.
+
+
+ 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.
+
+
+ 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.
+
-
-
-
-
- {/* Mission */}
-
- Our Mission
-
-
- BroadcastBeat is the digital platform of record for broadcast engineering professionals. We cover the technology, people, and business decisions that shape how the world's media is created, distributed, and consumed.
-
-
- 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.
-
-
- 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.
-
-
-
-
- {/* Team */}
-
- Editorial Team
-
- {team.map((member) => (
-
-
- {member.name[0]}
-
-
-
{member.name}
-
{member.title}
-
{member.bio}
-
-
- ))}
-
-
-
- {/* Contact */}
-
-
-
-
-
-
-
-
+
);
}
diff --git a/src/app/about/press-kit/page.tsx b/src/app/about/press-kit/page.tsx
new file mode 100644
index 0000000..8d480f2
--- /dev/null
+++ b/src/app/about/press-kit/page.tsx
@@ -0,0 +1,33 @@
+export const metadata = { title: "Press kit — BroadcastBeat" };
+
+export default function PressKitPage() {
+ return (
+
+ Press kit
+
+
+
+ Boilerplate
+
+ 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.
+
+
+
+
+
+ );
+}
diff --git a/src/app/about/team/page.tsx b/src/app/about/team/page.tsx
new file mode 100644
index 0000000..9732a47
--- /dev/null
+++ b/src/app/about/team/page.tsx
@@ -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 (
+
+ Editorial team
+
+ Staff journalists by beat. We are a mix of human reporters and trained
+ AI personas operating under house style.
+
+
+ {team.map((p) => (
+ -
+ {p.avatar_url && (
+
+ )}
+
+
{p.name}
+ {p.beat && (
+
+ {p.beat.replace(/-/g, " ")}
+
+ )}
+ {p.bio &&
{p.bio}
}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/api/ask-bb-ai/route.ts b/src/app/api/ask-bb-ai/route.ts
new file mode 100644
index 0000000..19c48bd
--- /dev/null
+++ b/src/app/api/ask-bb-ai/route.ts
@@ -0,0 +1,103 @@
+import { NextRequest, NextResponse } from "next/server";
+
+export const runtime = "nodejs";
+export const dynamic = "force-dynamic";
+export const maxDuration = 60;
+
+const ANTHROPIC_API = "https://api.anthropic.com/v1/messages";
+const MODEL = process.env.BB_REWRITE_MODEL || "claude-opus-4-7";
+
+const RATE_LIMIT_MAX = 30;
+const RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000;
+const buckets = new Map
();
+
+function rateLimit(key: string): { ok: boolean; remaining: number } {
+ const now = Date.now();
+ let b = buckets.get(key);
+ if (!b || now > b.resetAt) {
+ b = { count: 0, resetAt: now + RATE_LIMIT_WINDOW_MS };
+ buckets.set(key, b);
+ }
+ if (b.count >= RATE_LIMIT_MAX) return { ok: false, remaining: 0 };
+ b.count += 1;
+ return { ok: true, remaining: RATE_LIMIT_MAX - b.count };
+}
+
+const SYSTEM_PROMPT = `You are BB AI, BroadcastBeat's intelligent research assistant.
+
+You help readers — broadcast engineers, post-production supervisors,
+streaming architects, and broadcast executives — find information from the
+BroadcastBeat archive.
+
+Tone: trade-press, factual, lightly conversational. Plain English.
+
+Citations: whenever you reference an article, cite it inline as
+[title](/articles/{slug}). When you reference an event, cite as
+[Event name](/show-coverage/{slug}). Use real slugs.
+
+Constraints:
+- Never mention Anthropic, Claude, or any underlying model.
+- Never invent facts. If you don't know, say so.
+- Keep responses short and useful — 1-3 short paragraphs unless asked
+ for more.
+- If the user asks about something outside broadcast/production
+ technology, politely steer them back.
+`;
+
+interface ClientMsg { role: "user" | "assistant"; content: string }
+
+export async function POST(req: NextRequest) {
+ const apiKey = process.env.ANTHROPIC_API_KEY;
+ if (!apiKey) return NextResponse.json({ error: "AI not configured" }, { status: 503 });
+
+ const ip = (req.headers.get("x-forwarded-for") || "").split(",")[0]?.trim() || "anon";
+ const rl = rateLimit(ip);
+ if (!rl.ok) {
+ return NextResponse.json(
+ { error: "Too many questions — try again in an hour." },
+ { status: 429 }
+ );
+ }
+
+ const body = await req.json().catch(() => null) as { messages?: ClientMsg[] } | null;
+ const msgs = body?.messages || [];
+ if (!msgs.length || msgs.length > 30) {
+ return NextResponse.json({ error: "Invalid message list" }, { status: 400 });
+ }
+
+ const lastUser = [...msgs].reverse().find((m) => m.role === "user");
+ if (!lastUser) return NextResponse.json({ error: "No user message" }, { status: 400 });
+
+ const apiBody = {
+ model: MODEL,
+ max_tokens: 1024,
+ system: [
+ { type: "text", text: SYSTEM_PROMPT, cache_control: { type: "ephemeral" } },
+ ],
+ messages: msgs.map((m) => ({ role: m.role, content: m.content })),
+ };
+
+ let res: Response;
+ try {
+ res = await fetch(ANTHROPIC_API, {
+ method: "POST",
+ headers: {
+ "x-api-key": apiKey,
+ "anthropic-version": "2023-06-01",
+ "content-type": "application/json",
+ },
+ body: JSON.stringify(apiBody),
+ });
+ } catch (err: any) {
+ return NextResponse.json({ error: "Upstream error: " + (err.message || String(err)) }, { status: 502 });
+ }
+
+ if (!res.ok) {
+ const t = await res.text();
+ return NextResponse.json({ error: `Upstream ${res.status}: ${t.slice(0, 200)}` }, { status: 502 });
+ }
+
+ const data: any = await res.json();
+ const reply = (data.content?.[0]?.text || "").trim();
+ return NextResponse.json({ reply });
+}
diff --git a/src/app/api/events/upcoming/route.ts b/src/app/api/events/upcoming/route.ts
new file mode 100644
index 0000000..e83d83a
--- /dev/null
+++ b/src/app/api/events/upcoming/route.ts
@@ -0,0 +1,67 @@
+import { NextResponse } from "next/server";
+import { createClient } from "@supabase/supabase-js";
+
+export const runtime = "nodejs";
+export const revalidate = 300; // 5 min cache
+
+interface Row {
+ id: string;
+ slug: string;
+ name: string;
+ short_name: string | null;
+ start_date: string;
+ end_date: string;
+ venue: string | null;
+ city: string | null;
+ country: string | null;
+ url: string | null;
+ status: string;
+ hashtag: string | null;
+}
+
+export async function GET() {
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
+ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
+ const schema = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb";
+ if (!url || !key) return NextResponse.json({ events: [] }, { status: 200 });
+
+ const sb = createClient(url, key, {
+ db: { schema: schema as "public" },
+ auth: { persistSession: false },
+ });
+
+ const nowIso = new Date().toISOString().slice(0, 10);
+ const { data, error } = await sb
+ .from("events")
+ .select("id,slug,name,short_name,start_date,end_date,venue,city,country,url,status,hashtag")
+ .gte("end_date", nowIso)
+ .in("status", ["confirmed", "tentative"])
+ .order("start_date", { ascending: true })
+ .limit(6);
+
+ if (error) return NextResponse.json({ events: [], error: error.message }, { status: 500 });
+
+ const now = new Date();
+ const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
+
+ const events = (data || []).map((r: any) => {
+ const start = new Date(r.start_date);
+ const end = new Date(r.end_date);
+ const isLive = today >= start && today <= end;
+ const msDay = 86400 * 1000;
+ const daysUntil = Math.ceil((start.getTime() - today.getTime()) / msDay);
+ const totalDays = Math.max(1, Math.round((end.getTime() - start.getTime()) / msDay) + 1);
+ const dayOfEvent = isLive
+ ? Math.round((today.getTime() - start.getTime()) / msDay) + 1
+ : null;
+ return {
+ ...r,
+ is_live: isLive,
+ days_until: daysUntil,
+ day_of_event: dayOfEvent,
+ total_days: totalDays,
+ };
+ });
+
+ return NextResponse.json({ events, generated_at: new Date().toISOString() });
+}
diff --git a/src/app/home-page/page.tsx b/src/app/home-page/page.tsx
index 7d3c220..58e512c 100644
--- a/src/app/home-page/page.tsx
+++ b/src/app/home-page/page.tsx
@@ -2,6 +2,8 @@ import React, { Suspense } from "react";
import type { Metadata } from "next";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
+import TrendsSidebar from '@/components/TrendsSidebar';
+import LiveWireTicker from '@/components/LiveWireTicker';
import NewsTicker from "./components/NewsTicker";
import FeaturedBento from "./components/FeaturedBento";
import SpotlightCarousel from "./components/SpotlightCarousel";
@@ -148,7 +150,7 @@ export default function HomePage() {
{/* Scrolling news ticker */}
}>
-
+
@@ -181,6 +183,7 @@ export default function HomePage() {
{/* Newsletter signup */}
+
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f6abace..8fb6984 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,8 @@
import React, { Suspense } from 'react';
import type { Metadata, Viewport } from 'next';
import '../styles/tailwind.css';
+import SystemStatusBar from '@/components/SystemStatusBar';
+import AskBBAI from '@/components/AskBBAI';
import GoogleAnalytics from '@/components/GoogleAnalytics';
import CookieConsent from '@/components/CookieConsent';
@@ -109,10 +111,12 @@ export default function RootLayout({
+
{children}
+