slice 1+2: header social URLs, drop TwitterX/Press Kit, wrap About/Team/PressKit, add /newsletter + /rss

- Header.tsx: bare linkedin/instagram/facebook URLs → /broadcastbeat handles; remove TwitterXIcon from social bar + import (Footer retains TwitterXIcon per "DO NOT OVERRIDE" marker)
- AboutDropdown.tsx: drop "Press kit" item (the page itself stays at /about/press-kit, just delisted from menu)
- SystemStatusBar.tsx: remove "Index online" pulse-dot and "N wire sources" span (keep articles count + events count + build version)
- /about, /about/team, /about/press-kit: wrap in Header+Footer (previously rendered bare <main>)
- /newsletter/page.tsx (new): redirect to /newsletter/archive — Header was 404'ing on /newsletter
- /rss/route.ts (new): dynamic RSS feed reading from legacy-source news section; replaces 404 at /rss

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 21:49:34 +00:00
parent 09a24be3de
commit a240133ec7
8 changed files with 175 additions and 95 deletions

View File

@@ -1,3 +1,6 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export const metadata = {
title: "About BroadcastBeat",
description: "BroadcastBeat is an independent industry publication covering broadcast, production, and post-production technology.",
@@ -5,29 +8,33 @@ export const metadata = {
export default function AboutPage() {
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">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>
<div className="min-h-screen bg-background">
<Header />
<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>
<Footer />
</div>
);
}

View File

@@ -1,33 +1,40 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
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>
<div className="min-h-screen bg-background">
<Header />
<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>
<Footer />
</div>
);
}

View File

@@ -1,4 +1,6 @@
import { createClient } from "@supabase/supabase-js";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export const dynamic = "force-dynamic";
export const revalidate = 600;
@@ -32,36 +34,40 @@ export default async function TeamPage() {
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>
<div className="min-h-screen bg-background">
<Header />
<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]"
/>
)}
{p.bio && <p className="text-sm text-[#9ca3af] mt-2 leading-relaxed">{p.bio}</p>}
</div>
</li>
))}
</ul>
</main>
<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>
<Footer />
</div>
);
}

View File

@@ -0,0 +1,11 @@
import { redirect } from "next/navigation";
export const metadata = {
title: "Newsletter — BroadcastBeat",
description: "BroadcastBeat newsletter archive.",
alternates: { canonical: "/newsletter/archive" },
};
export default function NewsletterIndex() {
redirect("/newsletter/archive");
}

60
src/app/rss/route.ts Normal file
View File

@@ -0,0 +1,60 @@
import { getLegacyArticlesBySection } from "@/lib/articles/legacy-source";
export const dynamic = "force-dynamic";
export const revalidate = 1800;
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://broadcastbeat.com";
function escapeXml(s: string): string {
return s
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
export async function GET() {
let items = "";
try {
const articles = await getLegacyArticlesBySection("news", 50);
items = articles
.map((a) => {
const url = `${SITE_URL}/news/${a.slug}`;
const pubDate = a.date ? new Date(a.date).toUTCString() : new Date().toUTCString();
const title = escapeXml(a.title || "Untitled");
const description = escapeXml(a.excerpt || "");
return ` <item>
<title>${title}</title>
<link>${url}</link>
<guid isPermaLink="true">${url}</guid>
<pubDate>${pubDate}</pubDate>
<description>${description}</description>
</item>`;
})
.join("\n");
} catch {
// serve an empty but valid feed on failure
items = "";
}
const body = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>BroadcastBeat</title>
<link>${SITE_URL}</link>
<atom:link href="${SITE_URL}/rss" rel="self" type="application/rss+xml" />
<description>News &amp; Intelligence for Broadcast, Post-Production &amp; Streaming Technology</description>
<language>en-us</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
${items}
</channel>
</rss>`;
return new Response(body, {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
"Cache-Control": "public, max-age=1800, s-maxage=1800, stale-while-revalidate=3600",
},
});
}

View File

@@ -8,7 +8,6 @@ const ITEMS = [
{ label: "Editorial team", href: "/about/team" },
{ label: "Contact", href: "/about/contact" },
{ label: "Advertise", href: "/about/advertise" },
{ label: "Press kit", href: "/about/press-kit" },
];
export default function AboutDropdown() {

View File

@@ -6,7 +6,6 @@ import AppImage from "@/components/ui/AppImage";
import {
LinkedInIcon,
InstagramIcon,
TwitterXIcon,
FacebookIcon,
RssIcon,
MenuIcon,
@@ -215,19 +214,15 @@ export default function Header() {
News & Intelligence for Broadcast, Post-Production & Streaming Technology
</p>
<div className="flex items-center gap-3 ml-auto">
<a href="https://linkedin.com" target="_blank" rel="noopener noreferrer" aria-label="BroadcastBeat on LinkedIn"
<a href="https://linkedin.com/company/broadcastbeat" target="_blank" rel="noopener noreferrer" aria-label="BroadcastBeat on LinkedIn"
className="text-[#999] hover:text-[#3b82f6] focus:text-[#3b82f6] focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors social-icon">
<LinkedInIcon size={14} />
</a>
<a href="https://instagram.com" target="_blank" rel="noopener noreferrer" aria-label="BroadcastBeat on Instagram"
<a href="https://instagram.com/broadcastbeat" target="_blank" rel="noopener noreferrer" aria-label="BroadcastBeat on Instagram"
className="text-[#999] hover:text-[#3b82f6] focus:text-[#3b82f6] focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors social-icon">
<InstagramIcon size={14} />
</a>
<a href="https://twitter.com" target="_blank" rel="noopener noreferrer" aria-label="BroadcastBeat on Twitter/X"
className="text-[#999] hover:text-[#3b82f6] focus:text-[#3b82f6] focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors social-icon">
<TwitterXIcon size={14} />
</a>
<a href="https://facebook.com" target="_blank" rel="noopener noreferrer" aria-label="BroadcastBeat on Facebook"
<a href="https://facebook.com/broadcastbeat" target="_blank" rel="noopener noreferrer" aria-label="BroadcastBeat on Facebook"
className="text-[#999] hover:text-[#3b82f6] focus:text-[#3b82f6] focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors social-icon">
<FacebookIcon size={14} />
</a>

View File

@@ -50,12 +50,7 @@ export default async function SystemStatusBar() {
>
<div className="max-w-container mx-auto px-4 h-[28px] flex items-center justify-between text-[10px] font-mono uppercase tracking-wider text-[var(--color-text-tertiary,#6b7280)]">
<div className="flex items-center gap-5">
<span className="inline-flex items-center gap-1.5">
<span className="bb-pulse-dot bb-pulse-dot--green" />
Index online
</span>
<span>{stats.articles.toLocaleString()} articles indexed</span>
<span>{stats.sources.toLocaleString()} wire sources</span>
<span>{stats.events} events tracked</span>
</div>
<span className="text-[var(--color-text-info,#60a5fa)]">[{BUILD_VERSION}]</span>