AV Beat rebrand: theme swap (orange #E67E22 / near-black), logo, schema default → avb
- public/assets/images/logo.png + public/brand/logo*.png replaced with the 1320x310 AV Beat logo
- public/assets/logos/avbeat.png added as the canonical source
- tailwind.config.js + src/styles/tailwind.css + src/styles/redesign-tokens.css palette swapped (primary #0F0E0E, secondary #1D1A1A, accent #E67E22 orange, foreground #F0EBE6 warm white)
- Layout / robots / sitemap / home-page / about / contact / press-kit / team / global-error metadata switched to AV Beat (Where AV Meets IT)
- Bulk replaced 'Broadcast Beat'/'BroadcastBeat.com'/'broadcastbeat.com' across user-facing news/forum/marketplace/advertise/search/newsletter pages
- src/lib/supabase/{admin,server,client} now default to schema 'avb' (was 'bb') — overridable via NEXT_PUBLIC_SUPABASE_SCHEMA
- package.json renamed to 'avbeat'
- Single d60701 reference in about/team swapped to E67E22
Design-only replica — no article content migrated.
This commit is contained in:
@@ -2,13 +2,13 @@ import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import SidebarAdStack from "@/components/SidebarAdStack";
|
||||
|
||||
export const metadata = { title: "Contact — Broadcast Beat" };
|
||||
export const metadata = { title: "Contact — AV Beat" };
|
||||
|
||||
const CONTACTS = [
|
||||
{
|
||||
title: "Editorial",
|
||||
body: (
|
||||
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:editor@broadcastbeat.com">editor@broadcastbeat.com</a>
|
||||
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:editor@avbeat.com">editor@avbeat.com</a>
|
||||
),
|
||||
hint: "Story tips, corrections, interview requests.",
|
||||
},
|
||||
@@ -26,7 +26,7 @@ const CONTACTS = [
|
||||
title: "Advertising",
|
||||
body: (
|
||||
<>
|
||||
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:advertising@broadcastbeat.com">advertising@broadcastbeat.com</a> — see the{" "}
|
||||
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:advertising@avbeat.com">advertising@avbeat.com</a> — see the{" "}
|
||||
<a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="/about/advertise">media kit</a>.
|
||||
</>
|
||||
),
|
||||
@@ -52,8 +52,8 @@ export default function ContactPage() {
|
||||
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10">
|
||||
<main>
|
||||
<header className="mb-8 border-b border-[#252525] pb-6">
|
||||
<div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2">
|
||||
Broadcast Beat
|
||||
<div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#E67E22)] mb-2">
|
||||
AV Beat · Where AV Meets IT
|
||||
</div>
|
||||
<h1 className="font-serif text-4xl md:text-5xl font-bold tracking-tight">Contact</h1>
|
||||
<p className="mt-3 text-[#9ca3af] text-base">
|
||||
|
||||
@@ -3,8 +3,8 @@ import Footer from "@/components/Footer";
|
||||
import SidebarAdStack from "@/components/SidebarAdStack";
|
||||
|
||||
export const metadata = {
|
||||
title: "About Broadcast Beat",
|
||||
description: "Broadcast Beat is an independent industry publication covering broadcast, production, and post-production technology.",
|
||||
title: "About AV Beat",
|
||||
description: "AV Beat is an independent industry publication covering the convergence of pro AV and IT — broadcast, live production, streaming, and post-production technology.",
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
@@ -15,12 +15,12 @@ export default function AboutPage() {
|
||||
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10">
|
||||
<main>
|
||||
<header className="mb-8 border-b border-[#252525] pb-6">
|
||||
<div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2">
|
||||
Broadcast Beat
|
||||
<div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#E67E22)] mb-2">
|
||||
AV Beat · Where AV Meets IT
|
||||
</div>
|
||||
<h1 className="font-serif text-4xl md:text-5xl font-bold tracking-tight">About</h1>
|
||||
<p className="mt-3 text-[#9ca3af] text-base max-w-2xl">
|
||||
An independent trade publication covering broadcast, live production, streaming, and post-production technology.
|
||||
An independent trade publication covering the convergence of pro AV and IT — broadcast, live production, streaming, and post-production technology.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -32,11 +32,11 @@ export default function AboutPage() {
|
||||
|
||||
<section className="space-y-6 text-[#d1d5db] text-[15px] leading-relaxed">
|
||||
<Block title="What we cover">
|
||||
Broadcast Beat reports on the equipment, services, and people
|
||||
that move the professional video industry forward. We follow
|
||||
product launches, executive moves, acquisitions, and the live
|
||||
production / IP / cloud workflows reshaping how content gets
|
||||
made and delivered.
|
||||
AV Beat reports on the equipment, services, and people that
|
||||
move the professional AV and IT-converged video industry
|
||||
forward. We follow product launches, executive moves,
|
||||
acquisitions, and the live production / IP / cloud workflows
|
||||
reshaping how content gets made and delivered.
|
||||
</Block>
|
||||
|
||||
<Block title="Where we go">
|
||||
@@ -57,9 +57,9 @@ export default function AboutPage() {
|
||||
</Block>
|
||||
|
||||
<Block title="Get involved">
|
||||
For editorial pitches: <a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:editor@broadcastbeat.com">editor@broadcastbeat.com</a>.
|
||||
{" "}For advertising: <a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="/about/advertise">see the media kit</a>.
|
||||
{" "}For our team: <a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="/about/team">meet the editorial team</a>.
|
||||
For editorial pitches: <a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="mailto:editor@avbeat.com">editor@avbeat.com</a>.
|
||||
{" "}For advertising: <a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="/about/advertise">see the media kit</a>.
|
||||
{" "}For our team: <a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="/about/team">meet the editorial team</a>.
|
||||
</Block>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
export const metadata = { title: "Press kit — Broadcast Beat" };
|
||||
export const metadata = { title: "Press kit — AV Beat" };
|
||||
|
||||
export default function PressKitPage() {
|
||||
return (
|
||||
@@ -13,23 +13,23 @@ export default function PressKitPage() {
|
||||
<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>Broadcast Beat logo (PNG)</a></li>
|
||||
<li><a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="/assets/images/logo.png" download>AV Beat 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">
|
||||
Broadcast Beat is an independent trade publication covering broadcast,
|
||||
live production, streaming, and post-production technology. Founded
|
||||
to bring engineering-rigor reporting to industry coverage, Broadcast Beat
|
||||
publishes daily on the equipment, services, and people that move
|
||||
professional video forward.
|
||||
AV Beat is an independent trade publication covering the convergence
|
||||
of pro AV and IT — broadcast, live production, streaming, and
|
||||
post-production technology. Founded to bring engineering-rigor
|
||||
reporting to industry coverage, AV Beat publishes daily on the
|
||||
equipment, services, and people moving 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>
|
||||
<a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="mailto:ryan.salazar@relevantmediaproperties.com">ryan.salazar@relevantmediaproperties.com</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -98,11 +98,11 @@ export default async function TeamPage() {
|
||||
<main>
|
||||
{/* Hero */}
|
||||
<header className="relative overflow-hidden rounded-xl mb-10 border border-[#1a1f2a] bg-gradient-to-br from-[#0d1118] via-[#0b0f17] to-[#0d1118]">
|
||||
<div className="absolute inset-0 opacity-[0.04] bg-[radial-gradient(ellipse_at_top_right,#d60701,transparent_60%)]" aria-hidden />
|
||||
<div className="absolute inset-0 opacity-[0.04] bg-[radial-gradient(ellipse_at_top_right,#E67E22,transparent_60%)]" aria-hidden />
|
||||
<div className="relative px-6 py-10 sm:px-10 sm:py-14">
|
||||
<div className="inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.22em] text-[#ff5a55] mb-3">
|
||||
<span className="w-6 h-px bg-[#d60701]" />
|
||||
Broadcast Beat · editorial
|
||||
<div className="inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.22em] text-[#F0B07A] mb-3">
|
||||
<span className="w-6 h-px bg-[#E67E22]" />
|
||||
AV Beat · editorial
|
||||
</div>
|
||||
<h1 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight">
|
||||
The team behind every byline
|
||||
@@ -129,10 +129,10 @@ export default async function TeamPage() {
|
||||
return (
|
||||
<li
|
||||
key={p.slug}
|
||||
className="group relative rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 hover:border-[#d60701]/50 hover:shadow-[0_0_0_1px_rgba(214,7,1,0.15)] transition-all"
|
||||
className="group relative rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 hover:border-[#E67E22]/50 hover:shadow-[0_0_0_1px_rgba(230,126,34,0.15)] transition-all"
|
||||
>
|
||||
{/* Top accent */}
|
||||
<div className="absolute top-0 left-5 right-5 h-px bg-gradient-to-r from-transparent via-[#d60701]/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" aria-hidden />
|
||||
<div className="absolute top-0 left-5 right-5 h-px bg-gradient-to-r from-transparent via-[#E67E22]/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" aria-hidden />
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
{p.avatar_url ? (
|
||||
@@ -142,7 +142,7 @@ export default async function TeamPage() {
|
||||
alt={p.name}
|
||||
width={96}
|
||||
height={96}
|
||||
className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] group-hover:border-[#d60701]/40 transition-colors object-cover"
|
||||
className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] group-hover:border-[#E67E22]/40 transition-colors object-cover"
|
||||
/>
|
||||
</a>
|
||||
) : (
|
||||
@@ -155,7 +155,7 @@ export default async function TeamPage() {
|
||||
<h3 className="font-serif text-xl font-bold leading-tight">{p.name}</h3>
|
||||
</a>
|
||||
{(p.beat || p.slug === "ryan-salazar") && (
|
||||
<div className="inline-block mt-1.5 px-2 py-0.5 rounded-sm bg-[#d60701]/10 border border-[#d60701]/25 text-[10px] font-mono uppercase tracking-[0.12em] text-[#ff6b66]">
|
||||
<div className="inline-block mt-1.5 px-2 py-0.5 rounded-sm bg-[#E67E22]/10 border border-[#E67E22]/25 text-[10px] font-mono uppercase tracking-[0.12em] text-[#F0B07A]">
|
||||
{displayBeat(p)}
|
||||
</div>
|
||||
)}
|
||||
@@ -173,11 +173,11 @@ export default async function TeamPage() {
|
||||
<ObfuscatedEmail
|
||||
user={e.user}
|
||||
domain={e.domain}
|
||||
className="text-[#aab1bd] hover:text-[#ff6b66] font-mono"
|
||||
className="text-[#aab1bd] hover:text-[#F0B07A] font-mono"
|
||||
/>
|
||||
<a
|
||||
href={`/authors/${p.slug}`}
|
||||
className="text-[10px] font-mono uppercase tracking-[0.12em] text-[#6c7686] hover:text-[#ff6b66]"
|
||||
className="text-[10px] font-mono uppercase tracking-[0.12em] text-[#6c7686] hover:text-[#F0B07A]"
|
||||
>
|
||||
View →
|
||||
</a>
|
||||
@@ -209,7 +209,7 @@ export default async function TeamPage() {
|
||||
{ops.map((p) => (
|
||||
<li
|
||||
key={p.id}
|
||||
className="rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 flex gap-4 hover:border-[#d60701]/40 transition-colors"
|
||||
className="rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 flex gap-4 hover:border-[#E67E22]/40 transition-colors"
|
||||
>
|
||||
{p.avatar_url ? (
|
||||
<img
|
||||
@@ -235,13 +235,13 @@ export default async function TeamPage() {
|
||||
if (!e) return null;
|
||||
return (
|
||||
<div className="font-mono">
|
||||
<ObfuscatedEmail user={e.user} domain={e.domain} className="hover:text-[#ff6b66]" />
|
||||
<ObfuscatedEmail user={e.user} domain={e.domain} className="hover:text-[#F0B07A]" />
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{p.phone && (
|
||||
<div>
|
||||
<a href={`tel:${p.phone.replace(/[^\d+]/g, '')}`} className="hover:text-[#ff6b66] font-mono">
|
||||
<a href={`tel:${p.phone.replace(/[^\d+]/g, '')}`} className="hover:text-[#F0B07A] font-mono">
|
||||
{p.phone}{p.extension ? ` ext. ${p.extension}` : ''}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Advertise With Broadcast Beat — Reach Broadcast Engineering Professionals',
|
||||
description: 'Advertise with Broadcast Beat to reach broadcast engineers, production professionals, and media technology decision-makers. Display ads, sponsored content, and newsletter placements.',
|
||||
title: 'Advertise With AV Beat — Reach Broadcast Engineering Professionals',
|
||||
description: 'Advertise with AV Beat to reach broadcast engineers, production professionals, and media technology decision-makers. Display ads, sponsored content, and newsletter placements.',
|
||||
alternates: { canonical: '/advertise' },
|
||||
openGraph: {
|
||||
title: 'Advertise With Broadcast Beat',
|
||||
title: 'Advertise With AV Beat',
|
||||
description: 'Reach broadcast engineers, production professionals, and media technology decision-makers.',
|
||||
url: '/advertise',
|
||||
type: 'website',
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function AdvertisePage() {
|
||||
<span className="section-label mb-3 inline-block">Advertise</span>
|
||||
<h1 className="font-heading text-white text-4xl font-bold mb-4">Reach Broadcast Engineering Professionals</h1>
|
||||
<p className="text-[#aaa] font-body text-lg max-w-2xl mx-auto mb-6">
|
||||
Broadcast Beat is the leading digital platform for broadcast engineering professionals. Connect your brand with decision-makers, engineers, and executives across the broadcast industry.
|
||||
AV Beat is the leading digital platform for broadcast engineering professionals. Connect your brand with decision-makers, engineers, and executives across the broadcast industry.
|
||||
</p>
|
||||
<a
|
||||
href="#contact"
|
||||
@@ -69,7 +69,7 @@ export default function AdvertisePage() {
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.label} className="bg-[#111] border border-[#222] p-5 text-center">
|
||||
<div className="font-heading text-[#3b82f6] text-2xl font-bold mb-1">{stat.value}</div>
|
||||
<div className="font-heading text-[#E67E22] text-2xl font-bold mb-1">{stat.value}</div>
|
||||
<div className="font-body text-[#777] text-xs uppercase tracking-wide">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -86,10 +86,10 @@ export default function AdvertisePage() {
|
||||
<table className="w-full text-sm font-body">
|
||||
<thead>
|
||||
<tr className="border-b border-[#222]">
|
||||
<th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Placement</th>
|
||||
<th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Dimensions</th>
|
||||
<th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Location</th>
|
||||
<th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Rate</th>
|
||||
<th className="text-left py-3 px-4 text-[#E67E22] font-bold text-xs uppercase tracking-wider">Placement</th>
|
||||
<th className="text-left py-3 px-4 text-[#E67E22] font-bold text-xs uppercase tracking-wider">Dimensions</th>
|
||||
<th className="text-left py-3 px-4 text-[#E67E22] font-bold text-xs uppercase tracking-wider">Location</th>
|
||||
<th className="text-left py-3 px-4 text-[#E67E22] font-bold text-xs uppercase tracking-wider">Rate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -98,7 +98,7 @@ export default function AdvertisePage() {
|
||||
<td className="py-3 px-4 text-[#e0e0e0] font-medium">{zone.name}</td>
|
||||
<td className="py-3 px-4 text-[#888] font-mono text-xs">{zone.dimensions}</td>
|
||||
<td className="py-3 px-4 text-[#777]">{zone.placement}</td>
|
||||
<td className="py-3 px-4 text-[#3b82f6] font-medium">{zone.cpm}</td>
|
||||
<td className="py-3 px-4 text-[#E67E22] font-medium">{zone.cpm}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -123,23 +123,23 @@ export default function AdvertisePage() {
|
||||
<h3 className="font-heading text-[#e0e0e0] text-lg font-bold mb-4">Sales Contact</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p>
|
||||
<p className="font-body text-[#E67E22] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p>
|
||||
<p className="font-body text-[#e0e0e0] font-medium">Jeff Victor</p>
|
||||
<a href="mailto:jeff@broadcastbeat.com" className="font-body text-[#3b82f6] text-sm hover:underline">
|
||||
jeff@broadcastbeat.com
|
||||
<a href="mailto:jeff@avbeat.com" className="font-body text-[#E67E22] text-sm hover:underline">
|
||||
jeff@avbeat.com
|
||||
</a>
|
||||
</div>
|
||||
<div className="border-t border-[#222] pt-4">
|
||||
<p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Publication</p>
|
||||
<p className="font-body text-[#777] text-sm">Broadcast Beat</p>
|
||||
<p className="font-body text-[#E67E22] text-xs font-bold uppercase tracking-wider mb-1">Publication</p>
|
||||
<p className="font-body text-[#777] text-sm">AV Beat</p>
|
||||
<p className="font-body text-[#777] text-sm">Relevant Media Properties</p>
|
||||
</div>
|
||||
<div className="border-t border-[#222] pt-4">
|
||||
<p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-2">What We Offer</p>
|
||||
<p className="font-body text-[#E67E22] text-xs font-bold uppercase tracking-wider mb-2">What We Offer</p>
|
||||
<ul className="space-y-1.5">
|
||||
{["Display advertising (web)", "Newsletter sponsorships", "Sponsored content", "Event coverage partnerships", "Podcast sponsorships"].map((item) => (
|
||||
<li key={item} className="flex items-start gap-2 text-[#777] text-sm font-body">
|
||||
<span className="text-[#3b82f6] mt-0.5">✓</span>
|
||||
<span className="text-[#E67E22] mt-0.5">✓</span>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
@@ -152,16 +152,16 @@ export default function AdvertisePage() {
|
||||
{/* Form */}
|
||||
<div className="lg:col-span-8">
|
||||
{submitted ? (
|
||||
<div className="bg-[#111] border border-[#3b82f6] p-8 text-center">
|
||||
<div className="w-12 h-12 rounded-full bg-[#3b82f6]/20 flex items-center justify-center mx-auto mb-4">
|
||||
<div className="bg-[#111] border border-[#E67E22] p-8 text-center">
|
||||
<div className="w-12 h-12 rounded-full bg-[#E67E22]/20 flex items-center justify-center mx-auto mb-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M5 12l4 4 10-10" stroke="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M5 12l4 4 10-10" stroke="#E67E22" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="font-heading text-[#e0e0e0] text-xl font-bold mb-2">Message Sent!</h3>
|
||||
<p className="font-body text-[#777] text-sm">
|
||||
Thanks for reaching out. Jeff will be in touch within one business day at{" "}
|
||||
<a href="mailto:jeff@broadcastbeat.com" className="text-[#3b82f6] hover:underline">jeff@broadcastbeat.com</a>.
|
||||
<a href="mailto:jeff@avbeat.com" className="text-[#E67E22] hover:underline">jeff@avbeat.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -245,7 +245,7 @@ export default function AdvertisePage() {
|
||||
{loading ? "Sending..." : "Send Inquiry"}
|
||||
</button>
|
||||
<p className="text-[#555] text-xs font-body">
|
||||
Or email directly: <a href="mailto:jeff@broadcastbeat.com" className="text-[#3b82f6] hover:underline">jeff@broadcastbeat.com</a>
|
||||
Or email directly: <a href="mailto:jeff@avbeat.com" className="text-[#E67E22] hover:underline">jeff@avbeat.com</a>
|
||||
</p>
|
||||
</form>
|
||||
)}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'The Crew Lounge — Broadcast Beat',
|
||||
title: 'The Crew Lounge — AV Beat',
|
||||
description: 'The Crew Lounge: connect with broadcast, motion picture and post production professionals worldwide. Discuss live production, IP workflows, streaming, audio, cameras, AI automation, and more.',
|
||||
alternates: { canonical: '/forum' },
|
||||
openGraph: {
|
||||
title: 'The Crew Lounge — Broadcast Beat',
|
||||
title: 'The Crew Lounge — AV Beat',
|
||||
description: 'The Crew Lounge: connect with broadcast, motion picture and post production professionals worldwide. Ask questions, share expertise, and discuss broadcast technology.',
|
||||
url: '/forum',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary',
|
||||
title: 'The Crew Lounge — Broadcast Beat',
|
||||
description: 'The Crew Lounge: connect with broadcast, motion picture and post production professionals worldwide on Broadcast Beat.',
|
||||
title: 'The Crew Lounge — AV Beat',
|
||||
description: 'The Crew Lounge: connect with broadcast, motion picture and post production professionals worldwide on AV Beat.',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,19 +21,19 @@ export default function ForumLayout({ children }: { children: React.ReactNode })
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebPage',
|
||||
name: 'Broadcast Beat — The Crew Lounge',
|
||||
name: 'AV Beat — The Crew Lounge',
|
||||
description: 'The Crew Lounge: an industry community for broadcast, motion picture and post production professionals worldwide.',
|
||||
url: 'https://broadcastbeat.com/forum',
|
||||
url: 'https://avbeat.com/forum',
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: 'Broadcast Beat',
|
||||
url: 'https://broadcastbeat.com',
|
||||
name: 'AV Beat',
|
||||
url: 'https://avbeat.com',
|
||||
},
|
||||
breadcrumb: {
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://broadcastbeat.com' },
|
||||
{ '@type': 'ListItem', position: 2, name: 'The Crew Lounge', item: 'https://broadcastbeat.com/forum' },
|
||||
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://avbeat.com' },
|
||||
{ '@type': 'ListItem', position: 2, name: 'The Crew Lounge', item: 'https://avbeat.com/forum' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,8 +54,8 @@ function ForumLoginInner() {
|
||||
<main className="min-h-screen bg-gradient-to-b from-[#0d0d0d] via-[#111111] to-[#0d0d0d]">
|
||||
<div className="max-w-md mx-auto px-4 py-16">
|
||||
<div className="text-center mb-6">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#3b82f6] font-mono mb-2">Welcome back</div>
|
||||
<h1 className="text-3xl font-heading font-bold text-white">Sign in to Broadcast Beat</h1>
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#E67E22] font-mono mb-2">Welcome back</div>
|
||||
<h1 className="text-3xl font-heading font-bold text-white">Sign in to AV Beat</h1>
|
||||
<p className="text-[#888] font-body text-sm mt-2">
|
||||
Your account covers the forum, newsletter preferences, and saved articles.
|
||||
</p>
|
||||
@@ -79,7 +79,7 @@ function ForumLoginInner() {
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autoComplete="email"
|
||||
required
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#E67E22]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -92,20 +92,20 @@ function ForumLoginInner() {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#E67E22]"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors"
|
||||
className="w-full bg-[#E67E22] hover:bg-[#C8651B] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? 'Signing in…' : 'Sign In'}
|
||||
</button>
|
||||
<div className="text-right text-xs font-body">
|
||||
<Link
|
||||
href={`/forum/forgot-password?email=${encodeURIComponent(email)}`}
|
||||
className="text-[#888] hover:text-[#3b82f6] hover:underline"
|
||||
className="text-[#888] hover:text-[#E67E22] hover:underline"
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
@@ -115,7 +115,7 @@ function ForumLoginInner() {
|
||||
No account yet?{' '}
|
||||
<Link
|
||||
href={`/forum/register?next=${encodeURIComponent(next)}`}
|
||||
className="text-[#3b82f6] hover:underline"
|
||||
className="text-[#E67E22] hover:underline"
|
||||
>
|
||||
Create one
|
||||
</Link>
|
||||
|
||||
@@ -103,7 +103,7 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
|
||||
|
||||
const getMetricValue = (thread: ForumThread) => {
|
||||
if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' };
|
||||
if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#3b82f6]' };
|
||||
if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#E67E22]' };
|
||||
return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' };
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
|
||||
onClick={() => setActiveMetric(m.value)}
|
||||
className={`px-3 py-1 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
|
||||
activeMetric === m.value
|
||||
? 'bg-[#3b82f6] text-white'
|
||||
? 'bg-[#E67E22] text-white'
|
||||
: 'text-[#666] hover:text-[#aaa]'
|
||||
}`}
|
||||
>
|
||||
@@ -171,13 +171,13 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
|
||||
</span>
|
||||
{/* Thread info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white font-body text-sm font-semibold group-hover:text-[#3b82f6] transition-colors truncate leading-snug">
|
||||
<p className="text-white font-body text-sm font-semibold group-hover:text-[#E67E22] transition-colors truncate leading-snug">
|
||||
{thread.title}
|
||||
</p>
|
||||
<p className="text-[#555] font-body text-xs mt-0.5">
|
||||
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#3b82f6] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
|
||||
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#E67E22] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
|
||||
{thread.forum_categories && (
|
||||
<> · <span className="text-[#3b82f6]">{thread.forum_categories.name}</span></>
|
||||
<> · <span className="text-[#E67E22]">{thread.forum_categories.name}</span></>
|
||||
)}
|
||||
{' · '}{timeAgo(thread.created_at)}
|
||||
</p>
|
||||
@@ -301,7 +301,7 @@ export default function ForumIndexPage() {
|
||||
{currentUserId ? (
|
||||
<Link
|
||||
href="/forum/new"
|
||||
className="inline-flex items-center gap-2 bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors"
|
||||
className="inline-flex items-center gap-2 bg-[#E67E22] hover:bg-[#C8651B] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
@@ -311,7 +311,7 @@ export default function ForumIndexPage() {
|
||||
) : (
|
||||
<Link
|
||||
href="/forum/login?next=/forum"
|
||||
className="inline-flex items-center gap-2 bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors"
|
||||
className="inline-flex items-center gap-2 bg-[#E67E22] hover:bg-[#C8651B] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M15 12H3" />
|
||||
@@ -333,7 +333,7 @@ export default function ForumIndexPage() {
|
||||
<button
|
||||
onClick={() => setFeedMode('discover')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${
|
||||
feedMode === 'discover' ? 'bg-[#3b82f6] text-white' : 'text-[#888] hover:text-white'
|
||||
feedMode === 'discover' ? 'bg-[#E67E22] text-white' : 'text-[#888] hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
@@ -344,7 +344,7 @@ export default function ForumIndexPage() {
|
||||
<button
|
||||
onClick={() => setFeedMode('following')}
|
||||
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${
|
||||
feedMode === 'following' ? 'bg-[#3b82f6] text-white' : 'text-[#888] hover:text-white'
|
||||
feedMode === 'following' ? 'bg-[#E67E22] text-white' : 'text-[#888] hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
@@ -363,7 +363,7 @@ export default function ForumIndexPage() {
|
||||
<p className="text-[#aab4c4] font-body text-sm mb-3">Sign in to see threads from people you follow.</p>
|
||||
<Link
|
||||
href="/login"
|
||||
className="inline-block bg-[#3b82f6] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#2563eb] transition-colors"
|
||||
className="inline-block bg-[#E67E22] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#C8651B] transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
@@ -384,7 +384,7 @@ export default function ForumIndexPage() {
|
||||
<Link
|
||||
href="/forum"
|
||||
onClick={() => setFeedMode('discover')}
|
||||
className="inline-block bg-[#3b82f6] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#2563eb] transition-colors"
|
||||
className="inline-block bg-[#E67E22] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#C8651B] transition-colors"
|
||||
>
|
||||
Discover Members
|
||||
</Link>
|
||||
@@ -398,10 +398,10 @@ export default function ForumIndexPage() {
|
||||
<Link
|
||||
key={thread.id}
|
||||
href={`/forum/thread/${thread.id}`}
|
||||
className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#3b82f6] rounded-lg px-4 py-3 transition-all group"
|
||||
className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#E67E22] rounded-lg px-4 py-3 transition-all group"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#3b82f6] transition-colors truncate">
|
||||
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#E67E22] transition-colors truncate">
|
||||
{thread.title}
|
||||
</h3>
|
||||
<p className="text-[#666] font-body text-xs mt-0.5">
|
||||
@@ -409,7 +409,7 @@ export default function ForumIndexPage() {
|
||||
<Link
|
||||
href={`/profile/${thread.author_id}`}
|
||||
onClick={e => e.stopPropagation()}
|
||||
className="text-[#3b82f6] hover:underline"
|
||||
className="text-[#E67E22] hover:underline"
|
||||
>
|
||||
{thread.author_name}
|
||||
</Link>
|
||||
@@ -450,7 +450,7 @@ export default function ForumIndexPage() {
|
||||
placeholder="Search threads..."
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
className="w-full bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#3b82f6] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors"
|
||||
className="w-full bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#E67E22] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors"
|
||||
/>
|
||||
{search && (
|
||||
<button
|
||||
@@ -470,7 +470,7 @@ export default function ForumIndexPage() {
|
||||
<select
|
||||
value={selectedCategory}
|
||||
onChange={e => setSelectedCategory(e.target.value)}
|
||||
className="appearance-none bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#3b82f6] rounded-lg pl-3 pr-8 py-2.5 text-sm font-body text-white focus:outline-none transition-colors cursor-pointer min-w-[160px]"
|
||||
className="appearance-none bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#E67E22] rounded-lg pl-3 pr-8 py-2.5 text-sm font-body text-white focus:outline-none transition-colors cursor-pointer min-w-[160px]"
|
||||
>
|
||||
<option value="">All Categories</option>
|
||||
{categories.map(cat => (
|
||||
@@ -490,7 +490,7 @@ export default function ForumIndexPage() {
|
||||
onClick={() => setSort(opt.value)}
|
||||
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
|
||||
sort === opt.value
|
||||
? 'bg-[#3b82f6] text-white'
|
||||
? 'bg-[#E67E22] text-white'
|
||||
: 'text-[#888] hover:text-white'
|
||||
}`}
|
||||
>
|
||||
@@ -523,16 +523,16 @@ export default function ForumIndexPage() {
|
||||
<Link
|
||||
key={thread.id}
|
||||
href={`/forum/thread/${thread.id}`}
|
||||
className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#3b82f6] rounded-lg px-4 py-3 transition-all group"
|
||||
className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#E67E22] rounded-lg px-4 py-3 transition-all group"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#3b82f6] transition-colors truncate">
|
||||
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#E67E22] transition-colors truncate">
|
||||
{thread.title}
|
||||
</h3>
|
||||
<p className="text-[#666] font-body text-xs mt-0.5">
|
||||
by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#3b82f6] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
|
||||
by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#E67E22] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
|
||||
{thread.forum_categories && (
|
||||
<> · <span className="text-[#3b82f6]">{thread.forum_categories.name}</span></>
|
||||
<> · <span className="text-[#E67E22]">{thread.forum_categories.name}</span></>
|
||||
)}
|
||||
{' · '}{timeAgo(thread.created_at)}
|
||||
</p>
|
||||
@@ -584,11 +584,11 @@ export default function ForumIndexPage() {
|
||||
<Link
|
||||
key={cat.id}
|
||||
href={`/forum/${cat.slug}`}
|
||||
className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#3b82f6] rounded-lg p-4 transition-all group"
|
||||
className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#E67E22] rounded-lg p-4 transition-all group"
|
||||
>
|
||||
<span className="text-3xl flex-shrink-0 w-10 text-center">{cat.icon}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-white font-heading font-semibold text-lg group-hover:text-[#3b82f6] transition-colors">
|
||||
<h2 className="text-white font-heading font-semibold text-lg group-hover:text-[#E67E22] transition-colors">
|
||||
{cat.name}
|
||||
</h2>
|
||||
<p className="text-[#888] font-body text-sm mt-0.5 truncate">{cat.description}</p>
|
||||
@@ -610,7 +610,7 @@ export default function ForumIndexPage() {
|
||||
<div className="text-white font-body font-semibold text-sm">{cat.post_count}</div>
|
||||
<div className="text-[#666] font-body text-xs">posts</div>
|
||||
</div>
|
||||
<svg className="w-4 h-4 text-[#555] group-hover:text-[#3b82f6] flex-shrink-0 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-4 h-4 text-[#555] group-hover:text-[#E67E22] flex-shrink-0 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
@@ -629,7 +629,7 @@ export default function ForumIndexPage() {
|
||||
<ul className="text-[#aab4c4] font-body text-sm space-y-1">
|
||||
<li>• Be respectful and professional — this is a community of peers</li>
|
||||
<li>• Share real-world experience and cite sources when possible</li>
|
||||
<li>• The <span className="text-[#3b82f6]">Broadcast Beat AI</span> assistant is available in threads to help answer questions — it is clearly labeled</li>
|
||||
<li>• The <span className="text-[#E67E22]">AV Beat AI</span> assistant is available in threads to help answer questions — it is clearly labeled</li>
|
||||
<li>• No spam, self-promotion, or vendor pitches without disclosure</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -105,9 +105,9 @@ function ForumRegisterInner() {
|
||||
<div className="max-w-5xl mx-auto px-4 py-12 grid grid-cols-1 lg:grid-cols-[1fr_440px] gap-10 items-start">
|
||||
{/* Left rail — what membership gets you */}
|
||||
<aside className="hidden lg:block">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#3b82f6] font-mono mb-3">Membership</div>
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#E67E22] font-mono mb-3">Membership</div>
|
||||
<h1 className="font-heading text-white text-4xl xl:text-5xl font-bold leading-tight mb-4">
|
||||
Join Broadcast Beat
|
||||
Join AV Beat
|
||||
</h1>
|
||||
<p className="text-[#cbd5e1] text-base leading-relaxed mb-6 max-w-md">
|
||||
One free account for the broadcast engineering community — covering NAB, IBC, and the people behind every signal chain.
|
||||
@@ -131,13 +131,13 @@ function ForumRegisterInner() {
|
||||
<div className="mt-6 text-[11px] text-[#666] leading-relaxed max-w-md border-t border-[#252525] pt-4">
|
||||
<strong className="text-[#888]">PR firms & contributors:</strong> a separate contributor application is required to submit press releases.
|
||||
{" "}
|
||||
<Link href="/about/advertise" className="text-[#3b82f6] hover:underline">Contact us</Link> if you represent a manufacturer or agency.
|
||||
<Link href="/about/advertise" className="text-[#E67E22] hover:underline">Contact us</Link> if you represent a manufacturer or agency.
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Mobile-only hero (collapsed copy) */}
|
||||
<div className="lg:hidden text-center mb-2">
|
||||
<h1 className="font-heading text-white text-2xl font-bold">Join Broadcast Beat</h1>
|
||||
<h1 className="font-heading text-white text-2xl font-bold">Join AV Beat</h1>
|
||||
<p className="text-[#888] font-body text-sm mt-1">
|
||||
One account for the forum, newsletter preferences, and saved articles.
|
||||
</p>
|
||||
@@ -148,7 +148,7 @@ function ForumRegisterInner() {
|
||||
className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4 shadow-2xl"
|
||||
>
|
||||
<div className="hidden lg:block mb-2">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#3b82f6] font-mono mb-1">Create your account</div>
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#E67E22] font-mono mb-1">Create your account</div>
|
||||
<div className="text-[#888] text-xs">Takes 30 seconds. Free, no card needed.</div>
|
||||
</div>
|
||||
{error && (
|
||||
@@ -171,7 +171,7 @@ function ForumRegisterInner() {
|
||||
onChange={(e) => setFullName(e.target.value)}
|
||||
autoComplete="name"
|
||||
required
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#E67E22]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -184,7 +184,7 @@ function ForumRegisterInner() {
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autoComplete="email"
|
||||
required
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#E67E22]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -198,7 +198,7 @@ function ForumRegisterInner() {
|
||||
autoComplete="new-password"
|
||||
required
|
||||
minLength={6}
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#E67E22]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -211,21 +211,21 @@ function ForumRegisterInner() {
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#E67E22]"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors"
|
||||
className="w-full bg-[#E67E22] hover:bg-[#C8651B] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? 'Creating account…' : 'Create Free Account'}
|
||||
</button>
|
||||
<p className="text-[10px] text-[#666] leading-relaxed text-center px-2">
|
||||
By creating an account you agree to our{' '}
|
||||
<Link href="/terms" className="text-[#888] hover:text-[#3b82f6] underline">terms</Link>
|
||||
<Link href="/terms" className="text-[#888] hover:text-[#E67E22] underline">terms</Link>
|
||||
{' '}and{' '}
|
||||
<Link href="/privacy" className="text-[#888] hover:text-[#3b82f6] underline">privacy policy</Link>.
|
||||
<Link href="/privacy" className="text-[#888] hover:text-[#E67E22] underline">privacy policy</Link>.
|
||||
We’ll never sell your email.
|
||||
</p>
|
||||
<SocialAuthButtons next={next} />
|
||||
@@ -233,7 +233,7 @@ function ForumRegisterInner() {
|
||||
Already have an account?{' '}
|
||||
<Link
|
||||
href={`/forum/login?next=${encodeURIComponent(next)}`}
|
||||
className="text-[#3b82f6] hover:underline font-semibold"
|
||||
className="text-[#E67E22] hover:underline font-semibold"
|
||||
>
|
||||
Sign in
|
||||
</Link>
|
||||
|
||||
@@ -67,8 +67,8 @@ function timeAgo(dateStr: string): string {
|
||||
function Avatar({ name, isAI }: { name: string; isAI: boolean }) {
|
||||
if (isAI) {
|
||||
return (
|
||||
<div className="w-9 h-9 rounded-full bg-[#1e3a5f] border border-[#3b82f6] flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-[#3b82f6] text-xs font-bold">AI</span>
|
||||
<div className="w-9 h-9 rounded-full bg-[#1e3a5f] border border-[#E67E22] flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-[#E67E22] text-xs font-bold">AI</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -133,7 +133,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
|
||||
}
|
||||
};
|
||||
|
||||
const scoreColor = counts.vote_score > 0 ? 'text-[#3b82f6]' : counts.vote_score < 0 ? 'text-red-400' : 'text-[#666]';
|
||||
const scoreColor = counts.vote_score > 0 ? 'text-[#E67E22]' : counts.vote_score < 0 ? 'text-red-400' : 'text-[#666]';
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 mt-2">
|
||||
@@ -144,8 +144,8 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
|
||||
title={!isAuthenticated ? 'Sign in to vote' : isAI ? 'Cannot vote on AI responses' : 'Upvote'}
|
||||
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-body font-semibold transition-colors border ${
|
||||
currentVote === 1
|
||||
? 'bg-[#3b82f6]/20 border-[#3b82f6] text-[#3b82f6]'
|
||||
: 'bg-transparent border-[#2a2a2a] text-[#666] hover:border-[#3b82f6] hover:text-[#3b82f6]'
|
||||
? 'bg-[#E67E22]/20 border-[#E67E22] text-[#E67E22]'
|
||||
: 'bg-transparent border-[#2a2a2a] text-[#666] hover:border-[#E67E22] hover:text-[#E67E22]'
|
||||
} disabled:opacity-40 disabled:cursor-not-allowed`}
|
||||
aria-label="Upvote"
|
||||
>
|
||||
@@ -178,7 +178,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
|
||||
</button>
|
||||
|
||||
{!isAuthenticated && (
|
||||
<Link href="/login" className="text-xs font-body text-[#555] hover:text-[#3b82f6] ml-1 transition-colors">
|
||||
<Link href="/login" className="text-xs font-body text-[#555] hover:text-[#E67E22] ml-1 transition-colors">
|
||||
Sign in to vote
|
||||
</Link>
|
||||
)}
|
||||
@@ -322,7 +322,7 @@ export default function ForumThreadPage() {
|
||||
<div className="bg-red-900/20 border border-red-800 rounded-lg p-4 text-red-400 font-body">
|
||||
{error || 'Thread not found.'}
|
||||
</div>
|
||||
<Link href="/forum" className="mt-4 inline-block text-[#3b82f6] hover:underline font-body text-sm">
|
||||
<Link href="/forum" className="mt-4 inline-block text-[#E67E22] hover:underline font-body text-sm">
|
||||
← Back to Forum
|
||||
</Link>
|
||||
</div>
|
||||
@@ -342,11 +342,11 @@ export default function ForumThreadPage() {
|
||||
<div className="bg-[#1a2535] border-b border-[#2a3a50]">
|
||||
<div className="max-w-container mx-auto px-4 py-4">
|
||||
<nav className="text-sm font-body text-[#666]">
|
||||
<Link href="/forum" className="hover:text-[#3b82f6] transition-colors">Forum</Link>
|
||||
<Link href="/forum" className="hover:text-[#E67E22] transition-colors">Forum</Link>
|
||||
<span className="mx-2">›</span>
|
||||
{category && (
|
||||
<>
|
||||
<Link href={`/forum/${category.slug}`} className="hover:text-[#3b82f6] transition-colors">{category.name}</Link>
|
||||
<Link href={`/forum/${category.slug}`} className="hover:text-[#E67E22] transition-colors">{category.name}</Link>
|
||||
<span className="mx-2">›</span>
|
||||
</>
|
||||
)}
|
||||
@@ -366,7 +366,7 @@ export default function ForumThreadPage() {
|
||||
<Avatar name={thread.author_name} isAI={false} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#3b82f6] hover:underline">{thread.author_name}</Link>
|
||||
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#E67E22] hover:underline">{thread.author_name}</Link>
|
||||
<span className="text-[#555] font-body text-xs">·</span>
|
||||
<span className="text-[#555] font-body text-xs">{timeAgo(thread.created_at)}</span>
|
||||
<span className="text-[#555] font-body text-xs">·</span>
|
||||
@@ -403,9 +403,9 @@ export default function ForumThreadPage() {
|
||||
<Avatar name={reply.author_name} isAI={reply.is_ai_response} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
||||
<Link href={`/forum/user/${encodeURIComponent(reply.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#3b82f6] hover:underline">{reply.author_name}</Link>
|
||||
<Link href={`/forum/user/${encodeURIComponent(reply.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#E67E22] hover:underline">{reply.author_name}</Link>
|
||||
{reply.is_ai_response && (
|
||||
<span className="text-xs bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/30 px-2 py-0.5 rounded font-body font-semibold">
|
||||
<span className="text-xs bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/30 px-2 py-0.5 rounded font-body font-semibold">
|
||||
AI Assistant
|
||||
</span>
|
||||
)}
|
||||
@@ -443,12 +443,12 @@ export default function ForumThreadPage() {
|
||||
{/* AI Assistant Panel */}
|
||||
<div className="bg-[#0d1f35] border border-[#1e3a5f] rounded-lg p-5 mb-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="w-7 h-7 rounded-full bg-[#1e3a5f] border border-[#3b82f6] flex items-center justify-center">
|
||||
<span className="text-[#3b82f6] text-xs font-bold">AI</span>
|
||||
<div className="w-7 h-7 rounded-full bg-[#1e3a5f] border border-[#E67E22] flex items-center justify-center">
|
||||
<span className="text-[#E67E22] text-xs font-bold">AI</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-white font-body font-semibold text-sm">Broadcast Beat AI Assistant</span>
|
||||
<span className="ml-2 text-xs bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/30 px-2 py-0.5 rounded font-body">AI — Not a human</span>
|
||||
<span className="text-white font-body font-semibold text-sm">AV Beat AI Assistant</span>
|
||||
<span className="ml-2 text-xs bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/30 px-2 py-0.5 rounded font-body">AI — Not a human</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[#7a9ab8] font-body text-xs mb-3">
|
||||
@@ -461,12 +461,12 @@ export default function ForumThreadPage() {
|
||||
value={aiQuestion}
|
||||
onChange={e => setAiQuestion(e.target.value)}
|
||||
disabled={aiLoading}
|
||||
className="flex-1 bg-[#111] border border-[#2a3a50] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#3a5a7a] focus:outline-none focus:border-[#3b82f6] disabled:opacity-50"
|
||||
className="flex-1 bg-[#111] border border-[#2a3a50] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#3a5a7a] focus:outline-none focus:border-[#E67E22] disabled:opacity-50"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={aiLoading || !aiQuestion.trim()}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors flex-shrink-0"
|
||||
className="bg-[#E67E22] hover:bg-[#C8651B] disabled:opacity-50 text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors flex-shrink-0"
|
||||
>
|
||||
{aiLoading ? (
|
||||
<span className="flex items-center gap-1">
|
||||
@@ -495,7 +495,7 @@ export default function ForumThreadPage() {
|
||||
onChange={e => setReplyBody(e.target.value)}
|
||||
required
|
||||
rows={5}
|
||||
className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#3b82f6] resize-none"
|
||||
className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#E67E22] resize-none"
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<ImageAttachButton onInsert={(md) => setReplyBody((b) => (b || "") + md)} />
|
||||
@@ -509,7 +509,7 @@ export default function ForumThreadPage() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting || !replyBody.trim()}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
|
||||
className="bg-[#E67E22] hover:bg-[#C8651B] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
|
||||
>
|
||||
{submitting ? 'Posting...' : 'Post Reply'}
|
||||
</button>
|
||||
@@ -526,13 +526,13 @@ export default function ForumThreadPage() {
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Link
|
||||
href={`/forum/login?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
|
||||
className="bg-[#E67E22] hover:bg-[#C8651B] text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
<Link
|
||||
href={`/forum/register?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`}
|
||||
className="border border-[#3b82f6] text-[#3b82f6] hover:bg-[#3b82f6]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
|
||||
className="border border-[#E67E22] text-[#E67E22] hover:bg-[#E67E22]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
|
||||
>
|
||||
Register
|
||||
</Link>
|
||||
|
||||
@@ -16,13 +16,13 @@ export default function GlobalError({
|
||||
<body style={{ background: "#0d0d0d", color: "#e0e0e0", fontFamily: "system-ui", padding: 24, minHeight: "100vh" }}>
|
||||
<div style={{ maxWidth: 480, margin: "10vh auto", textAlign: "center" }}>
|
||||
<h2 style={{ fontSize: 22, marginBottom: 12 }}>Something went wrong</h2>
|
||||
<p style={{ marginBottom: 12, color: "#888" }}>Broadcast Beat hit an unexpected error.</p>
|
||||
<p style={{ marginBottom: 12, color: "#888" }}>AV Beat hit an unexpected error.</p>
|
||||
{error?.digest && (
|
||||
<p style={{ fontSize: 12, opacity: 0.5, marginBottom: 16, fontFamily: "monospace" }}>ref: {error.digest}</p>
|
||||
)}
|
||||
<button
|
||||
onClick={() => reset()}
|
||||
style={{ padding: "8px 16px", background: "#3b82f6", color: "white", border: 0, cursor: "pointer", borderRadius: 2 }}
|
||||
style={{ padding: "8px 16px", background: "#E67E22", color: "white", border: 0, cursor: "pointer", borderRadius: 2 }}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
|
||||
@@ -314,7 +314,7 @@ export default function ArticleFeed() {
|
||||
value={sortOrder}
|
||||
onChange={(e) => { setSortOrder(e.target.value as SortOrder); setPage(1); setInfiniteCount(ARTICLES_PER_PAGE); }}
|
||||
aria-label="Sort articles"
|
||||
className="bg-[#111] border border-[#2a2a2a] text-[#aaa] font-body text-[11px] font-semibold uppercase tracking-wide rounded-sm pl-2.5 pr-7 py-1 appearance-none focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]"
|
||||
className="bg-[#111] border border-[#2a2a2a] text-[#aaa] font-body text-[11px] font-semibold uppercase tracking-wide rounded-sm pl-2.5 pr-7 py-1 appearance-none focus:outline-none focus:border-[#E67E22] focus:ring-1 focus:ring-[#E67E22]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]"
|
||||
>
|
||||
<option value="recent">Recent</option>
|
||||
<option value="trending">Trending</option>
|
||||
@@ -333,8 +333,8 @@ export default function ArticleFeed() {
|
||||
onClick={() => handleFeedModeChange("pagination")}
|
||||
aria-pressed={feedMode === "pagination"}
|
||||
title="Pagination mode"
|
||||
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${
|
||||
feedMode === "pagination" ?"bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
|
||||
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${
|
||||
feedMode === "pagination" ?"bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
|
||||
}`}>
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="1" width="10" height="2.5" rx="0.5" fill="currentColor" opacity="0.9"/>
|
||||
@@ -347,8 +347,8 @@ export default function ArticleFeed() {
|
||||
onClick={() => handleFeedModeChange("infinite")}
|
||||
aria-pressed={feedMode === "infinite"}
|
||||
title="Infinite scroll mode"
|
||||
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${
|
||||
feedMode === "infinite" ?"bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
|
||||
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${
|
||||
feedMode === "infinite" ?"bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
|
||||
}`}>
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="1" width="10" height="2" rx="0.5" fill="currentColor" opacity="0.9"/>
|
||||
@@ -372,7 +372,7 @@ export default function ArticleFeed() {
|
||||
value={dateFrom}
|
||||
onChange={(e) => { setDateFrom(e.target.value); setPage(1); }}
|
||||
max={dateTo || undefined}
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#3b82f6] transition-colors [color-scheme:dark]"
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#E67E22] transition-colors [color-scheme:dark]"
|
||||
title="From date"
|
||||
aria-label="Filter articles from date"
|
||||
/>
|
||||
@@ -382,7 +382,7 @@ export default function ArticleFeed() {
|
||||
value={dateTo}
|
||||
onChange={(e) => { setDateTo(e.target.value); setPage(1); }}
|
||||
min={dateFrom || undefined}
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#3b82f6] transition-colors [color-scheme:dark]"
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#E67E22] transition-colors [color-scheme:dark]"
|
||||
title="To date"
|
||||
aria-label="Filter articles to date"
|
||||
/>
|
||||
@@ -401,9 +401,9 @@ export default function ArticleFeed() {
|
||||
onClick={() => setShowAdvanced((v) => !v)}
|
||||
aria-expanded={showAdvanced}
|
||||
aria-controls={`${feedId}-advanced`}
|
||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-sm border font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] whitespace-nowrap ${
|
||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-sm border font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] whitespace-nowrap ${
|
||||
showAdvanced || hasAdvancedActive
|
||||
? "bg-[#3b82f6]/15 text-[#3b82f6] border-[#3b82f6]/40"
|
||||
? "bg-[#E67E22]/15 text-[#E67E22] border-[#E67E22]/40"
|
||||
: "bg-[#111] text-[#666] border-[#2a2a2a] hover:text-[#aaa] hover:border-[#3a3a3a]"
|
||||
}`}>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
@@ -411,7 +411,7 @@ export default function ArticleFeed() {
|
||||
</svg>
|
||||
More
|
||||
{hasAdvancedActive && (
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] inline-block ml-0.5" aria-label="Active filters" />
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#E67E22] inline-block ml-0.5" aria-label="Active filters" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -428,9 +428,9 @@ export default function ArticleFeed() {
|
||||
type="button"
|
||||
onClick={() => { setActiveCategory(cat); setPage(1); }}
|
||||
aria-pressed={isActive}
|
||||
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${
|
||||
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${
|
||||
isActive
|
||||
? "bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50"
|
||||
? "bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/50"
|
||||
: "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc] hover:border-[#3a3a3a]"
|
||||
}`}>
|
||||
{cat}
|
||||
@@ -458,7 +458,7 @@ export default function ArticleFeed() {
|
||||
id={`${feedId}-author`}
|
||||
value={authorFilter}
|
||||
onChange={(e) => { setAuthorFilter(e.target.value); setPage(1); }}
|
||||
className="w-full bg-[#111] border border-[#2a2a2a] text-[#ccc] font-body text-sm rounded-sm px-3 py-1.5 pr-8 appearance-none focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]/30 transition-colors [color-scheme:dark]"
|
||||
className="w-full bg-[#111] border border-[#2a2a2a] text-[#ccc] font-body text-sm rounded-sm px-3 py-1.5 pr-8 appearance-none focus:outline-none focus:border-[#E67E22] focus:ring-1 focus:ring-[#E67E22]/30 transition-colors [color-scheme:dark]"
|
||||
aria-label="Filter by author">
|
||||
<option value="All">All Authors</option>
|
||||
{uniqueAuthors.map((author) => (
|
||||
@@ -483,10 +483,10 @@ export default function ArticleFeed() {
|
||||
key={sf}
|
||||
onClick={() => { setSourceFilter(sf); setPage(1); }}
|
||||
aria-pressed={isActive}
|
||||
className={`px-3 py-1 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${
|
||||
className={`px-3 py-1 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${
|
||||
isActive
|
||||
? sf === "Imported" ?"bg-amber-500/20 text-amber-400 border border-amber-500/40"
|
||||
: sf === "Live" ?"bg-emerald-500/20 text-emerald-400 border border-emerald-500/40" :"bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
|
||||
: sf === "Live" ?"bg-emerald-500/20 text-emerald-400 border border-emerald-500/40" :"bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
|
||||
}`}>
|
||||
{sf}
|
||||
{sf === "Imported" && importedCount > 0 && (
|
||||
@@ -507,7 +507,7 @@ export default function ArticleFeed() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setAuthorFilter("All"); setDateFrom(""); setDateTo(""); setPage(1); }}
|
||||
className="font-body text-[11px] text-[#3b82f6] hover:underline focus:outline-none focus-visible:underline">
|
||||
className="font-body text-[11px] text-[#E67E22] hover:underline focus:outline-none focus-visible:underline">
|
||||
Clear advanced filters
|
||||
</button>
|
||||
</div>
|
||||
@@ -537,7 +537,7 @@ export default function ArticleFeed() {
|
||||
{sortedFiltered.length === 0 && (
|
||||
<button
|
||||
onClick={clearAllFilters}
|
||||
className="ml-1 text-[#3b82f6] hover:underline focus:outline-none focus-visible:underline">
|
||||
className="ml-1 text-[#E67E22] hover:underline focus:outline-none focus-visible:underline">
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
@@ -557,7 +557,7 @@ export default function ArticleFeed() {
|
||||
</p>
|
||||
<button
|
||||
onClick={clearAllFilters}
|
||||
className="mt-4 font-body text-sm text-[#3b82f6] hover:underline focus:outline-none focus-visible:underline">
|
||||
className="mt-4 font-body text-sm text-[#E67E22] hover:underline focus:outline-none focus-visible:underline">
|
||||
Clear filters
|
||||
</button>
|
||||
</div>
|
||||
@@ -585,7 +585,7 @@ export default function ArticleFeed() {
|
||||
const promotedClass = promoted === 'premium'
|
||||
? 'border-l-4 border-amber-400 bg-gradient-to-r from-amber-500/10 to-transparent pl-3 -ml-2 mr-0'
|
||||
: promoted === 'client'
|
||||
? 'border-l-4 border-[#3b82f6] bg-gradient-to-r from-[#3b82f6]/8 to-transparent pl-3 -ml-2 mr-0'
|
||||
? 'border-l-4 border-[#E67E22] bg-gradient-to-r from-[#E67E22]/8 to-transparent pl-3 -ml-2 mr-0'
|
||||
: '';
|
||||
return (
|
||||
<Fragment key={`feed-${i}`}>
|
||||
@@ -593,7 +593,7 @@ export default function ArticleFeed() {
|
||||
<Link
|
||||
href={articleHref}
|
||||
{...linkProps}
|
||||
className="flex-shrink-0 img-zoom overflow-hidden w-[100px] h-[70px] sm:w-[140px] sm:h-[95px] md:w-[190px] md:h-[128px] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"
|
||||
className="flex-shrink-0 img-zoom overflow-hidden w-[100px] h-[70px] sm:w-[140px] sm:h-[95px] md:w-[190px] md:h-[128px] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]"
|
||||
tabIndex={-1}
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -614,21 +614,21 @@ export default function ArticleFeed() {
|
||||
</span>
|
||||
)}
|
||||
{promoted === 'client' && (
|
||||
<span className="font-body text-[9px] font-bold text-[#7aa7d4] bg-[#3b82f6]/15 border border-[#3b82f6]/40 px-1.5 py-0.5 rounded uppercase tracking-widest" title={article?.promoted_advertiser ? `Advertiser coverage: ${article.promoted_advertiser}` : 'Advertiser coverage'}>
|
||||
<span className="font-body text-[9px] font-bold text-[#7aa7d4] bg-[#E67E22]/15 border border-[#E67E22]/40 px-1.5 py-0.5 rounded uppercase tracking-widest" title={article?.promoted_advertiser ? `Advertiser coverage: ${article.promoted_advertiser}` : 'Advertiser coverage'}>
|
||||
◆ Advertiser
|
||||
</span>
|
||||
)}
|
||||
{article?.category && article.category.toLowerCase() !== "legacy" && (
|
||||
<span className="font-body text-[10px] font-bold text-[#3b82f6] uppercase tracking-wide">
|
||||
<span className="font-body text-[10px] font-bold text-[#E67E22] uppercase tracking-wide">
|
||||
{article.category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="font-heading text-[#e0e0e0] font-bold text-[0.875rem] md:text-[1rem] leading-snug mb-1 md:mb-1.5 group-hover:text-[#3b82f6] transition-colors duration-200 line-clamp-2">
|
||||
<h3 className="font-heading text-[#e0e0e0] font-bold text-[0.875rem] md:text-[1rem] leading-snug mb-1 md:mb-1.5 group-hover:text-[#E67E22] transition-colors duration-200 line-clamp-2">
|
||||
<Link
|
||||
href={articleHref}
|
||||
{...linkProps}
|
||||
className="focus:outline-none focus-visible:text-[#3b82f6] focus-visible:underline">
|
||||
className="focus:outline-none focus-visible:text-[#E67E22] focus-visible:underline">
|
||||
{article?.title}
|
||||
</Link>
|
||||
</h3>
|
||||
@@ -664,7 +664,7 @@ export default function ArticleFeed() {
|
||||
|
||||
{/* Inline social share row */}
|
||||
{(() => {
|
||||
const shareUrl = `https://broadcastbeat.com${articleHref.startsWith('/') ? articleHref : `/articles/${article?.slug}`}`;
|
||||
const shareUrl = `https://avbeat.com${articleHref.startsWith('/') ? articleHref : `/articles/${article?.slug}`}`;
|
||||
const shareTitle = article?.title || '';
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 mt-2 pt-2 border-t border-[#1a1a1a]">
|
||||
@@ -675,7 +675,7 @@ export default function ArticleFeed() {
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#0a66c2] hover:bg-[#1a1a1a] transition-colors">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 0h-14C2.24 0 0 2.24 0 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5V5c0-2.76-2.24-5-5-5zM8 19H5V8h3v11zM6.5 6.73a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5zM20 19h-3v-5.6c0-3.37-4-3.12-4 0V19h-3V8h3v1.77c1.4-2.59 7-2.78 7 2.48V19z"/></svg>
|
||||
</a>
|
||||
<a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}&via=BroadcastBeat`}
|
||||
<a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}&via=AVBeat`}
|
||||
target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-white hover:bg-[#1a1a1a] transition-colors">
|
||||
@@ -702,7 +702,7 @@ export default function ArticleFeed() {
|
||||
<a href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`}
|
||||
aria-label="Share via email" title="Email"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#3b82f6] hover:bg-[#1a1a1a] transition-colors">
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#E67E22] hover:bg-[#1a1a1a] transition-colors">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||
</svg>
|
||||
@@ -735,9 +735,9 @@ export default function ArticleFeed() {
|
||||
{isLoadingMore && (
|
||||
<div className="flex items-center justify-center gap-2 py-6" aria-live="polite" aria-label="Loading more articles">
|
||||
<div className="flex gap-1">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] animate-bounce" style={{ animationDelay: "0ms" }} />
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] animate-bounce" style={{ animationDelay: "150ms" }} />
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] animate-bounce" style={{ animationDelay: "300ms" }} />
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#E67E22] animate-bounce" style={{ animationDelay: "0ms" }} />
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#E67E22] animate-bounce" style={{ animationDelay: "150ms" }} />
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#E67E22] animate-bounce" style={{ animationDelay: "300ms" }} />
|
||||
</div>
|
||||
<span className="font-body text-xs text-[#555]">Loading more articles…</span>
|
||||
</div>
|
||||
@@ -762,7 +762,7 @@ export default function ArticleFeed() {
|
||||
</p>
|
||||
<div className="flex items-center gap-1" role="navigation" aria-label="Pagination">
|
||||
<button
|
||||
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"
|
||||
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]"
|
||||
disabled={page === 1}
|
||||
onClick={() => { setPage(Math.max(1, page - 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
|
||||
aria-label="Previous page">
|
||||
@@ -786,7 +786,7 @@ export default function ArticleFeed() {
|
||||
) : (
|
||||
<button
|
||||
key={p}
|
||||
className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${page === p ? "active" : ""}`}
|
||||
className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${page === p ? "active" : ""}`}
|
||||
onClick={() => { setPage(p as number); window.scrollTo({ top: 0, behavior: "smooth" }); }}
|
||||
aria-label={`Page ${p}`}
|
||||
aria-current={page === p ? "page" : undefined}>
|
||||
@@ -796,7 +796,7 @@ export default function ArticleFeed() {
|
||||
);
|
||||
})()}
|
||||
<button
|
||||
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"
|
||||
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]"
|
||||
disabled={page === totalPages}
|
||||
onClick={() => { setPage(Math.min(totalPages, page + 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
|
||||
aria-label="Next page">
|
||||
|
||||
@@ -13,14 +13,14 @@ import SponsorLogoStrip from "@/components/SponsorLogoStrip";
|
||||
import CompanyMentionsHover from "@/components/CompanyMentionsHover";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Broadcast Beat — Broadcast Engineering News & Insights',
|
||||
description: 'The digital platform for broadcast engineering professionals. Breaking news, deep-dive features, and industry spotlights from NAB to IBC and beyond.',
|
||||
title: 'AV Beat — Where AV Meets IT',
|
||||
description: 'AV Beat covers the convergence of pro AV and IT — broadcast, live production, streaming, and post-production technology. Breaking news, deep-dive features, and industry spotlights from NAB to InfoComm and beyond.',
|
||||
alternates: {
|
||||
canonical: '/',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Broadcast Beat — Broadcast News',
|
||||
description: 'Breaking news and insights for broadcast engineering professionals.',
|
||||
title: 'AV Beat — Where AV Meets IT',
|
||||
description: 'Breaking news and insights at the intersection of pro AV and IT.',
|
||||
url: '/',
|
||||
type: 'website',
|
||||
images: [
|
||||
@@ -28,14 +28,14 @@ export const metadata: Metadata = {
|
||||
url: '/assets/images/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Broadcast Beat — Broadcast Engineering News & Insights',
|
||||
alt: 'AV Beat — Where AV Meets IT',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Broadcast Beat — Broadcast News',
|
||||
description: 'Breaking news and insights for broadcast engineering professionals.',
|
||||
title: 'AV Beat — Where AV Meets IT',
|
||||
description: 'Breaking news and insights at the intersection of pro AV and IT.',
|
||||
images: ['/assets/images/og-image.png'],
|
||||
},
|
||||
};
|
||||
@@ -138,7 +138,7 @@ export default function HomePage() {
|
||||
|
||||
{/* H1 Hero Heading */}
|
||||
<div className="sr-only">
|
||||
<h1>Broadcast Beat — Broadcast Engineering News & Insights</h1>
|
||||
<h1>AV Beat — Where AV Meets IT</h1>
|
||||
</div>
|
||||
|
||||
{/* Featured: cinematic hero + 4-up rail below (FeaturedBentoFromDb) */}
|
||||
|
||||
@@ -34,8 +34,8 @@ export const viewport: Viewport = {
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'),
|
||||
title: 'Broadcast Beat — Broadcast Engineering News & Insights',
|
||||
description: 'The digital platform for broadcast engineering professionals. Breaking news, deep-dive features, and industry spotlights from NAB to IBC and beyond.',
|
||||
title: 'AV Beat — Where AV Meets IT',
|
||||
description: 'AV Beat covers the convergence of pro AV and IT — broadcast, live production, streaming, and post-production technology. Breaking news, deep-dive features, and industry spotlights from NAB to InfoComm and beyond.',
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: '/favicon.ico', type: 'image/x-icon', sizes: 'any' },
|
||||
@@ -58,25 +58,25 @@ export const metadata: Metadata = {
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
url: '/',
|
||||
siteName: 'Broadcast Beat',
|
||||
title: 'Broadcast Beat — Broadcast News',
|
||||
description: 'Breaking news and insights for broadcast engineering professionals.',
|
||||
siteName: 'AV Beat',
|
||||
title: 'AV Beat — Where AV Meets IT',
|
||||
description: 'Breaking news and insights at the intersection of pro AV and IT.',
|
||||
images: [
|
||||
{
|
||||
url: '/assets/images/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Broadcast Beat — Broadcast Engineering News & Insights',
|
||||
alt: 'AV Beat — Where AV Meets IT',
|
||||
type: 'image/png'
|
||||
}]
|
||||
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
site: '@Broadcast Beat',
|
||||
creator: '@Broadcast Beat',
|
||||
title: 'Broadcast Beat — Broadcast News',
|
||||
description: 'Breaking news and insights for broadcast engineering professionals.',
|
||||
site: '@AVBeat',
|
||||
creator: '@AVBeat',
|
||||
title: 'AV Beat — Where AV Meets IT',
|
||||
description: 'Breaking news and insights at the intersection of pro AV and IT.',
|
||||
images: ['/assets/images/og-image.png']
|
||||
},
|
||||
robots: {
|
||||
@@ -122,10 +122,10 @@ export default function RootLayout({
|
||||
__html: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: 'Broadcast Beat',
|
||||
url: 'https://broadcastbeat.com',
|
||||
logo: "https://broadcastbeat.com/assets/images/logo.png",
|
||||
description: 'The digital platform for broadcast engineering professionals. Breaking news, deep-dive features, and industry spotlights from NAB to IBC and beyond.',
|
||||
name: 'AV Beat',
|
||||
url: 'https://avbeat.com',
|
||||
logo: "https://avbeat.com/assets/images/logo.png",
|
||||
description: 'AV Beat covers the convergence of pro AV and IT — broadcast, live production, streaming, and post-production technology.',
|
||||
sameAs: [
|
||||
'https://linkedin.com',
|
||||
'https://twitter.com',
|
||||
@@ -135,7 +135,7 @@ export default function RootLayout({
|
||||
contactPoint: {
|
||||
'@type': 'ContactPoint',
|
||||
contactType: 'Editorial',
|
||||
url: 'https://broadcastbeat.com'
|
||||
url: 'https://avbeat.com'
|
||||
}
|
||||
})
|
||||
}} />
|
||||
@@ -144,7 +144,7 @@ export default function RootLayout({
|
||||
{/* Skip to main content — WCAG 2.4.1 */}
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[10000] focus:px-4 focus:py-2 focus:bg-[#3b82f6] focus:text-white focus:rounded focus:font-bold focus:text-sm focus:outline-none"
|
||||
className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[10000] focus:px-4 focus:py-2 focus:bg-[#E67E22] focus:text-white focus:rounded focus:font-bold focus:text-sm focus:outline-none"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Header from '@/components/Header';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Billing | Broadcast Beat Marketplace',
|
||||
title: 'Billing | AV Beat Marketplace',
|
||||
};
|
||||
|
||||
export default function BillingPage() {
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function GigsPage() {
|
||||
<div className="text-center py-20 text-gray-500">
|
||||
<div className="text-4xl mb-4">🎬</div>
|
||||
<p className="text-lg font-semibold mb-2">No gigs posted yet</p>
|
||||
<p className="text-sm">Be the first to post a crew call on Broadcast Beat Marketplace.</p>
|
||||
<p className="text-sm">Be the first to post a crew call on AV Beat Marketplace.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function JobsPage() {
|
||||
<div className="text-center py-20 text-gray-500">
|
||||
<div className="text-4xl mb-4">📋</div>
|
||||
<p className="text-lg font-semibold mb-2">No jobs posted yet</p>
|
||||
<p className="text-sm">Be the first to post a job on Broadcast Beat Marketplace.</p>
|
||||
<p className="text-sm">Be the first to post a job on AV Beat Marketplace.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
|
||||
@@ -3,7 +3,7 @@ import Header from '@/components/Header';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Production Crew & Vendor Marketplace | Broadcast Beat',
|
||||
title: 'Production Crew & Vendor Marketplace | AV Beat',
|
||||
description:
|
||||
'Find broadcast and production crew, vendors, jobs, and gig opportunities. The professional marketplace built for the broadcast industry.',
|
||||
};
|
||||
|
||||
@@ -9,16 +9,16 @@ import CompanyMentionsHover from "@/components/CompanyMentionsHover";
|
||||
export const revalidate = 600;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "2026 NAB Show — Live Coverage Hub | Broadcast Beat",
|
||||
title: "2026 NAB Show — Live Coverage Hub | AV Beat",
|
||||
description:
|
||||
"Broadcast Beat's live coverage of 2026 NAB Show: 1,100+ exhibitors, the latest product launches, booth-by-booth news, and editorial pick stories from the show floor.",
|
||||
"AV Beat's live coverage of 2026 NAB Show: 1,100+ exhibitors, the latest product launches, booth-by-booth news, and editorial pick stories from the show floor.",
|
||||
alternates: { canonical: "/nab-2026" },
|
||||
openGraph: {
|
||||
title: "2026 NAB Show — Live Coverage Hub",
|
||||
description:
|
||||
"Live coverage, exhibitor list, and breaking product news from 2026 NAB Show.",
|
||||
type: "website",
|
||||
url: "https://broadcastbeat.com/nab-2026",
|
||||
url: "https://avbeat.com/nab-2026",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ export default async function NabHub() {
|
||||
</h1>
|
||||
<p className="text-[#aaa] text-base max-w-3xl">
|
||||
{exhibitors.length.toLocaleString()} exhibitors confirmed.
|
||||
{sponsorRows.length > 0 && <> {sponsorRows.length} are Broadcast Beat content partners.</>}
|
||||
{sponsorRows.length > 0 && <> {sponsorRows.length} are AV Beat content partners.</>}
|
||||
{" "}Browse the directory, read the latest product news, and find every booth.
|
||||
</p>
|
||||
</div>
|
||||
@@ -153,7 +153,7 @@ export default async function NabHub() {
|
||||
<h2 className="text-xl font-display font-bold text-[#e0e0e0]">
|
||||
Latest from the show floor
|
||||
</h2>
|
||||
<Link href="/news?search=NAB+2026" className="text-xs text-[#3b82f6] hover:underline">
|
||||
<Link href="/news?search=NAB+2026" className="text-xs text-[#E67E22] hover:underline">
|
||||
See all NAB coverage →
|
||||
</Link>
|
||||
</header>
|
||||
@@ -162,7 +162,7 @@ export default async function NabHub() {
|
||||
<Link
|
||||
key={a.wp_slug}
|
||||
href={`/news/${a.wp_slug}`}
|
||||
className="group bg-[#0d0d0d] border border-[#252525] rounded-lg overflow-hidden hover:border-[#3b82f6] hover:shadow-sm transition-all"
|
||||
className="group bg-[#0d0d0d] border border-[#252525] rounded-lg overflow-hidden hover:border-[#E67E22] hover:shadow-sm transition-all"
|
||||
>
|
||||
{a.featured_image && (
|
||||
/* eslint-disable-next-line @next/next/no-img-element */
|
||||
@@ -174,7 +174,7 @@ export default async function NabHub() {
|
||||
/>
|
||||
)}
|
||||
<div className="p-3">
|
||||
<h3 className="font-semibold text-[#e0e0e0] text-sm leading-snug group-hover:text-[#3b82f6] line-clamp-3 mb-1">
|
||||
<h3 className="font-semibold text-[#e0e0e0] text-sm leading-snug group-hover:text-[#E67E22] line-clamp-3 mb-1">
|
||||
{a.title}
|
||||
</h3>
|
||||
{a.wp_published_at && (
|
||||
@@ -195,7 +195,7 @@ export default async function NabHub() {
|
||||
<h2 className="text-xl font-display font-bold text-[#e0e0e0]">
|
||||
All 2026 NAB Show exhibitors
|
||||
</h2>
|
||||
<Link href="/manufacturers" className="text-xs text-[#3b82f6] hover:underline">
|
||||
<Link href="/manufacturers" className="text-xs text-[#E67E22] hover:underline">
|
||||
Full directory →
|
||||
</Link>
|
||||
</header>
|
||||
@@ -205,7 +205,7 @@ export default async function NabHub() {
|
||||
key={e.slug}
|
||||
href={`/manufacturers/${e.slug}`}
|
||||
data-company-slug={e.slug}
|
||||
className="bg-[#0d0d0d] border border-[#252525] rounded-md p-2.5 hover:border-[#3b82f6] transition-all flex items-center gap-2.5"
|
||||
className="bg-[#0d0d0d] border border-[#252525] rounded-md p-2.5 hover:border-[#E67E22] transition-all flex items-center gap-2.5"
|
||||
>
|
||||
{e.logo_url ? (
|
||||
/* eslint-disable-next-line @next/next/no-img-element */
|
||||
@@ -222,7 +222,7 @@ export default async function NabHub() {
|
||||
{otherRows.length > 200 && (
|
||||
<p className="text-center text-xs text-[#888] mt-4">
|
||||
Showing 200 of {otherRows.length.toLocaleString()} exhibitors.{" "}
|
||||
<Link href="/manufacturers" className="text-[#3b82f6] hover:underline">Browse the full directory →</Link>
|
||||
<Link href="/manufacturers" className="text-[#E67E22] hover:underline">Browse the full directory →</Link>
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -6,9 +6,9 @@ export const revalidate = 600; // 10 min — Google rechecks frequently
|
||||
// Google News sitemap. Only original-editorial articles (ai_rewritten_at IS
|
||||
// NOT NULL or rewritten-articles surface) from the last 48 hours so we
|
||||
// stay inside the GN window. Standard sitemap.xml covers older pages.
|
||||
const PUBLICATION_NAME = 'Broadcast Beat';
|
||||
const PUBLICATION_NAME = 'AV Beat';
|
||||
const PUBLICATION_LANG = 'en';
|
||||
const HOST = 'https://broadcastbeat.com';
|
||||
const HOST = 'https://avbeat.com';
|
||||
|
||||
function esc(s: string): string {
|
||||
return (s || '').replace(/[&<>"']/g, (c) =>
|
||||
|
||||
@@ -175,7 +175,7 @@ export default function NewsPage({
|
||||
placeholder="Search articles, topics, authors…"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full bg-[#1a1a1a] border border-[#2a2a2a] text-[#e0e0e0] placeholder-[#555] text-sm font-body pl-9 pr-4 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] transition-colors"
|
||||
className="w-full bg-[#1a1a1a] border border-[#2a2a2a] text-[#e0e0e0] placeholder-[#555] text-sm font-body pl-9 pr-4 py-2.5 rounded-sm focus:outline-none focus:border-[#E67E22] transition-colors"
|
||||
/>
|
||||
{search && (
|
||||
<button
|
||||
@@ -196,7 +196,7 @@ export default function NewsPage({
|
||||
type="date"
|
||||
value={dateFrom}
|
||||
onChange={(e) => setDateFrom(e.target.value)}
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] transition-colors [color-scheme:dark]"
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#E67E22] transition-colors [color-scheme:dark]"
|
||||
title="From date"
|
||||
/>
|
||||
<span className="text-[#444] text-xs">–</span>
|
||||
@@ -204,7 +204,7 @@ export default function NewsPage({
|
||||
type="date"
|
||||
value={dateTo}
|
||||
onChange={(e) => setDateTo(e.target.value)}
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] transition-colors [color-scheme:dark]"
|
||||
className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#E67E22] transition-colors [color-scheme:dark]"
|
||||
title="To date"
|
||||
/>
|
||||
</div>
|
||||
@@ -214,7 +214,7 @@ export default function NewsPage({
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
className="appearance-none bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body pl-3 pr-8 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] transition-colors cursor-pointer"
|
||||
className="appearance-none bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body pl-3 pr-8 py-2.5 rounded-sm focus:outline-none focus:border-[#E67E22] transition-colors cursor-pointer"
|
||||
>
|
||||
{SORT_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
@@ -236,8 +236,8 @@ export default function NewsPage({
|
||||
onClick={() => setActiveCategory(cat)}
|
||||
className={`px-2.5 py-1 text-[11px] font-body rounded-sm border transition-colors ${
|
||||
activeCategory === cat
|
||||
? "bg-[#3b82f6] border-[#3b82f6] text-white"
|
||||
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]"
|
||||
? "bg-[#E67E22] border-[#E67E22] text-white"
|
||||
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#E67E22] hover:text-[#E67E22]"
|
||||
}`}
|
||||
>
|
||||
{cat}
|
||||
@@ -275,7 +275,7 @@ export default function NewsPage({
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<p className="text-[#555] font-body text-sm">No articles match your filters.</p>
|
||||
<button onClick={clearFilters} className="mt-3 text-[#3b82f6] text-xs font-body hover:underline">Clear filters</button>
|
||||
<button onClick={clearFilters} className="mt-3 text-[#E67E22] text-xs font-body hover:underline">Clear filters</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-0">
|
||||
@@ -289,7 +289,7 @@ export default function NewsPage({
|
||||
<div className="flex-shrink-0 w-[160px] h-[105px] relative overflow-hidden rounded-sm">
|
||||
<AppImage
|
||||
src={article.image || "/assets/images/article-placeholder.jpg"}
|
||||
alt={article.alt || article.title || "Broadcast Beat article"}
|
||||
alt={article.alt || article.title || "AV Beat article"}
|
||||
fill
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
sizes="160px"
|
||||
@@ -297,14 +297,14 @@ export default function NewsPage({
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">{article.category || "NEWS"}</span>
|
||||
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider">{article.category || "NEWS"}</span>
|
||||
</div>
|
||||
<h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#3b82f6] transition-colors line-clamp-2">
|
||||
<h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#E67E22] transition-colors line-clamp-2">
|
||||
{article.title || "Untitled"}
|
||||
</h2>
|
||||
<p className="text-[#777] font-body text-sm leading-relaxed line-clamp-2">{article.excerpt || ""}</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<span className="text-[#555] font-body text-xs">By {article.author || "Broadcast Beat"}</span>
|
||||
<span className="text-[#555] font-body text-xs">By {article.author || "AV Beat"}</span>
|
||||
<span className="text-[#444] text-[10px]">·</span>
|
||||
<span className="text-[#555] font-body text-xs">{article.readTime || ""}</span>
|
||||
</div>
|
||||
@@ -326,7 +326,7 @@ export default function NewsPage({
|
||||
{!hideAiSuggestions && <AISuggestedArticles variant="full" />}
|
||||
|
||||
<div className="bg-[#111] border border-[#222] p-4">
|
||||
<h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-3 pb-2 border-b border-[#222]">
|
||||
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-3 pb-2 border-b border-[#222]">
|
||||
Popular Tags
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -336,8 +336,8 @@ export default function NewsPage({
|
||||
onClick={() => setActiveCategory(tag)}
|
||||
className={`px-2 py-1 border text-xs font-body rounded-sm transition-colors ${
|
||||
activeCategory === tag
|
||||
? "bg-[#3b82f6] border-[#3b82f6] text-white"
|
||||
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]"
|
||||
? "bg-[#E67E22] border-[#E67E22] text-white"
|
||||
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#E67E22] hover:text-[#E67E22]"
|
||||
}`}
|
||||
>
|
||||
{tag}
|
||||
@@ -346,7 +346,7 @@ export default function NewsPage({
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#0d1520] border border-[#1e3a5f] p-4">
|
||||
<h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-2">Newsletter</h3>
|
||||
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-2">Newsletter</h3>
|
||||
<p className="text-[#777] text-xs font-body mb-3">Get broadcast news delivered to your inbox every week.</p>
|
||||
<Link href="/home-page#newsletter" className="btn-subscribe text-xs py-2 px-4 inline-block">Subscribe Free</Link>
|
||||
</div>
|
||||
|
||||
@@ -233,7 +233,7 @@ export default function NewsArticleDetailClient({
|
||||
setEmailSending(true);
|
||||
setEmailStatus("idle");
|
||||
try {
|
||||
const articleUrl = `${process.env.NEXT_PUBLIC_SITE_URL || "https://broadcastbeat.com"}/news/${article.slug}`;
|
||||
const articleUrl = `${process.env.NEXT_PUBLIC_SITE_URL || "https://avbeat.com"}/news/${article.slug}`;
|
||||
const res = await fetch("/api/news/share-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -269,7 +269,7 @@ export default function NewsArticleDetailClient({
|
||||
const articleUrl =
|
||||
typeof window !== "undefined"
|
||||
? window.location.href
|
||||
: `https://broadcastbeat.com/news/${article.slug}`;
|
||||
: `https://avbeat.com/news/${article.slug}`;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@@ -278,11 +278,11 @@ export default function NewsArticleDetailClient({
|
||||
{/* Breadcrumb */}
|
||||
<div className="bg-[#0d0d0d] border-b border-[#1a1a1a]">
|
||||
<div className="max-w-container mx-auto px-4 py-2.5 flex items-center gap-2 text-xs font-body text-[#555]">
|
||||
<Link href="/home-page" className="hover:text-[#3b82f6] transition-colors">
|
||||
<Link href="/home-page" className="hover:text-[#E67E22] transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<Link href="/news" className="hover:text-[#3b82f6] transition-colors">
|
||||
<Link href="/news" className="hover:text-[#E67E22] transition-colors">
|
||||
News
|
||||
</Link>
|
||||
<span>/</span>
|
||||
@@ -300,7 +300,7 @@ export default function NewsArticleDetailClient({
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Link
|
||||
href="/news"
|
||||
className="text-xs font-bold uppercase tracking-widest text-[#3b82f6] hover:underline">
|
||||
className="text-xs font-bold uppercase tracking-widest text-[#E67E22] hover:underline">
|
||||
{article.category}
|
||||
</Link>
|
||||
<span className="text-xs text-[#777]">{article.readTime}</span>
|
||||
@@ -329,7 +329,7 @@ export default function NewsArticleDetailClient({
|
||||
<div>
|
||||
<Link
|
||||
href={`/authors/${article.authorSlug}`}
|
||||
className="font-heading font-bold text-sm hover:text-[#3b82f6] transition-colors text-[#e0e0e0]">
|
||||
className="font-heading font-bold text-sm hover:text-[#E67E22] transition-colors text-[#e0e0e0]">
|
||||
{article.author}
|
||||
</Link>
|
||||
<p className="font-body text-xs text-[#777]">{article.authorTitle}</p>
|
||||
@@ -343,7 +343,7 @@ export default function NewsArticleDetailClient({
|
||||
onClick={() => setEmailShareOpen(true)}
|
||||
aria-label="Share via email"
|
||||
title="Email"
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#3b82f6] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#E67E22] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||
@@ -364,7 +364,7 @@ export default function NewsArticleDetailClient({
|
||||
|
||||
{/* X / Twitter */}
|
||||
<a
|
||||
href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=BroadcastBeat`}
|
||||
href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=AVBeat`}
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
aria-label="Share on X"
|
||||
title="X (Twitter)"
|
||||
@@ -418,7 +418,7 @@ export default function NewsArticleDetailClient({
|
||||
}}
|
||||
aria-label="Copy article link"
|
||||
title="Copy link"
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#3b82f6] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#E67E22] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
||||
@@ -432,7 +432,7 @@ export default function NewsArticleDetailClient({
|
||||
onClick={handleSaveToReadingList}
|
||||
disabled={savingState !== "idle"}
|
||||
aria-label={isSaved ? "Remove from reading list" : "Save to reading list"}
|
||||
className="text-xs font-bold uppercase tracking-widest text-[#3b82f6] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="text-xs font-bold uppercase tracking-widest text-[#E67E22] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
{savingState === "saving" ?"Saving..."
|
||||
: savingState === "removing" ?"Removing..."
|
||||
: isSaved
|
||||
@@ -469,7 +469,7 @@ export default function NewsArticleDetailClient({
|
||||
{article.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="px-3 py-1 text-xs font-body bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6] transition-colors rounded-sm cursor-default">
|
||||
className="px-3 py-1 text-xs font-body bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] hover:border-[#E67E22] hover:text-[#E67E22] transition-colors rounded-sm cursor-default">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
@@ -483,7 +483,7 @@ export default function NewsArticleDetailClient({
|
||||
<p className="font-body text-sm text-[#777]">Found this article useful?</p>
|
||||
<button
|
||||
onClick={() => setEmailShareOpen(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] text-[#888] hover:text-[#3b82f6] text-xs font-bold uppercase tracking-widest transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="flex items-center gap-2 px-4 py-2 bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#E67E22] text-[#888] hover:text-[#E67E22] text-xs font-bold uppercase tracking-widest transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
@@ -504,7 +504,7 @@ export default function NewsArticleDetailClient({
|
||||
<div className="pt-6 border-t border-[#222]">
|
||||
<Link
|
||||
href="/news"
|
||||
className="inline-flex items-center gap-2 text-[#3b82f6] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="inline-flex items-center gap-2 text-[#E67E22] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
@@ -534,7 +534,7 @@ export default function NewsArticleDetailClient({
|
||||
|
||||
{/* Related Articles Sidebar */}
|
||||
<div className="bg-[#111] border border-[#222] p-5">
|
||||
<h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
|
||||
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
|
||||
Related Articles
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
@@ -553,10 +553,10 @@ export default function NewsArticleDetailClient({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5">
|
||||
<p className="text-[#E67E22] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5">
|
||||
{related.category}
|
||||
</p>
|
||||
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug">
|
||||
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#E67E22] transition-colors line-clamp-2 leading-snug">
|
||||
{related.title}
|
||||
</p>
|
||||
</div>
|
||||
@@ -568,19 +568,19 @@ export default function NewsArticleDetailClient({
|
||||
{/* Related Forum Posts — same widget as /articles/[slug] */}
|
||||
{relatedForumThreads.length > 0 && (
|
||||
<div className="bg-[#111] border border-[#222] p-5">
|
||||
<h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
|
||||
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
|
||||
Related Forum Posts
|
||||
</h3>
|
||||
<ul className="space-y-3.5">
|
||||
{relatedForumThreads.map((t) => (
|
||||
<li key={t.id}>
|
||||
<Link href={`/forum/thread/${t.id}`} className="group block">
|
||||
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug">
|
||||
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#E67E22] transition-colors line-clamp-2 leading-snug">
|
||||
{t.title}
|
||||
</p>
|
||||
<p className="flex items-center gap-2 mt-1 text-[10px] text-[#666]">
|
||||
{t.category_name && (
|
||||
<span className="text-[#3b82f6] font-body font-bold uppercase tracking-wider">
|
||||
<span className="text-[#E67E22] font-body font-bold uppercase tracking-wider">
|
||||
{t.category_name}
|
||||
</span>
|
||||
)}
|
||||
@@ -594,7 +594,7 @@ export default function NewsArticleDetailClient({
|
||||
</ul>
|
||||
<Link
|
||||
href="/forum"
|
||||
className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#3b82f6] hover:underline font-body font-semibold uppercase tracking-wider"
|
||||
className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#E67E22] hover:underline font-body font-semibold uppercase tracking-wider"
|
||||
>
|
||||
Browse all forum threads →
|
||||
</Link>
|
||||
@@ -639,7 +639,7 @@ export default function NewsArticleDetailClient({
|
||||
onClick={prevCarousel}
|
||||
disabled={carouselIndex === 0}
|
||||
aria-label="Previous articles"
|
||||
className="w-8 h-8 flex items-center justify-center border border-[#2a2a2a] text-[#666] hover:border-[#3b82f6] hover:text-[#3b82f6] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="w-8 h-8 flex items-center justify-center border border-[#2a2a2a] text-[#666] hover:border-[#E67E22] hover:text-[#E67E22] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
@@ -655,7 +655,7 @@ export default function NewsArticleDetailClient({
|
||||
onClick={nextCarousel}
|
||||
disabled={carouselIndex >= maxIndex}
|
||||
aria-label="Next articles"
|
||||
className="w-8 h-8 flex items-center justify-center border border-[#2a2a2a] text-[#666] hover:border-[#3b82f6] hover:text-[#3b82f6] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="w-8 h-8 flex items-center justify-center border border-[#2a2a2a] text-[#666] hover:border-[#E67E22] hover:text-[#E67E22] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
@@ -694,11 +694,11 @@ export default function NewsArticleDetailClient({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">
|
||||
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider">
|
||||
{related.category}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug group-hover:text-[#3b82f6] transition-colors line-clamp-2 mb-1.5">
|
||||
<h3 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug group-hover:text-[#E67E22] transition-colors line-clamp-2 mb-1.5">
|
||||
{related.title}
|
||||
</h3>
|
||||
<p className="text-[#666] font-body text-xs line-clamp-2 leading-relaxed">
|
||||
@@ -723,7 +723,7 @@ export default function NewsArticleDetailClient({
|
||||
onClick={() => setCarouselIndex(i)}
|
||||
aria-label={`Go to slide ${i + 1}`}
|
||||
className={`w-1.5 h-1.5 rounded-full transition-colors focus:outline-none ${
|
||||
i === carouselIndex ? "bg-[#3b82f6]" : "bg-[#333]"
|
||||
i === carouselIndex ? "bg-[#E67E22]" : "bg-[#333]"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
@@ -756,7 +756,7 @@ export default function NewsArticleDetailClient({
|
||||
setEmailStatus("idle");
|
||||
}}
|
||||
aria-label="Close share dialog"
|
||||
className="absolute top-4 right-4 text-[#555] hover:text-[#e0e0e0] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
className="absolute top-4 right-4 text-[#555] hover:text-[#e0e0e0] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
@@ -777,7 +777,7 @@ export default function NewsArticleDetailClient({
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#3b82f6"
|
||||
stroke="#E67E22"
|
||||
strokeWidth="2"
|
||||
aria-hidden="true">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
@@ -823,7 +823,7 @@ export default function NewsArticleDetailClient({
|
||||
<label
|
||||
htmlFor="email-to"
|
||||
className="block font-body text-xs text-[#888] uppercase tracking-wider mb-1.5">
|
||||
Recipient Email <span className="text-[#3b82f6]">*</span>
|
||||
Recipient Email <span className="text-[#E67E22]">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="email-to"
|
||||
@@ -831,7 +831,7 @@ export default function NewsArticleDetailClient({
|
||||
value={emailTo}
|
||||
onChange={(e) => setEmailTo(e.target.value)}
|
||||
placeholder="colleague@example.com"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] focus:border-[#3b82f6] text-[#e0e0e0] placeholder-[#444] text-sm font-body px-3 py-2.5 rounded-sm outline-none transition-colors"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] focus:border-[#E67E22] text-[#e0e0e0] placeholder-[#444] text-sm font-body px-3 py-2.5 rounded-sm outline-none transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -846,7 +846,7 @@ export default function NewsArticleDetailClient({
|
||||
onChange={(e) => setEmailNote(e.target.value)}
|
||||
placeholder="Thought you might find this interesting..."
|
||||
rows={3}
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] focus:border-[#3b82f6] text-[#e0e0e0] placeholder-[#444] text-sm font-body px-3 py-2.5 rounded-sm outline-none transition-colors resize-none"
|
||||
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] focus:border-[#E67E22] text-[#e0e0e0] placeholder-[#444] text-sm font-body px-3 py-2.5 rounded-sm outline-none transition-colors resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -890,7 +890,7 @@ export default function NewsArticleDetailClient({
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="fixed bottom-4 right-4 bg-[#1a1a1a] border border-[#3b82f6] text-white px-4 py-3 rounded-sm text-sm font-body shadow-lg z-50">
|
||||
className="fixed bottom-4 right-4 bg-[#1a1a1a] border border-[#E67E22] text-white px-4 py-3 rounded-sm text-sm font-body shadow-lg z-50">
|
||||
{saveToast}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,7 @@ interface PageProps {
|
||||
}
|
||||
|
||||
const SITE_URL =
|
||||
process.env.NEXT_PUBLIC_SITE_URL || "https://broadcastbeat.com";
|
||||
process.env.NEXT_PUBLIC_SITE_URL || "https://avbeat.com";
|
||||
|
||||
/** Convert a free-form date (e.g. "April 9, 2026") to ISO 8601 for JSON-LD. */
|
||||
function toIso(d: string | null | undefined): string {
|
||||
@@ -59,7 +59,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
return { title: "Article Not Found", description: "The article you are looking for does not exist." };
|
||||
}
|
||||
return {
|
||||
title: `${article.title} — Broadcast Beat`,
|
||||
title: `${article.title} — AV Beat`,
|
||||
description: article.excerpt,
|
||||
alternates: { canonical: `/news/${article.slug}` },
|
||||
openGraph: {
|
||||
@@ -77,7 +77,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
title: article.title,
|
||||
description: article.excerpt,
|
||||
images: [article.image],
|
||||
creator: "@Broadcast Beat",
|
||||
creator: "@AV Beat",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export default async function NewsArticlePage({ params }: PageProps) {
|
||||
author: { "@type": "Person", name: article.author },
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Broadcast Beat",
|
||||
name: "AV Beat",
|
||||
logo: { "@type": "ImageObject", url: `${SITE_URL}/assets/images/logo.png` },
|
||||
},
|
||||
datePublished: toIso(article.date),
|
||||
|
||||
@@ -6,9 +6,9 @@ import { getLegacyFeaturedArticles } from "@/lib/articles/legacy-source";
|
||||
export const revalidate = 1800;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Featured Stories — Broadcast Beat",
|
||||
title: "Featured Stories — AV Beat",
|
||||
description:
|
||||
"Editorial picks from the Broadcast Beat staff — the most important industry stories, hand-selected from across broadcast engineering, IP/cloud workflows, streaming, AI, and live production.",
|
||||
"Editorial picks from the AV Beat staff — the most important industry stories, hand-selected from across broadcast engineering, IP/cloud workflows, streaming, AI, and live production.",
|
||||
alternates: { canonical: "/news/featured" },
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ export default async function FeaturedNewsPage() {
|
||||
articles={articles}
|
||||
kicker="Editorial"
|
||||
heading="Featured Editorial"
|
||||
subtitle="Hand-picked feature stories, exclusives, and cover articles from the Broadcast Beat editorial desk."
|
||||
subtitle="Hand-picked feature stories, exclusives, and cover articles from the AV Beat editorial desk."
|
||||
hideAiSuggestions
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getLegacyArticlesBySection } from "@/lib/articles/legacy-source";
|
||||
export const revalidate = 1800;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Industry News — Broadcast Beat",
|
||||
title: "Industry News — AV Beat",
|
||||
description:
|
||||
"Breaking news and industry updates from the world of broadcast engineering, IP/cloud workflows, streaming, AI, and live production.",
|
||||
alternates: { canonical: "/news" },
|
||||
|
||||
@@ -63,7 +63,7 @@ function NewsletterCard({ newsletter }: { newsletter: Newsletter }) {
|
||||
const blocks = Array.isArray(newsletter.article_blocks)
|
||||
? newsletter.article_blocks.filter((b) => b.title)
|
||||
: [];
|
||||
const accent = newsletter.accent_color || "#3b82f6";
|
||||
const accent = newsletter.accent_color || "#E67E22";
|
||||
|
||||
const layoutLabel: Record<string, string> = {
|
||||
featured: "Weekly Digest",
|
||||
@@ -113,7 +113,7 @@ function NewsletterCard({ newsletter }: { newsletter: Newsletter }) {
|
||||
<span className="mt-1.5 w-1 h-1 rounded-full bg-[#444] flex-shrink-0" />
|
||||
<span className="font-body text-[#888] text-xs leading-relaxed line-clamp-1">
|
||||
{block.category && (
|
||||
<span className="text-[#3b82f6] font-bold mr-1">{block.category}:</span>
|
||||
<span className="text-[#E67E22] font-bold mr-1">{block.category}:</span>
|
||||
)}
|
||||
{block.title}
|
||||
</span>
|
||||
@@ -132,7 +132,7 @@ function NewsletterCard({ newsletter }: { newsletter: Newsletter }) {
|
||||
<span className="font-body text-[#555] text-xs">
|
||||
{blocks.length} {blocks.length === 1 ? "story" : "stories"}
|
||||
</span>
|
||||
<span className="font-body text-[#3b82f6] text-xs font-medium">
|
||||
<span className="font-body text-[#E67E22] text-xs font-medium">
|
||||
Issue #{newsletter.id.slice(0, 6).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
@@ -197,9 +197,9 @@ function SignupCTA() {
|
||||
|
||||
{status === "success" ? (
|
||||
<div className="text-center py-4" role="status" aria-live="polite">
|
||||
<div className="w-10 h-10 rounded-full bg-[#3b82f6]/20 flex items-center justify-center mx-auto mb-3">
|
||||
<div className="w-10 h-10 rounded-full bg-[#E67E22]/20 flex items-center justify-center mx-auto mb-3">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<path d="M4 10l4 4 8-8" stroke="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M4 10l4 4 8-8" stroke="#E67E22" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="font-heading text-[#e0e0e0] font-bold mb-1">You're subscribed!</p>
|
||||
@@ -228,7 +228,7 @@ function SignupCTA() {
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#3b82f6] border-[#3b82f6]" : "border-[#444]"}`}>
|
||||
className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#E67E22] border-[#E67E22]" : "border-[#444]"}`}>
|
||||
{checked && (
|
||||
<svg viewBox="0 0 12 12" fill="none" className="w-full h-full" aria-hidden="true">
|
||||
<path d="M2 6l3 3 5-5" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
@@ -262,7 +262,7 @@ function SignupCTA() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={status === "loading"}
|
||||
className={`btn-subscribe py-2.5 px-6 text-sm whitespace-nowrap focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${status === "loading" ? "opacity-70 cursor-not-allowed" : ""}`}>
|
||||
className={`btn-subscribe py-2.5 px-6 text-sm whitespace-nowrap focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${status === "loading" ? "opacity-70 cursor-not-allowed" : ""}`}>
|
||||
{status === "loading" ? "Subscribing..." : "Subscribe Free"}
|
||||
</button>
|
||||
</div>
|
||||
@@ -275,7 +275,7 @@ function SignupCTA() {
|
||||
|
||||
<p className="font-body text-[#555] text-xs mt-3 text-center">
|
||||
No spam. Unsubscribe anytime.{" "}
|
||||
<Link href="/privacy" className="text-[#3b82f6] hover:underline">
|
||||
<Link href="/privacy" className="text-[#E67E22] hover:underline">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</p>
|
||||
@@ -379,7 +379,7 @@ export default function NewsletterArchivePage() {
|
||||
</div>
|
||||
<h1 className="font-heading text-white text-3xl font-bold">Newsletter Archive</h1>
|
||||
<p className="text-[#777] font-body text-sm mt-2">
|
||||
Browse past issues of the Broadcast Beat newsletter — searchable by date and topic.
|
||||
Browse past issues of the AV Beat newsletter — searchable by date and topic.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -419,9 +419,9 @@ export default function NewsletterArchivePage() {
|
||||
<button
|
||||
key={topic}
|
||||
onClick={() => setSelectedTopic(topic)}
|
||||
className={`font-body text-xs px-3 py-1.5 rounded-sm border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${
|
||||
className={`font-body text-xs px-3 py-1.5 rounded-sm border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${
|
||||
selectedTopic === topic
|
||||
? "bg-[#3b82f6] border-[#3b82f6] text-white"
|
||||
? "bg-[#E67E22] border-[#E67E22] text-white"
|
||||
: "bg-transparent border-[#333] text-[#888] hover:border-[#555] hover:text-[#ccc]"
|
||||
}`}
|
||||
aria-pressed={selectedTopic === topic}>
|
||||
@@ -462,7 +462,7 @@ export default function NewsletterArchivePage() {
|
||||
{hasActiveFilters && (
|
||||
<button
|
||||
onClick={handleClearFilters}
|
||||
className="font-body text-xs text-[#3b82f6] hover:text-[#60a5fa] transition-colors underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]">
|
||||
className="font-body text-xs text-[#E67E22] hover:text-[#E67E22] transition-colors underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]">
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
@@ -498,7 +498,7 @@ export default function NewsletterArchivePage() {
|
||||
<p className="font-body text-red-400 text-sm mb-3">{error}</p>
|
||||
<button
|
||||
onClick={fetchArchive}
|
||||
className="font-body text-xs text-[#3b82f6] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]">
|
||||
className="font-body text-xs text-[#E67E22] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]">
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
@@ -521,7 +521,7 @@ export default function NewsletterArchivePage() {
|
||||
</p>
|
||||
<button
|
||||
onClick={handleClearFilters}
|
||||
className="font-body text-xs text-[#3b82f6] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]">
|
||||
className="font-body text-xs text-[#E67E22] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]">
|
||||
Clear all filters
|
||||
</button>
|
||||
</div>
|
||||
@@ -555,9 +555,9 @@ export default function NewsletterArchivePage() {
|
||||
<li key={topic}>
|
||||
<button
|
||||
onClick={() => setSelectedTopic(topic)}
|
||||
className={`w-full text-left font-body text-sm py-1.5 px-2 rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${
|
||||
className={`w-full text-left font-body text-sm py-1.5 px-2 rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${
|
||||
selectedTopic === topic
|
||||
? "text-[#3b82f6] bg-[#3b82f6]/10"
|
||||
? "text-[#E67E22] bg-[#E67E22]/10"
|
||||
: "text-[#888] hover:text-[#ccc] hover:bg-[#1a1a1a]"
|
||||
}`}>
|
||||
{topic}
|
||||
@@ -578,9 +578,9 @@ export default function NewsletterArchivePage() {
|
||||
<li key={year}>
|
||||
<button
|
||||
onClick={() => { setSelectedYear(String(year)); setSelectedMonth(""); }}
|
||||
className={`w-full text-left font-body text-sm py-1.5 px-2 rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${
|
||||
className={`w-full text-left font-body text-sm py-1.5 px-2 rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${
|
||||
selectedYear === String(year)
|
||||
? "text-[#3b82f6] bg-[#3b82f6]/10"
|
||||
? "text-[#E67E22] bg-[#E67E22]/10"
|
||||
: "text-[#888] hover:text-[#ccc] hover:bg-[#1a1a1a]"
|
||||
}`}>
|
||||
{year}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const metadata = {
|
||||
title: "Newsletter — Broadcast Beat",
|
||||
description: "Broadcast Beat newsletter archive.",
|
||||
title: "Newsletter — AV Beat",
|
||||
description: "AV Beat newsletter archive.",
|
||||
alternates: { canonical: "/newsletter/archive" },
|
||||
};
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Privacy Policy — Broadcast Beat",
|
||||
description: "Broadcast Beat Privacy Policy — how we collect, use, and protect your personal information. GDPR and CCPA compliant.",
|
||||
title: "Privacy Policy — AV Beat",
|
||||
description: "AV Beat Privacy Policy — how we collect, use, and protect your personal information. GDPR and CCPA compliant.",
|
||||
alternates: {
|
||||
canonical: "/privacy",
|
||||
},
|
||||
@@ -26,7 +26,7 @@ export default function PrivacyPage() {
|
||||
|
||||
<section aria-labelledby="section-intro">
|
||||
<p id="section-intro">
|
||||
This Privacy Policy describes how <strong className="text-[#e0e0e0]">Relevant Media Properties, LLC</strong> ("we," "us," or "our"), publisher of Broadcast Beat, AV Beat, Backlot Beat, and BroadcastEngineering.com, collects, uses, and shares information about you when you use our websites and services. We are committed to protecting your privacy and complying with applicable data protection laws including GDPR and CCPA.
|
||||
This Privacy Policy describes how <strong className="text-[#e0e0e0]">Relevant Media Properties, LLC</strong> ("we," "us," or "our"), publisher of AV Beat, AV Beat, Backlot Beat, and BroadcastEngineering.com, collects, uses, and shares information about you when you use our websites and services. We are committed to protecting your privacy and complying with applicable data protection laws including GDPR and CCPA.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -103,7 +103,7 @@ export default function PrivacyPage() {
|
||||
<li><strong className="text-[#ccc]">Opt-out (CCPA):</strong> California residents may opt out of the sale of personal information</li>
|
||||
<li><strong className="text-[#ccc]">Unsubscribe:</strong> Opt out of marketing emails at any time via the unsubscribe link</li>
|
||||
</ul>
|
||||
<p className="mt-3">To exercise any of these rights, contact us at <a href="mailto:privacy@broadcastbeat.com" className="text-[#3b82f6] underline hover:text-blue-400 transition-colors">privacy@broadcastbeat.com</a>. We will respond within 30 days.</p>
|
||||
<p className="mt-3">To exercise any of these rights, contact us at <a href="mailto:privacy@avbeat.com" className="text-[#E67E22] underline hover:text-blue-400 transition-colors">privacy@avbeat.com</a>. We will respond within 30 days.</p>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="section-6">
|
||||
@@ -127,7 +127,7 @@ export default function PrivacyPage() {
|
||||
<address className="not-italic mt-3 space-y-1 text-[#999]">
|
||||
<p><strong className="text-[#ccc]">Relevant Media Properties, LLC</strong></p>
|
||||
<p>Privacy Officer</p>
|
||||
<p>Email: <a href="mailto:privacy@broadcastbeat.com" className="text-[#3b82f6] underline hover:text-blue-400 transition-colors">privacy@broadcastbeat.com</a></p>
|
||||
<p>Email: <a href="mailto:privacy@avbeat.com" className="text-[#E67E22] underline hover:text-blue-400 transition-colors">privacy@avbeat.com</a></p>
|
||||
</address>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function RegisterPage() {
|
||||
<div className="min-h-screen bg-[#111111]">
|
||||
<Header />
|
||||
<div className="flex items-center justify-center py-24">
|
||||
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-6 h-6 border-2 border-[#E67E22] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
@@ -97,7 +97,7 @@ export default function RegisterPage() {
|
||||
<div className="bg-[#0d0d0d] border-b border-[#1e1e1e]">
|
||||
<div className="max-w-container mx-auto px-4 py-2.5">
|
||||
<nav aria-label="Breadcrumb" className="flex items-center gap-2 text-xs font-body text-[#555]">
|
||||
<Link href="/home-page" className="hover:text-[#3b82f6] transition-colors">Home</Link>
|
||||
<Link href="/home-page" className="hover:text-[#E67E22] transition-colors">Home</Link>
|
||||
<span aria-hidden="true">/</span>
|
||||
<span className="text-[#888]">Create Account</span>
|
||||
</nav>
|
||||
@@ -112,7 +112,7 @@ export default function RegisterPage() {
|
||||
Create Account
|
||||
</h1>
|
||||
<p className="font-body text-sm text-[#666]">
|
||||
Join Broadcast Beat to save articles, track your reading history, and get personalized content.
|
||||
Join AV Beat to save articles, track your reading history, and get personalized content.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function RegisterPage() {
|
||||
onChange={(e) => setFullName(e.target.value)}
|
||||
placeholder="Jane Smith"
|
||||
disabled={loading}
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] transition-colors disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -159,7 +159,7 @@ export default function RegisterPage() {
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@example.com"
|
||||
disabled={loading}
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] transition-colors disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -176,7 +176,7 @@ export default function RegisterPage() {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Min. 6 characters"
|
||||
disabled={loading}
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] transition-colors disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -193,7 +193,7 @@ export default function RegisterPage() {
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="Re-enter password"
|
||||
disabled={loading}
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
|
||||
className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] transition-colors disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -201,7 +201,7 @@ export default function RegisterPage() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full h-10 bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-sm font-bold uppercase tracking-wider rounded-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2">
|
||||
className="w-full h-10 bg-[#E67E22] hover:bg-[#C8651B] text-white font-body text-sm font-bold uppercase tracking-wider rounded-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2">
|
||||
{loading ? (
|
||||
<>
|
||||
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
@@ -223,7 +223,7 @@ export default function RegisterPage() {
|
||||
{/* Login Link */}
|
||||
<Link
|
||||
href="/login"
|
||||
className="block w-full h-10 border border-[#2a2a2a] hover:border-[#3b82f6] text-[#888] hover:text-[#3b82f6] font-body text-sm font-bold uppercase tracking-wider rounded-sm transition-colors flex items-center justify-center">
|
||||
className="block w-full h-10 border border-[#2a2a2a] hover:border-[#E67E22] text-[#888] hover:text-[#E67E22] font-body text-sm font-bold uppercase tracking-wider rounded-sm transition-colors flex items-center justify-center">
|
||||
Sign In
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com';
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
|
||||
|
||||
return {
|
||||
rules: [
|
||||
|
||||
@@ -11,9 +11,9 @@ import SidebarAdStack from "@/components/SidebarAdStack";
|
||||
export const revalidate = 60;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Search — Broadcast Beat",
|
||||
title: "Search — AV Beat",
|
||||
description:
|
||||
"Search Broadcast Beat for broadcast engineering news, gear reviews, show coverage, and technology insights.",
|
||||
"Search AV Beat for broadcast engineering news, gear reviews, show coverage, and technology insights.",
|
||||
alternates: { canonical: "/search" },
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
|
||||
<div className="flex-1 h-px bg-[#2a2a2a]" />
|
||||
</div>
|
||||
<h1 className="font-heading text-white text-3xl font-bold">
|
||||
{query ? `Results for "${query}"` : "Search Broadcast Beat"}
|
||||
{query ? `Results for "${query}"` : "Search AV Beat"}
|
||||
</h1>
|
||||
<p className="text-[#777] font-body text-sm mt-2">
|
||||
{query
|
||||
@@ -51,7 +51,7 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
|
||||
{query && results.length === 0 && (
|
||||
<div className="py-16 text-center">
|
||||
<p className="text-[#555] font-body text-sm mb-3">No articles match your search.</p>
|
||||
<Link href="/news" className="text-[#3b82f6] text-xs font-body hover:underline">
|
||||
<Link href="/news" className="text-[#E67E22] text-xs font-body hover:underline">
|
||||
Browse all news →
|
||||
</Link>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
|
||||
<div className="flex-shrink-0 w-[160px] h-[105px] relative overflow-hidden rounded-sm">
|
||||
<AppImage
|
||||
src={article.image || "/assets/images/article-placeholder.jpg"}
|
||||
alt={article.alt || article.title || "Broadcast Beat article"}
|
||||
alt={article.alt || article.title || "AV Beat article"}
|
||||
fill
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
sizes="160px"
|
||||
@@ -77,18 +77,18 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">
|
||||
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider">
|
||||
{article.category || "NEWS"}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#3b82f6] transition-colors line-clamp-2">
|
||||
<h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#E67E22] transition-colors line-clamp-2">
|
||||
{article.title || "Untitled"}
|
||||
</h2>
|
||||
<p className="text-[#777] font-body text-sm leading-relaxed line-clamp-2">
|
||||
{article.excerpt || ""}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<span className="text-[#555] font-body text-xs">By {article.author || "Broadcast Beat"}</span>
|
||||
<span className="text-[#555] font-body text-xs">By {article.author || "AV Beat"}</span>
|
||||
<span className="text-[#444] text-[10px]">·</span>
|
||||
<span className="text-[#555] font-body text-xs">{article.readTime || ""}</span>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MetadataRoute } from 'next';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { getLegacyRecentSitemapEntries } from '@/lib/articles/legacy-source';
|
||||
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com';
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
|
||||
|
||||
// Cap below Google's 50k URL/sitemap hard limit, leaving room for the
|
||||
// static + author + event entries we add below.
|
||||
|
||||
@@ -49,7 +49,7 @@ interface PageProps {
|
||||
export async function generateMetadata({ searchParams }: PageProps): Promise<Metadata> {
|
||||
const sp = await searchParams;
|
||||
const cat = sp?.category && CATEGORY_FILTERS[sp.category];
|
||||
const title = cat ? `${cat.title} — Broadcast Beat` : "Production Technology Deep Dives — Broadcast Beat";
|
||||
const title = cat ? `${cat.title} — AV Beat` : "Production Technology Deep Dives — AV Beat";
|
||||
const desc = cat?.description || "Deep-dive analysis of broadcast technology trends: IP workflows, cloud production, AI automation, streaming infrastructure, and ATSC 3.0.";
|
||||
return {
|
||||
title,
|
||||
@@ -87,7 +87,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
<span className="section-label">Technology</span>
|
||||
<div className="flex-1 h-px bg-[#2a2a2a]" />
|
||||
{filter && (
|
||||
<Link href="/technology" className="text-[#666] hover:text-[#3b82f6] font-body text-[11px] underline">
|
||||
<Link href="/technology" className="text-[#666] hover:text-[#E67E22] font-body text-[11px] underline">
|
||||
clear filter
|
||||
</Link>
|
||||
)}
|
||||
@@ -101,7 +101,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
href="/technology"
|
||||
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all ${
|
||||
!filterSlug
|
||||
? "bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50"
|
||||
? "bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/50"
|
||||
: "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc]"
|
||||
}`}>
|
||||
All
|
||||
@@ -114,7 +114,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
href={`/technology?category=${slug}`}
|
||||
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all ${
|
||||
isActive
|
||||
? "bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50"
|
||||
? "bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/50"
|
||||
: "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc]"
|
||||
}`}>
|
||||
{def.title}
|
||||
@@ -133,7 +133,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
No articles found{filter ? ` for "${filter.title}"` : ""}.
|
||||
</p>
|
||||
{filter && (
|
||||
<Link href="/technology" className="inline-block mt-3 text-[#3b82f6] hover:underline font-body text-sm">
|
||||
<Link href="/technology" className="inline-block mt-3 text-[#E67E22] hover:underline font-body text-sm">
|
||||
← Back to all technology coverage
|
||||
</Link>
|
||||
)}
|
||||
@@ -145,7 +145,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
<div className="mb-10">
|
||||
<Link
|
||||
href={`/articles/${articles[0].slug}`}
|
||||
className="flex flex-col md:flex-row gap-6 bg-[#111] border border-[#222] overflow-hidden group hover:border-[#3b82f6] transition-colors p-5">
|
||||
className="flex flex-col md:flex-row gap-6 bg-[#111] border border-[#222] overflow-hidden group hover:border-[#E67E22] transition-colors p-5">
|
||||
<div className="flex-shrink-0 relative w-full md:w-[320px] h-[200px] md:h-[200px] overflow-hidden rounded-sm">
|
||||
<AppImage
|
||||
src={articles[0].image}
|
||||
@@ -157,13 +157,13 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">{articles[0].category}</span>
|
||||
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider">{articles[0].category}</span>
|
||||
<span className="text-[#444] text-[10px]">·</span>
|
||||
<span className="text-[#555] font-body text-[11px]">{articles[0].date}</span>
|
||||
<span className="text-[#444] text-[10px]">·</span>
|
||||
<span className="text-[#555] font-body text-[11px]">{articles[0].readTime}</span>
|
||||
</div>
|
||||
<h2 className="font-heading text-[#e0e0e0] text-xl font-bold leading-tight mb-3 group-hover:text-[#3b82f6] transition-colors">
|
||||
<h2 className="font-heading text-[#e0e0e0] text-xl font-bold leading-tight mb-3 group-hover:text-[#E67E22] transition-colors">
|
||||
{articles[0].title}
|
||||
</h2>
|
||||
<p className="text-[#777] font-body text-sm leading-relaxed mb-4">{articles[0].excerpt}</p>
|
||||
@@ -185,7 +185,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
<Link
|
||||
key={article.slug}
|
||||
href={`/articles/${article.slug}`}
|
||||
className="flex gap-4 bg-[#111] border border-[#222] p-4 group hover:border-[#3b82f6] transition-colors">
|
||||
className="flex gap-4 bg-[#111] border border-[#222] p-4 group hover:border-[#E67E22] transition-colors">
|
||||
<div className="flex-shrink-0 w-[120px] h-[80px] relative overflow-hidden rounded-sm">
|
||||
<AppImage
|
||||
src={article.image}
|
||||
@@ -197,11 +197,11 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider">{article.category}</span>
|
||||
<span className="text-[#E67E22] font-body text-[9px] font-bold uppercase tracking-wider">{article.category}</span>
|
||||
<span className="text-[#444] text-[9px]">·</span>
|
||||
<span className="text-[#555] font-body text-[10px]">{article.readTime}</span>
|
||||
</div>
|
||||
<h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-1 group-hover:text-[#3b82f6] transition-colors line-clamp-2">
|
||||
<h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-1 group-hover:text-[#E67E22] transition-colors line-clamp-2">
|
||||
{article.title}
|
||||
</h2>
|
||||
{article.category !== "Featured" && (
|
||||
|
||||
Reference in New Issue
Block a user