1 Commits

Author SHA1 Message Date
Claude
d43f78b161 av: clone broadcastbeat sources + AV Beat rebrand (Phase 1)
- Replaces the prior Rocket scaffold (saved on pre-bb-clone-rollback branch)
- Domain: broadcastbeat.com -> avbeat.com
- Brand: Broadcast Beat -> AV Beat
- Slug: broadcastbeat -> avbeat (package, identifiers)
- Schema fallback: bb -> av (env var name unchanged)
- Placeholder AV BEAT logo (gold->red gradient) at /assets/logos/av.svg
- All BB/RMP logo references repointed

Outstanding before public DNS swap:
  - Supabase av schema bootstrap (mirror of bb tables)
  - WordPress archive import (avbeat-com -> av.articles)
  - Coolify env vars
  - dev-avbeat.onsethost.com staging deploy + visual review
2026-06-02 15:32:56 +00:00
137 changed files with 4728 additions and 4541 deletions

View File

@@ -14,11 +14,7 @@ ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL
ENV NEXT_PUBLIC_GA_MEASUREMENT_ID=$NEXT_PUBLIC_GA_MEASUREMENT_ID ENV NEXT_PUBLIC_GA_MEASUREMENT_ID=$NEXT_PUBLIC_GA_MEASUREMENT_ID
COPY package*.json ./ COPY package*.json ./
# Force dev-deps even if NODE_ENV=production was supplied as a Coolify RUN npm install
# build arg — autoprefixer/postcss/tailwindcss live in devDependencies and
# Next.js needs them at build time. Same fix that landed for RMP cold-build
# on 2026-05-30.
RUN npm install --include=dev
COPY . . COPY . .
RUN npm run build RUN npm run build
ENV NODE_ENV=production ENV NODE_ENV=production

View File

@@ -1,4 +1,4 @@
# Pending — broadcastbeat.com tonight's deferred work # Pending — avbeat.com tonight's deferred work
## Featured-image backfill (P0-1, deferred) ## Featured-image backfill (P0-1, deferred)
@@ -12,7 +12,7 @@ in Supabase storage) is **NOT done**. The smoke-test attempt
`/tmp/bb-image-backfill.py` ran on coolify01: `/tmp/bb-image-backfill.py` ran on coolify01:
- Matrox+Amagi smoke article (`matrox-video-and-amagi-collaborate-...`): - Matrox+Amagi smoke article (`matrox-video-and-amagi-collaborate-...`):
Wayback returned no archived snapshot of `https://www.broadcastbeat.com/<slug>/`, Wayback returned no archived snapshot of `https://www.avbeat.com/<slug>/`,
so the og:image extraction path didn't apply. Many of the broken-image so the og:image extraction path didn't apply. Many of the broken-image
articles are recent NAB 2026 posts that have never been crawled by articles are recent NAB 2026 posts that have never been crawled by
Wayback. Wayback.
@@ -22,7 +22,7 @@ in Supabase storage) is **NOT done**. The smoke-test attempt
**What needs to happen next session:** **What needs to happen next session:**
1. For each broken article, try multiple sources in order: 1. For each broken article, try multiple sources in order:
- Wayback Machine snapshot of `broadcastbeat.com/<slug>/` (skip if 404). - Wayback Machine snapshot of `avbeat.com/<slug>/` (skip if 404).
- Manufacturer press-kit page based on title keywords (Matrox, Amagi, - Manufacturer press-kit page based on title keywords (Matrox, Amagi,
Telestream, Calrec, AJA, etc. — keep a per-vendor mapping of press Telestream, Calrec, AJA, etc. — keep a per-vendor mapping of press
kit URLs). kit URLs).

View File

@@ -29,6 +29,6 @@ export const imageHosts = [
}, },
{ {
protocol: 'https', protocol: 'https',
hostname: 'www.broadcastbeat.com', hostname: 'www.avbeat.com',
}, },
]; ];

View File

@@ -70,7 +70,7 @@ const nextConfig = {
destination: 'https://relevantmediaproperties.com/broadcast-beat---media-kit', destination: 'https://relevantmediaproperties.com/broadcast-beat---media-kit',
permanent: true, permanent: true,
}, },
// WordPress admin redirects for broadcastbeat.com (301 permanent) // WordPress admin redirects for avbeat.com (301 permanent)
{ {
source: '/wp-admin', source: '/wp-admin',
destination: '/admin', destination: '/admin',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 96" width="360" height="96">
<defs>
<linearGradient id="av-grad" x1="0" x2="1">
<stop offset="0%" stop-color="#FFB300"/>
<stop offset="55%" stop-color="#FF6B00"/>
<stop offset="100%" stop-color="#D60701"/>
</linearGradient>
</defs>
<g font-family="Plus Jakarta Sans, sans-serif" font-weight="800">
<text x="0" y="44" font-size="40" fill="url(#av-grad)" letter-spacing="2">AVB</text>
<text x="0" y="82" font-size="20" fill="#f4f4f6" letter-spacing="2.5" opacity="0.85">AV Beat</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 362 KiB

View File

@@ -52,8 +52,8 @@ export default function ContactPage() {
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10"> <div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10">
<main> <main>
<header className="mb-8 border-b border-[#252525] pb-6"> <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,#E67E22)] mb-2"> <div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2">
AV Beat · Where AV Meets IT AV Beat
</div> </div>
<h1 className="font-serif text-4xl md:text-5xl font-bold tracking-tight">Contact</h1> <h1 className="font-serif text-4xl md:text-5xl font-bold tracking-tight">Contact</h1>
<p className="mt-3 text-[#9ca3af] text-base"> <p className="mt-3 text-[#9ca3af] text-base">

View File

@@ -4,7 +4,7 @@ import SidebarAdStack from "@/components/SidebarAdStack";
export const metadata = { export const metadata = {
title: "About AV Beat", 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.", description: "AV Beat is an independent industry publication covering broadcast, production, and post-production technology.",
}; };
export default function AboutPage() { export default function AboutPage() {
@@ -15,12 +15,12 @@ export default function AboutPage() {
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10"> <div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10">
<main> <main>
<header className="mb-8 border-b border-[#252525] pb-6"> <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,#E67E22)] mb-2"> <div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2">
AV Beat · Where AV Meets IT AV Beat
</div> </div>
<h1 className="font-serif text-4xl md:text-5xl font-bold tracking-tight">About</h1> <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"> <p className="mt-3 text-[#9ca3af] text-base max-w-2xl">
An independent trade publication covering the convergence of pro AV and IT broadcast, live production, streaming, and post-production technology. An independent trade publication covering broadcast, live production, streaming, and post-production technology.
</p> </p>
</header> </header>
@@ -32,11 +32,11 @@ export default function AboutPage() {
<section className="space-y-6 text-[#d1d5db] text-[15px] leading-relaxed"> <section className="space-y-6 text-[#d1d5db] text-[15px] leading-relaxed">
<Block title="What we cover"> <Block title="What we cover">
AV Beat reports on the equipment, services, and people that AV Beat reports on the equipment, services, and people
move the professional AV and IT-converged video industry that move the professional video industry forward. We follow
forward. We follow product launches, executive moves, product launches, executive moves, acquisitions, and the live
acquisitions, and the live production / IP / cloud workflows production / IP / cloud workflows reshaping how content gets
reshaping how content gets made and delivered. made and delivered.
</Block> </Block>
<Block title="Where we go"> <Block title="Where we go">
@@ -57,9 +57,9 @@ export default function AboutPage() {
</Block> </Block>
<Block title="Get involved"> <Block title="Get involved">
For editorial pitches: <a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="mailto:editor@avbeat.com">editor@avbeat.com</a>. For editorial pitches: <a className="text-[var(--color-text-info,#60a5fa)] 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 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,#E67E22)] hover:underline" href="/about/team">meet the editorial team</a>. {" "}For our team: <a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="/about/team">meet the editorial team</a>.
</Block> </Block>
</section> </section>
</main> </main>

View File

@@ -13,23 +13,23 @@ export default function PressKitPage() {
<section> <section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Logos</h2> <h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Logos</h2>
<ul className="space-y-1"> <ul className="space-y-1">
<li><a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="/assets/images/logo.png" download>AV Beat logo (PNG)</a></li> <li><a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="/assets/images/logo.png" download>AV Beat logo (PNG)</a></li>
</ul> </ul>
</section> </section>
<section> <section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Boilerplate</h2> <h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Boilerplate</h2>
<p className="text-[#d1d5db] leading-relaxed"> <p className="text-[#d1d5db] leading-relaxed">
AV Beat is an independent trade publication covering the convergence AV Beat is an independent trade publication covering broadcast,
of pro AV and IT broadcast, live production, streaming, and live production, streaming, and post-production technology. Founded
post-production technology. Founded to bring engineering-rigor to bring engineering-rigor reporting to industry coverage, AV Beat
reporting to industry coverage, AV Beat publishes daily on the publishes daily on the equipment, services, and people that move
equipment, services, and people moving professional video forward. professional video forward.
</p> </p>
</section> </section>
<section> <section>
<h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Media contact</h2> <h2 className="font-mono text-xs uppercase tracking-wider text-[#6b7280] mb-2">Media contact</h2>
<p> <p>
<a className="text-[var(--color-text-info,#E67E22)] hover:underline" href="mailto:ryan.salazar@relevantmediaproperties.com">ryan.salazar@relevantmediaproperties.com</a> <a className="text-[var(--color-text-info,#60a5fa)] hover:underline" href="mailto:ryan.salazar@relevantmediaproperties.com">ryan.salazar@relevantmediaproperties.com</a>
</p> </p>
</section> </section>
</div> </div>

View File

@@ -9,7 +9,7 @@ export const revalidate = 600;
export const metadata = { export const metadata = {
title: "Our team — AV Beat", title: "Our team — AV Beat",
description: "Meet the AV Beat team — editorial staff plus accounts and support, covering the convergence of pro AV and IT.", description: "Meet the AV Beat team — editorial staff plus accounts and support, covering broadcast, streaming, and post-production.",
}; };
interface Persona { interface Persona {
@@ -63,7 +63,7 @@ export default async function TeamPage() {
.from("personas") .from("personas")
.select("id, display_name, role, email, phone, extension, avatar_url") .select("id, display_name, role, email, phone, extension, avatar_url")
.eq("active", true) .eq("active", true)
.in("id", ["chloe", "jeff", "riley"]); .in("id", ["chloe", "jeff", "riley", "brian", "avery"]);
const ops = (opsData || []) as OpsPersona[]; const ops = (opsData || []) as OpsPersona[];
const displayBeat = (p: Persona) => const displayBeat = (p: Persona) =>
@@ -98,10 +98,10 @@ export default async function TeamPage() {
<main> <main>
{/* Hero */} {/* Hero */}
<header className="relative overflow-hidden rounded-xl mb-10 border border-[#1a1f2a] bg-gradient-to-br from-[#0d1118] via-[#0b0f17] to-[#0d1118]"> <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,#E67E22,transparent_60%)]" aria-hidden /> <div className="absolute inset-0 opacity-[0.04] bg-[radial-gradient(ellipse_at_top_right,#d60701,transparent_60%)]" aria-hidden />
<div className="relative px-6 py-10 sm:px-10 sm:py-14"> <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-[#F0B07A] mb-3"> <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-[#E67E22]" /> <span className="w-6 h-px bg-[#d60701]" />
AV Beat &middot; editorial AV Beat &middot; editorial
</div> </div>
<h1 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight"> <h1 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight">
@@ -129,10 +129,10 @@ export default async function TeamPage() {
return ( return (
<li <li
key={p.slug} key={p.slug}
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" 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"
> >
{/* Top accent */} {/* Top accent */}
<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="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="flex items-start gap-4"> <div className="flex items-start gap-4">
{p.avatar_url ? ( {p.avatar_url ? (
@@ -142,7 +142,7 @@ export default async function TeamPage() {
alt={p.name} alt={p.name}
width={96} width={96}
height={96} height={96}
className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] group-hover:border-[#E67E22]/40 transition-colors object-cover" className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] group-hover:border-[#d60701]/40 transition-colors object-cover"
/> />
</a> </a>
) : ( ) : (
@@ -155,7 +155,7 @@ export default async function TeamPage() {
<h3 className="font-serif text-xl font-bold leading-tight">{p.name}</h3> <h3 className="font-serif text-xl font-bold leading-tight">{p.name}</h3>
</a> </a>
{(p.beat || p.slug === "ryan-salazar") && ( {(p.beat || p.slug === "ryan-salazar") && (
<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]"> <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]">
{displayBeat(p)} {displayBeat(p)}
</div> </div>
)} )}
@@ -173,11 +173,11 @@ export default async function TeamPage() {
<ObfuscatedEmail <ObfuscatedEmail
user={e.user} user={e.user}
domain={e.domain} domain={e.domain}
className="text-[#aab1bd] hover:text-[#F0B07A] font-mono" className="text-[#aab1bd] hover:text-[#ff6b66] font-mono"
/> />
<a <a
href={`/authors/${p.slug}`} href={`/authors/${p.slug}`}
className="text-[10px] font-mono uppercase tracking-[0.12em] text-[#6c7686] hover:text-[#F0B07A]" className="text-[10px] font-mono uppercase tracking-[0.12em] text-[#6c7686] hover:text-[#ff6b66]"
> >
View View
</a> </a>
@@ -209,7 +209,7 @@ export default async function TeamPage() {
{ops.map((p) => ( {ops.map((p) => (
<li <li
key={p.id} key={p.id}
className="rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 flex gap-4 hover:border-[#E67E22]/40 transition-colors" className="rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 flex gap-4 hover:border-[#d60701]/40 transition-colors"
> >
{p.avatar_url ? ( {p.avatar_url ? (
<img <img
@@ -235,13 +235,13 @@ export default async function TeamPage() {
if (!e) return null; if (!e) return null;
return ( return (
<div className="font-mono"> <div className="font-mono">
<ObfuscatedEmail user={e.user} domain={e.domain} className="hover:text-[#F0B07A]" /> <ObfuscatedEmail user={e.user} domain={e.domain} className="hover:text-[#ff6b66]" />
</div> </div>
); );
})()} })()}
{p.phone && ( {p.phone && (
<div> <div>
<a href={`tel:${p.phone.replace(/[^\d+]/g, '')}`} className="hover:text-[#F0B07A] font-mono"> <a href={`tel:${p.phone.replace(/[^\d+]/g, '')}`} className="hover:text-[#ff6b66] font-mono">
{p.phone}{p.extension ? ` ext. ${p.extension}` : ''} {p.phone}{p.extension ? ` ext. ${p.extension}` : ''}
</a> </a>
</div> </div>

View File

@@ -4,7 +4,7 @@ import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
const SITES = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering']; const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
const STATUSES = ['active','completed','cancelled']; const STATUSES = ['active','completed','cancelled'];
const SCHEDULES = ['single','monthly','quarterly']; const SCHEDULES = ['single','monthly','quarterly'];

View File

@@ -6,7 +6,7 @@ import { useRouter } from 'next/navigation';
function fmt(cents: number) { return '$' + (cents / 100).toLocaleString('en-US', { minimumFractionDigits: 2 }); } function fmt(cents: number) { return '$' + (cents / 100).toLocaleString('en-US', { minimumFractionDigits: 2 }); }
const SITES = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering']; const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
export default function ReportsPage() { export default function ReportsPage() {
const { user, loading } = useAuth(); const { user, loading } = useAuth();

View File

@@ -98,7 +98,7 @@ export default function AdOpsIOsPage() {
</select> </select>
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{['broadcastbeat','avbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)} {['avbeat','avbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>

View File

@@ -75,7 +75,7 @@ export default function AdOpsDashboardPage() {
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{['broadcastbeat','avbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)} {['avbeat','avbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none"> <select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Statuses</option> <option value="">All Statuses</option>

View File

@@ -4,7 +4,7 @@ import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
const SITES = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering']; const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
const PRODUCT_TYPES = [ const PRODUCT_TYPES = [
'display_banner_300x250','display_banner_728x90','display_banner_300x600', 'display_banner_300x250','display_banner_728x90','display_banner_300x600',
'display_banner_970x250','newsletter_banner_600x100','dedicated_email_blast', 'display_banner_970x250','newsletter_banner_600x100','dedicated_email_blast',

View File

@@ -9,7 +9,7 @@ interface BannedTerm {
id: string; id: string;
term: string; term: string;
ban_level: 'soft' | 'hard'; ban_level: 'soft' | 'hard';
site_scope: 'broadcastbeat' | 'avbeat' | 'both'; site_scope: 'avbeat' | 'avbeat' | 'both';
is_active: boolean; is_active: boolean;
notes: string | null; notes: string | null;
created_at: string; created_at: string;
@@ -19,7 +19,7 @@ interface BannedTerm {
interface FormState { interface FormState {
term: string; term: string;
ban_level: 'soft' | 'hard'; ban_level: 'soft' | 'hard';
site_scope: 'broadcastbeat' | 'avbeat' | 'both'; site_scope: 'avbeat' | 'avbeat' | 'both';
notes: string; notes: string;
} }
@@ -255,7 +255,7 @@ export default function BannedTermsPage() {
<select value={form.site_scope} onChange={e => setForm(f => ({ ...f, site_scope: e.target.value as any }))} <select value={form.site_scope} onChange={e => setForm(f => ({ ...f, site_scope: e.target.value as any }))}
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#3b82f6]"> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#3b82f6]">
<option value="both">Both Sites</option> <option value="both">Both Sites</option>
<option value="broadcastbeat">Broadcast Beat only</option> <option value="avbeat">AV Beat only</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
</select> </select>
</div> </div>
@@ -291,7 +291,7 @@ export default function BannedTermsPage() {
<select value={filterSiteScope} onChange={e => setFilterSiteScope(e.target.value)} <select value={filterSiteScope} onChange={e => setFilterSiteScope(e.target.value)}
className="bg-[#111] border border-[#252525] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#3b82f6]"> className="bg-[#111] border border-[#252525] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#3b82f6]">
<option value="">All Sites</option> <option value="">All Sites</option>
<option value="broadcastbeat">Broadcast Beat</option> <option value="avbeat">AV Beat</option>
<option value="avbeat">AV Beat</option> <option value="avbeat">AV Beat</option>
<option value="both">Both</option> <option value="both">Both</option>
</select> </select>
@@ -336,7 +336,7 @@ export default function BannedTermsPage() {
</span> </span>
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className="text-xs text-[#888] font-body capitalize">{term.site_scope === 'both' ? 'Both Sites' : term.site_scope === 'broadcastbeat' ? 'Broadcast Beat' : 'AV Beat'}</span> <span className="text-xs text-[#888] font-body capitalize">{term.site_scope === 'both' ? 'Both Sites' : term.site_scope === 'avbeat' ? 'AV Beat' : 'AV Beat'}</span>
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className={`text-xs px-2 py-0.5 rounded-full font-body ${term.is_active ? 'bg-green-500/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}> <span className={`text-xs px-2 py-0.5 rounded-full font-body ${term.is_active ? 'bg-green-500/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>
@@ -379,7 +379,7 @@ export default function BannedTermsPage() {
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<span className="text-xs px-2 py-0.5 rounded-full bg-red-500/10 text-red-400 font-bold">Hard Ban</span> <span className="text-xs px-2 py-0.5 rounded-full bg-red-500/10 text-red-400 font-bold">Hard Ban</span>
</div> </div>
<p className="text-xs text-[#555] font-body">Submission is blocked. Post saved as draft automatically. Contributor sees only: "This post could not be submitted. Please contact the editorial team at editor@broadcastbeat.com for assistance." Admin receives email notification.</p> <p className="text-xs text-[#555] font-body">Submission is blocked. Post saved as draft automatically. Contributor sees only: "This post could not be submitted. Please contact the editorial team at editor@avbeat.com for assistance." Admin receives email notification.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -68,7 +68,7 @@ type TabType = 'queue' | 'companies' | 'crawl-log' | 'settings';
type QueueStatus = 'generated' | 'pending' | 'approved' | 'published' | 'rejected'; type QueueStatus = 'generated' | 'pending' | 'approved' | 'published' | 'rejected';
const SITE_CONFIG = { const SITE_CONFIG = {
1: { name: 'Broadcast Beat', badge: 'BB', bgClass: 'bg-[#0ea5e9]/10', textClass: 'text-[#0ea5e9]' }, 1: { name: 'AV Beat', badge: 'BB', bgClass: 'bg-[#0ea5e9]/10', textClass: 'text-[#0ea5e9]' },
2: { name: 'AV Beat', badge: 'AV', bgClass: 'bg-[#f97316]/10', textClass: 'text-[#f97316]' }, 2: { name: 'AV Beat', badge: 'AV', bgClass: 'bg-[#f97316]/10', textClass: 'text-[#f97316]' },
}; };
@@ -275,7 +275,7 @@ export default function CompanyTrackerPage() {
<span className="text-[#888] text-sm font-body">Company Tracker</span> <span className="text-[#888] text-sm font-body">Company Tracker</span>
</div> </div>
<h1 className="text-2xl font-display font-bold text-white">Company Story Engine</h1> <h1 className="text-2xl font-display font-bold text-white">Company Story Engine</h1>
<p className="text-[#555] text-sm font-body mt-1">Automated story generation from company mentions across Broadcast Beat & AV Beat</p> <p className="text-[#555] text-sm font-body mt-1">Automated story generation from company mentions across AV Beat & AV Beat</p>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Auto-Pilot toggle */} {/* Auto-Pilot toggle */}
@@ -344,7 +344,7 @@ export default function CompanyTrackerPage() {
<select value={queueSiteFilter} onChange={e => setQueueSiteFilter(e.target.value)} <select value={queueSiteFilter} onChange={e => setQueueSiteFilter(e.target.value)}
className="bg-[#111] border border-[#252525] rounded-lg px-3 py-1.5 text-[#555] text-xs font-body focus:outline-none"> className="bg-[#111] border border-[#252525] rounded-lg px-3 py-1.5 text-[#555] text-xs font-body focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
<option value="1">Broadcast Beat</option> <option value="1">AV Beat</option>
<option value="2">AV Beat</option> <option value="2">AV Beat</option>
</select> </select>
</div> </div>
@@ -412,7 +412,7 @@ export default function CompanyTrackerPage() {
<select onChange={e => handleQueueAction(item.id, 'reassign_site', { site_id: parseInt(e.target.value) })} <select onChange={e => handleQueueAction(item.id, 'reassign_site', { site_id: parseInt(e.target.value) })}
defaultValue={item.site_id} defaultValue={item.site_id}
className="bg-[#111] border border-[#333] rounded px-2 py-1 text-xs text-white font-body focus:outline-none"> className="bg-[#111] border border-[#333] rounded px-2 py-1 text-xs text-white font-body focus:outline-none">
<option value={1}>Broadcast Beat</option> <option value={1}>AV Beat</option>
<option value={2}>AV Beat</option> <option value={2}>AV Beat</option>
</select> </select>
</div> </div>
@@ -455,7 +455,7 @@ export default function CompanyTrackerPage() {
<select value={addCompanyForm.site_scope} onChange={e => setAddCompanyForm(f => ({ ...f, site_scope: e.target.value }))} <select value={addCompanyForm.site_scope} onChange={e => setAddCompanyForm(f => ({ ...f, site_scope: e.target.value }))}
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#3b82f6]"> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#3b82f6]">
<option value="both">Both Sites</option> <option value="both">Both Sites</option>
<option value="broadcastbeat">Broadcast Beat only</option> <option value="avbeat">AV Beat only</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
</select> </select>
</div> </div>
@@ -494,7 +494,7 @@ export default function CompanyTrackerPage() {
<td className="px-4 py-3 text-sm font-body font-semibold text-white">{company.company_name}</td> <td className="px-4 py-3 text-sm font-body font-semibold text-white">{company.company_name}</td>
<td className="px-4 py-3 text-xs text-[#555] font-body">{company.company_website ? <a href={`https://${company.company_website}`} target="_blank" rel="noopener noreferrer" className="text-[#3b82f6] hover:underline">{company.company_website}</a> : '—'}</td> <td className="px-4 py-3 text-xs text-[#555] font-body">{company.company_website ? <a href={`https://${company.company_website}`} target="_blank" rel="noopener noreferrer" className="text-[#3b82f6] hover:underline">{company.company_website}</a> : '—'}</td>
<td className="px-4 py-3 text-sm font-body text-white font-bold">{company.mention_count}</td> <td className="px-4 py-3 text-sm font-body text-white font-bold">{company.mention_count}</td>
<td className="px-4 py-3 text-xs text-[#888] font-body capitalize">{company.site_scope === 'both' ? 'Both' : company.site_scope === 'broadcastbeat' ? 'BB' : 'AV'}</td> <td className="px-4 py-3 text-xs text-[#888] font-body capitalize">{company.site_scope === 'both' ? 'Both' : company.site_scope === 'avbeat' ? 'BB' : 'AV'}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<button onClick={() => handleToggleCompanyAutoStory(company.id, !company.auto_story_enabled)} disabled={actionLoading === company.id} <button onClick={() => handleToggleCompanyAutoStory(company.id, !company.auto_story_enabled)} disabled={actionLoading === company.id}
className={`relative w-9 h-5 rounded-full transition-colors ${company.auto_story_enabled ? 'bg-[#3b82f6]' : 'bg-[#333]'}`}> className={`relative w-9 h-5 rounded-full transition-colors ${company.auto_story_enabled ? 'bg-[#3b82f6]' : 'bg-[#333]'}`}>

View File

@@ -47,7 +47,7 @@ interface LaunchChecklist {
// ─── Site labels ────────────────────────────────────────────────────────────── // ─── Site labels ──────────────────────────────────────────────────────────────
const SITE_LABELS: Record<string, { label: string; color: string; target: string }> = { const SITE_LABELS: Record<string, { label: string; color: string; target: string }> = {
'broadcast-beat': { label: 'Broadcast Beat', color: 'text-blue-400', target: '1525/day' }, 'broadcast-beat': { label: 'AV Beat', color: 'text-blue-400', target: '1525/day' },
'av-beat': { label: 'AV Beat', color: 'text-purple-400', target: '1015/day' }, 'av-beat': { label: 'AV Beat', color: 'text-purple-400', target: '1015/day' },
'backlot-beat': { label: 'Backlot Beat', color: 'text-amber-400', target: '1015/day' }, 'backlot-beat': { label: 'Backlot Beat', color: 'text-amber-400', target: '1015/day' },
'broadcast-engineering': { label: 'BroadcastEngineering.com', color: 'text-green-400', target: '1015/day' }, 'broadcast-engineering': { label: 'BroadcastEngineering.com', color: 'text-green-400', target: '1015/day' },

View File

@@ -4,7 +4,7 @@ import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
const SITES = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering']; const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
function DnsRow({ label, valid, checkedAt }: { label: string; valid: boolean; checkedAt: string | null }) { function DnsRow({ label, valid, checkedAt }: { label: string; valid: boolean; checkedAt: string | null }) {
return ( return (

View File

@@ -4,7 +4,7 @@ import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
const SITES = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering']; const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
function DnsCheck({ label, valid }: { label: string; valid: boolean | null }) { function DnsCheck({ label, valid }: { label: string; valid: boolean | null }) {
return ( return (

View File

@@ -4,7 +4,7 @@ import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
const SITES = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering']; const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
export default function SendLogPage() { export default function SendLogPage() {
const { user, loading } = useAuth(); const { user, loading } = useAuth();

View File

@@ -4,13 +4,13 @@ import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
const SITES = ['broadcastbeat','avbeat','backlotbeat','broadcastengineering']; const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
const SUPP_TYPES = ['unsubscribe','bounce_hard','bounce_soft','spam_complaint','hostile_reply','manual']; const SUPP_TYPES = ['unsubscribe','bounce_hard','bounce_soft','spam_complaint','hostile_reply','manual'];
export default function SuppressionsPage() { export default function SuppressionsPage() {
const { user, loading } = useAuth(); const { user, loading } = useAuth();
const router = useRouter(); const router = useRouter();
const [site, setSite] = useState('broadcastbeat'); const [site, setSite] = useState('avbeat');
const [suppressions, setSuppressions] = useState<any[]>([]); const [suppressions, setSuppressions] = useState<any[]>([]);
const [keywords, setKeywords] = useState<any[]>([]); const [keywords, setKeywords] = useState<any[]>([]);
const [stats, setStats] = useState<any>(null); const [stats, setStats] = useState<any>(null);

View File

@@ -158,8 +158,8 @@ export default function WPImportPage() {
<h1 className="text-2xl font-bold text-white font-heading">WordPress Post Importer</h1> <h1 className="text-2xl font-bold text-white font-heading">WordPress Post Importer</h1>
<p className="text-[#666] text-sm mt-1"> <p className="text-[#666] text-sm mt-1">
Import posts from{' '} Import posts from{' '}
<a href="https://www.broadcastbeat.com" target="_blank" rel="noopener noreferrer" className="text-[#3b82f6] hover:underline"> <a href="https://www.avbeat.com" target="_blank" rel="noopener noreferrer" className="text-[#3b82f6] hover:underline">
broadcastbeat.com avbeat.com
</a>{' '} </a>{' '}
via WordPress REST API via WordPress REST API
</p> </p>
@@ -228,7 +228,7 @@ export default function WPImportPage() {
<div className="w-5 h-5 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin flex-shrink-0" /> <div className="w-5 h-5 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin flex-shrink-0" />
<div> <div>
<p className="text-[#3b82f6] text-sm font-bold">Import in progress</p> <p className="text-[#3b82f6] text-sm font-bold">Import in progress</p>
<p className="text-[#666] text-xs mt-0.5">Fetching posts from broadcastbeat.com and saving to database...</p> <p className="text-[#666] text-xs mt-0.5">Fetching posts from avbeat.com and saving to database...</p>
</div> </div>
</div> </div>
<div className="mt-3 h-1.5 bg-[#1a2535] rounded-full overflow-hidden"> <div className="mt-3 h-1.5 bg-[#1a2535] rounded-full overflow-hidden">
@@ -365,11 +365,11 @@ export default function WPImportPage() {
</div> </div>
<div className="flex items-center gap-2 flex-shrink-0"> <div className="flex items-center gap-2 flex-shrink-0">
<a <a
href={`https://www.broadcastbeat.com/${post.wp_slug}`} href={`https://www.avbeat.com/${post.wp_slug}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#555] hover:text-[#3b82f6] transition-colors p-1" className="text-[#555] hover:text-[#3b82f6] transition-colors p-1"
title="View on broadcastbeat.com"> title="View on avbeat.com">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />
</svg> </svg>

View File

@@ -632,7 +632,7 @@ export default function CampaignEditorPage() {
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs font-medium text-white">Broadcast Beat</span> <span className="text-xs font-medium text-white">AV Beat</span>
<span className="text-[10px] text-[#444]">now</span> <span className="text-[10px] text-[#444]">now</span>
</div> </div>
<p className="text-xs text-[#888] truncate">{campaign.subject || 'Your subject line…'}</p> <p className="text-xs text-[#888] truncate">{campaign.subject || 'Your subject line…'}</p>
@@ -645,7 +645,7 @@ export default function CampaignEditorPage() {
<div className="p-4 space-y-3 max-h-[400px] overflow-y-auto"> <div className="p-4 space-y-3 max-h-[400px] overflow-y-auto">
{/* Header */} {/* Header */}
<div className="bg-[#0a0a0a] rounded-lg p-3 text-center border border-[#1a1a1a]"> <div className="bg-[#0a0a0a] rounded-lg p-3 text-center border border-[#1a1a1a]">
<p className="text-xs font-bold text-white tracking-widest uppercase">Broadcast Beat</p> <p className="text-xs font-bold text-white tracking-widest uppercase">AV Beat</p>
{campaign.previewText && ( {campaign.previewText && (
<p className="text-[10px] text-[#555] mt-1">{campaign.previewText}</p> <p className="text-[10px] text-[#555] mt-1">{campaign.previewText}</p>
)} )}
@@ -680,7 +680,7 @@ export default function CampaignEditorPage() {
{/* Footer */} {/* Footer */}
<div className="text-center pt-2 border-t border-[#1a1a1a]"> <div className="text-center pt-2 border-t border-[#1a1a1a]">
<p className="text-[9px] text-[#333]">Broadcast Beat · Unsubscribe · View in browser</p> <p className="text-[9px] text-[#333]">AV Beat · Unsubscribe · View in browser</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -689,7 +689,7 @@ export default function AdminNewsletterPage() {
type="text" type="text"
value={digestSubject} value={digestSubject}
onChange={(e) => setDigestSubject(e.target.value)} onChange={(e) => setDigestSubject(e.target.value)}
placeholder="e.g. Broadcast Beat Weekly — NAB Show Highlights" placeholder="e.g. AV Beat Weekly — NAB Show Highlights"
className="search-input w-full py-2.5 px-3 text-sm" className="search-input w-full py-2.5 px-3 text-sm"
/> />
</div> </div>

View File

@@ -435,7 +435,7 @@ export default function NewsletterTemplatesPage() {
type="text" type="text"
value={form.subject_prefix} value={form.subject_prefix}
onChange={(e) => setForm((f) => ({ ...f, subject_prefix: e.target.value }))} onChange={(e) => setForm((f) => ({ ...f, subject_prefix: e.target.value }))}
placeholder="e.g. Broadcast Beat Weekly —" placeholder="e.g. AV Beat Weekly —"
className="search-input w-full py-2.5 px-3 text-sm" className="search-input w-full py-2.5 px-3 text-sm"
/> />
<p className="text-xs text-[#555] font-body mt-1">Prepended to the digest subject line when using this template</p> <p className="text-xs text-[#555] font-body mt-1">Prepended to the digest subject line when using this template</p>

View File

@@ -69,7 +69,7 @@ export default function AdvertisePage() {
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{stats.map((stat) => ( {stats.map((stat) => (
<div key={stat.label} className="bg-[#111] border border-[#222] p-5 text-center"> <div key={stat.label} className="bg-[#111] border border-[#222] p-5 text-center">
<div className="font-heading text-[#E67E22] text-2xl font-bold mb-1">{stat.value}</div> <div className="font-heading text-[#3b82f6] text-2xl font-bold mb-1">{stat.value}</div>
<div className="font-body text-[#777] text-xs uppercase tracking-wide">{stat.label}</div> <div className="font-body text-[#777] text-xs uppercase tracking-wide">{stat.label}</div>
</div> </div>
))} ))}
@@ -86,10 +86,10 @@ export default function AdvertisePage() {
<table className="w-full text-sm font-body"> <table className="w-full text-sm font-body">
<thead> <thead>
<tr className="border-b border-[#222]"> <tr className="border-b border-[#222]">
<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-[#3b82f6] 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-[#3b82f6] 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-[#3b82f6] 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> <th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Rate</th>
</tr> </tr>
</thead> </thead>
<tbody> <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-[#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-[#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-[#777]">{zone.placement}</td>
<td className="py-3 px-4 text-[#E67E22] font-medium">{zone.cpm}</td> <td className="py-3 px-4 text-[#3b82f6] font-medium">{zone.cpm}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@@ -123,23 +123,23 @@ export default function AdvertisePage() {
<h3 className="font-heading text-[#e0e0e0] text-lg font-bold mb-4">Sales Contact</h3> <h3 className="font-heading text-[#e0e0e0] text-lg font-bold mb-4">Sales Contact</h3>
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<p className="font-body text-[#E67E22] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p> <p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p>
<p className="font-body text-[#e0e0e0] font-medium">Jeff Victor</p> <p className="font-body text-[#e0e0e0] font-medium">Jeff Victor</p>
<a href="mailto:jeff@avbeat.com" className="font-body text-[#E67E22] text-sm hover:underline"> <a href="mailto:jeff@avbeat.com" className="font-body text-[#3b82f6] text-sm hover:underline">
jeff@avbeat.com jeff@avbeat.com
</a> </a>
</div> </div>
<div className="border-t border-[#222] pt-4"> <div className="border-t border-[#222] pt-4">
<p className="font-body text-[#E67E22] text-xs font-bold uppercase tracking-wider mb-1">Publication</p> <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">AV Beat</p> <p className="font-body text-[#777] text-sm">AV Beat</p>
<p className="font-body text-[#777] text-sm">Relevant Media Properties</p> <p className="font-body text-[#777] text-sm">Relevant Media Properties</p>
</div> </div>
<div className="border-t border-[#222] pt-4"> <div className="border-t border-[#222] pt-4">
<p className="font-body text-[#E67E22] text-xs font-bold uppercase tracking-wider mb-2">What We Offer</p> <p className="font-body text-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-2">What We Offer</p>
<ul className="space-y-1.5"> <ul className="space-y-1.5">
{["Display advertising (web)", "Newsletter sponsorships", "Sponsored content", "Event coverage partnerships", "Podcast sponsorships"].map((item) => ( {["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"> <li key={item} className="flex items-start gap-2 text-[#777] text-sm font-body">
<span className="text-[#E67E22] mt-0.5"></span> <span className="text-[#3b82f6] mt-0.5"></span>
{item} {item}
</li> </li>
))} ))}
@@ -152,16 +152,16 @@ export default function AdvertisePage() {
{/* Form */} {/* Form */}
<div className="lg:col-span-8"> <div className="lg:col-span-8">
{submitted ? ( {submitted ? (
<div className="bg-[#111] border border-[#E67E22] p-8 text-center"> <div className="bg-[#111] border border-[#3b82f6] p-8 text-center">
<div className="w-12 h-12 rounded-full bg-[#E67E22]/20 flex items-center justify-center mx-auto mb-4"> <div className="w-12 h-12 rounded-full bg-[#3b82f6]/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"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M5 12l4 4 10-10" stroke="#E67E22" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M5 12l4 4 10-10" stroke="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<h3 className="font-heading text-[#e0e0e0] text-xl font-bold mb-2">Message Sent!</h3> <h3 className="font-heading text-[#e0e0e0] text-xl font-bold mb-2">Message Sent!</h3>
<p className="font-body text-[#777] text-sm"> <p className="font-body text-[#777] text-sm">
Thanks for reaching out. Jeff will be in touch within one business day at{" "} Thanks for reaching out. Jeff will be in touch within one business day at{" "}
<a href="mailto:jeff@avbeat.com" className="text-[#E67E22] hover:underline">jeff@avbeat.com</a>. <a href="mailto:jeff@avbeat.com" className="text-[#3b82f6] hover:underline">jeff@avbeat.com</a>.
</p> </p>
</div> </div>
) : ( ) : (
@@ -245,7 +245,7 @@ export default function AdvertisePage() {
{loading ? "Sending..." : "Send Inquiry"} {loading ? "Sending..." : "Send Inquiry"}
</button> </button>
<p className="text-[#555] text-xs font-body"> <p className="text-[#555] text-xs font-body">
Or email directly: <a href="mailto:jeff@avbeat.com" className="text-[#E67E22] hover:underline">jeff@avbeat.com</a> Or email directly: <a href="mailto:jeff@avbeat.com" className="text-[#3b82f6] hover:underline">jeff@avbeat.com</a>
</p> </p>
</form> </form>
)} )}

View File

@@ -56,7 +56,7 @@ export async function GET(request: NextRequest) {
.select('site, amount_cents, status, paid_date') .select('site, amount_cents, status, paid_date')
.gte('created_at', yearStart); .gte('created_at', yearStart);
const sites = ['broadcastbeat', 'avbeat', 'backlotbeat', 'broadcastengineering']; const sites = ['avbeat', 'avbeat', 'backlotbeat', 'broadcastengineering'];
const siteRevenue = sites.map(site => { const siteRevenue = sites.map(site => {
const siteData = (siteInvoices ?? []).filter((i: any) => i.site === site); const siteData = (siteInvoices ?? []).filter((i: any) => i.site === site);
const revenue_ytd = siteData.filter((i: any) => i.status === 'paid').reduce((s: number, r: any) => s + (r.amount_cents ?? 0), 0); const revenue_ytd = siteData.filter((i: any) => i.status === 'paid').reduce((s: number, r: any) => s + (r.amount_cents ?? 0), 0);

View File

@@ -81,7 +81,7 @@ export async function POST(req: NextRequest) {
// Trigger article publish notification if published immediately // Trigger article publish notification if published immediately
if (status === 'published' && data) { if (status === 'published' && data) {
try { try {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
fetch(`${siteUrl}/api/newsletter/notify-article`, { fetch(`${siteUrl}/api/newsletter/notify-article`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -152,7 +152,7 @@ export async function PATCH(req: NextRequest) {
.single(); .single();
if (articleData) { if (articleData) {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
// Fire-and-forget: call notify-article route internally // Fire-and-forget: call notify-article route internally
fetch(`${siteUrl}/api/newsletter/notify-article`, { fetch(`${siteUrl}/api/newsletter/notify-article`, {
method: 'POST', method: 'POST',

View File

@@ -13,9 +13,9 @@ export async function POST(request: NextRequest) {
}); });
await transport.sendMail({ await transport.sendMail({
from: `"${process.env.SMTP_FROM_NAME || 'Broadcast Beat'}" <${process.env.SMTP_FROM_EMAIL}>`, from: `"${process.env.SMTP_FROM_NAME || 'AV Beat'}" <${process.env.SMTP_FROM_EMAIL}>`,
to: 'editor@broadcastbeat.com', to: 'editor@avbeat.com',
subject: `[Broadcast Beat] Blocked post attempt — banned term triggered`, subject: `[AV Beat] Blocked post attempt — banned term triggered`,
html: ` html: `
<div style="font-family:Arial,sans-serif;max-width:600px;background:#0a0a0a;color:#e5e5e5;padding:32px;border-radius:8px;"> <div style="font-family:Arial,sans-serif;max-width:600px;background:#0a0a0a;color:#e5e5e5;padding:32px;border-radius:8px;">
<h2 style="color:#ef4444;margin:0 0 16px;">Blocked Post Attempt</h2> <h2 style="color:#ef4444;margin:0 0 16px;">Blocked Post Attempt</h2>

View File

@@ -30,7 +30,7 @@ export async function POST(request: NextRequest) {
const titleLower = (post.title || '').toLowerCase(); const titleLower = (post.title || '').toLowerCase();
for (const bt of bannedTerms) { for (const bt of bannedTerms) {
const termLower = bt.term.toLowerCase(); const termLower = bt.term.toLowerCase();
const siteMatch = bt.site_scope === 'both' || (bt.site_scope === 'broadcastbeat' && post.site_id === 1) || (bt.site_scope === 'avbeat' && post.site_id === 2); const siteMatch = bt.site_scope === 'both' || (bt.site_scope === 'avbeat' && post.site_id === 1) || (bt.site_scope === 'avbeat' && post.site_id === 2);
if (siteMatch && titleLower.includes(termLower)) { if (siteMatch && titleLower.includes(termLower)) {
matches.push({ title: post.title, author: post.author_name, published_at: post.published_at, site_id: post.site_id, matched_term: bt.term, ban_level: bt.ban_level }); matches.push({ title: post.title, author: post.author_name, published_at: post.published_at, site_id: post.site_id, matched_term: bt.term, ban_level: bt.ban_level });
break; // one match per post break; // one match per post

View File

@@ -13,14 +13,14 @@ export async function POST(request: NextRequest) {
const companyList = (companies || []).slice(0, 10).map((c: string) => `<li>${c}</li>`).join(''); const companyList = (companies || []).slice(0, 10).map((c: string) => `<li>${c}</li>`).join('');
const moreText = companies?.length > 10 ? `<p style="color:#888;font-size:13px;">...and ${companies.length - 10} more</p>` : ''; const moreText = companies?.length > 10 ? `<p style="color:#888;font-size:13px;">...and ${companies.length - 10} more</p>` : '';
await transport.sendMail({ await transport.sendMail({
from: `"${process.env.SMTP_FROM_NAME || 'Broadcast Beat'}" <${process.env.SMTP_FROM_EMAIL}>`, from: `"${process.env.SMTP_FROM_NAME || 'AV Beat'}" <${process.env.SMTP_FROM_EMAIL}>`,
to: email || 'ryan.salazar@relevantmediaproperties.com', to: email || 'ryan.salazar@relevantmediaproperties.com',
subject: `[Relevant Media Properties] ${count} new company story suggestion${count !== 1 ? 's' : ''} queued`, subject: `[Relevant Media Properties] ${count} new company story suggestion${count !== 1 ? 's' : ''} queued`,
html: ` html: `
<div style="font-family:Arial,sans-serif;max-width:600px;background:#0a0a0a;color:#e5e5e5;padding:32px;border-radius:8px;"> <div style="font-family:Arial,sans-serif;max-width:600px;background:#0a0a0a;color:#e5e5e5;padding:32px;border-radius:8px;">
<div style="margin-bottom:24px;"> <div style="margin-bottom:24px;">
<h1 style="color:#3b82f6;font-size:20px;margin:0 0 4px;">Relevant Media Properties</h1> <h1 style="color:#3b82f6;font-size:20px;margin:0 0 4px;">Relevant Media Properties</h1>
<p style="color:#555;font-size:12px;margin:0;">Broadcast Beat · AV Beat — Company Story Engine</p> <p style="color:#555;font-size:12px;margin:0;">AV Beat · AV Beat — Company Story Engine</p>
</div> </div>
<h2 style="font-size:18px;color:#fff;margin:0 0 12px;">New Company Stories Queued for Review</h2> <h2 style="font-size:18px;color:#fff;margin:0 0 12px;">New Company Stories Queued for Review</h2>
<p style="color:#aaa;font-size:14px;line-height:1.6;margin:0 0 20px;"> <p style="color:#aaa;font-size:14px;line-height:1.6;margin:0 0 20px;">
@@ -31,12 +31,12 @@ export async function POST(request: NextRequest) {
<ul style="margin:0;padding-left:20px;color:#e5e5e5;font-size:14px;line-height:1.8;">${companyList}</ul> <ul style="margin:0;padding-left:20px;color:#e5e5e5;font-size:14px;line-height:1.8;">${companyList}</ul>
${moreText} ${moreText}
</div> </div>
<a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'}/admin/company-tracker" <a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com'}/admin/company-tracker"
style="display:inline-block;background:#3b82f6;color:#fff;text-decoration:none;padding:10px 20px;border-radius:6px;font-size:14px;font-weight:bold;"> style="display:inline-block;background:#3b82f6;color:#fff;text-decoration:none;padding:10px 20px;border-radius:6px;font-size:14px;font-weight:bold;">
Review Story Queue → Review Story Queue →
</a> </a>
<p style="color:#444;font-size:12px;margin-top:24px;"> <p style="color:#444;font-size:12px;margin-top:24px;">
Manage settings at <a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'}/admin/company-tracker" style="color:#3b82f6;">Company Tracker</a> Manage settings at <a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com'}/admin/company-tracker" style="color:#3b82f6;">Company Tracker</a>
</p> </p>
</div> </div>
`, `,

View File

@@ -23,7 +23,7 @@ function pickPenName(siteId: number, recentNames: string[]): string {
const available = pool.filter(n => !recentNames.slice(-3).includes(n)); const available = pool.filter(n => !recentNames.slice(-3).includes(n));
return available[Math.floor(Math.random() * available.length)] || pool[0]; return available[Math.floor(Math.random() * available.length)] || pool[0];
} }
// Broadcast Beat: fixed byline (Ryan) unless overridden to "rotate". // AV Beat: fixed byline (Ryan) unless overridden to "rotate".
if (BB_AUTOSTORY_BYLINE.toLowerCase() === 'rotate') { if (BB_AUTOSTORY_BYLINE.toLowerCase() === 'rotate') {
const pool = BB_PEN_NAMES_LEGACY; const pool = BB_PEN_NAMES_LEGACY;
const available = pool.filter(n => !recentNames.slice(-3).includes(n)); const available = pool.filter(n => !recentNames.slice(-3).includes(n));
@@ -224,7 +224,7 @@ export async function POST(request: NextRequest) {
if ((count || 0) >= 3) continue; // max 3 per week if ((count || 0) >= 3) continue; // max 3 per week
const siteId = article.site_id || 1; const siteId = article.site_id || 1;
const siteName = siteId === 2 ? 'AV Beat' : 'Broadcast Beat'; const siteName = siteId === 2 ? 'AV Beat' : 'AV Beat';
const penName = pickPenName(siteId, recentPenNames); const penName = pickPenName(siteId, recentPenNames);
recentPenNames.push(penName); recentPenNames.push(penName);

View File

@@ -432,7 +432,7 @@ export async function POST(request: NextRequest) {
const penName = pickPenNameForShow(site_id, story_type, recentNames, preferred_pen_name); const penName = pickPenNameForShow(site_id, story_type, recentNames, preferred_pen_name);
const penNameData = (site_id === 2 ? AV_PEN_NAMES : BB_PEN_NAMES).find(p => p.name === penName); const penNameData = (site_id === 2 ? AV_PEN_NAMES : BB_PEN_NAMES).find(p => p.name === penName);
const phase = getShowPhase(event); const phase = getShowPhase(event);
const siteName = site_id === 2 ? 'AV Beat' : 'Broadcast Beat'; const siteName = site_id === 2 ? 'AV Beat' : 'AV Beat';
const story = await generateShowStory({ const story = await generateShowStory({
penName, penName,

View File

@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server'; import { createClient } from '@/lib/supabase/server';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
async function sendSuspensionEmail( async function sendSuspensionEmail(
email: string, email: string,
@@ -35,8 +35,8 @@ async function sendSuspensionEmail(
: ''; : '';
const subject = isBanned const subject = isBanned
? `Your Broadcast Beat account has been banned` ? `Your AV Beat account has been banned`
: `Your Broadcast Beat account has been suspended`; : `Your AV Beat account has been suspended`;
const html = ` const html = `
<!DOCTYPE html> <!DOCTYPE html>
@@ -45,7 +45,7 @@ async function sendSuspensionEmail(
<body style="background:#0d1117;color:#e5e7eb;font-family:system-ui,sans-serif;margin:0;padding:0;"> <body style="background:#0d1117;color:#e5e7eb;font-family:system-ui,sans-serif;margin:0;padding:0;">
<div style="max-width:560px;margin:40px auto;padding:32px;background:#111827;border:1px solid #1f2937;border-radius:12px;"> <div style="max-width:560px;margin:40px auto;padding:32px;background:#111827;border:1px solid #1f2937;border-radius:12px;">
<div style="margin-bottom:24px;"> <div style="margin-bottom:24px;">
<span style="font-size:24px;font-weight:800;color:#fff;">Broadcast Beat</span> <span style="font-size:24px;font-weight:800;color:#fff;">AV Beat</span>
</div> </div>
<div style="background:${isBanned ? '#7f1d1d' : '#78350f'};border:1px solid ${isBanned ? '#991b1b' : '#92400e'};border-radius:8px;padding:16px;margin-bottom:24px;"> <div style="background:${isBanned ? '#7f1d1d' : '#78350f'};border:1px solid ${isBanned ? '#991b1b' : '#92400e'};border-radius:8px;padding:16px;margin-bottom:24px;">
<p style="margin:0;font-size:14px;font-weight:700;color:${isBanned ? '#fca5a5' : '#fcd34d'};"> <p style="margin:0;font-size:14px;font-weight:700;color:${isBanned ? '#fca5a5' : '#fcd34d'};">
@@ -54,7 +54,7 @@ async function sendSuspensionEmail(
</div> </div>
<p style="color:#d1d5db;font-size:15px;line-height:1.6;">Hi ${fullName || 'there'},</p> <p style="color:#d1d5db;font-size:15px;line-height:1.6;">Hi ${fullName || 'there'},</p>
<p style="color:#d1d5db;font-size:15px;line-height:1.6;"> <p style="color:#d1d5db;font-size:15px;line-height:1.6;">
Your Broadcast Beat account has been <strong style="color:#fff;">${isBanned ? 'permanently banned' : `suspended ${durationText}`}</strong>. Your AV Beat account has been <strong style="color:#fff;">${isBanned ? 'permanently banned' : `suspended ${durationText}`}</strong>.
</p> </p>
<div style="background:#0d1117;border:1px solid #1f2937;border-radius:8px;padding:16px;margin:20px 0;"> <div style="background:#0d1117;border:1px solid #1f2937;border-radius:8px;padding:16px;margin:20px 0;">
<p style="margin:0 0 8px;color:#888;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;">Reason</p> <p style="margin:0 0 8px;color:#888;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;">Reason</p>
@@ -65,7 +65,7 @@ async function sendSuspensionEmail(
${isBanned ? 'This decision is final. If you believe this is an error, please contact our support team.' : 'During this period, you will not be able to post threads or replies. If you believe this is an error, please contact our support team.'} ${isBanned ? 'This decision is final. If you believe this is an error, please contact our support team.' : 'During this period, you will not be able to post threads or replies. If you believe this is an error, please contact our support team.'}
</p> </p>
<div style="margin-top:24px;padding-top:24px;border-top:1px solid #1f2937;"> <div style="margin-top:24px;padding-top:24px;border-top:1px solid #1f2937;">
<p style="margin:0;color:#555;font-size:12px;">Broadcast Beat &mdash; ${siteUrl}</p> <p style="margin:0;color:#555;font-size:12px;">AV Beat &mdash; ${siteUrl}</p>
</div> </div>
</div> </div>
</body> </body>
@@ -73,7 +73,7 @@ async function sendSuspensionEmail(
`; `;
await transporter.sendMail({ await transporter.sendMail({
from: `"${process.env.SMTP_FROM_NAME || 'Broadcast Beat'}" <${process.env.SMTP_FROM_EMAIL}>`, from: `"${process.env.SMTP_FROM_NAME || 'AV Beat'}" <${process.env.SMTP_FROM_EMAIL}>`,
to: email, to: email,
subject, subject,
html, html,

View File

@@ -107,7 +107,7 @@ export async function POST(req: NextRequest) {
// Send email via Supabase Edge Function // Send email via Supabase Edge Function
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
try { try {
const emailRes = await fetch(`${supabaseUrl}/functions/v1/send-contributor-invite`, { const emailRes = await fetch(`${supabaseUrl}/functions/v1/send-contributor-invite`, {
@@ -118,7 +118,7 @@ export async function POST(req: NextRequest) {
}, },
body: JSON.stringify({ body: JSON.stringify({
email, email,
inviterName: inviterProfile?.full_name || inviterProfile?.email || 'Broadcast Beat Admin', inviterName: inviterProfile?.full_name || inviterProfile?.email || 'AV Beat Admin',
role: role || 'contributor', role: role || 'contributor',
token, token,
message, message,

View File

@@ -39,7 +39,7 @@ export async function GET(request: NextRequest) {
</head> </head>
<body> <body>
<div class="site-header"> <div class="site-header">
<div class="site-logo">Broadcast Beat</div> <div class="site-logo">AV Beat</div>
<nav class="site-nav"> <nav class="site-nav">
<a href="#">News</a> <a href="#">News</a>
<a href="#">Reviews</a> <a href="#">Reviews</a>

View File

@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
date: a.date, date: a.date,
})); }));
const systemPrompt = `You are a personalized content recommendation engine for Broadcast Beat, a broadcast engineering news platform. const systemPrompt = `You are a personalized content recommendation engine for AV Beat, a broadcast engineering news platform.
Given a reader's topic interests and reading history, select the 4 most relevant articles from the provided article list. Given a reader's topic interests and reading history, select the 4 most relevant articles from the provided article list.
Return ONLY a valid JSON array of slugs in order of relevance. Example: ["slug-1","slug-2","slug-3","slug-4"] Return ONLY a valid JSON array of slugs in order of relevance. Example: ["slug-1","slug-2","slug-3","slug-4"]
Do not include any explanation or markdown — only the raw JSON array.`; Do not include any explanation or markdown — only the raw JSON array.`;

View File

@@ -38,7 +38,7 @@ export async function POST(request: NextRequest) {
try { try {
response = await fetch(url, { response = await fetch(url, {
headers: { headers: {
'User-Agent': 'Mozilla/5.0 (compatible; Broadcast Beat/1.0; +https://broadcastbeat.com)', 'User-Agent': 'Mozilla/5.0 (compatible; AV Beat/1.0; +https://avbeat.com)',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}, },
signal: controller.signal, signal: controller.signal,

View File

@@ -24,11 +24,11 @@ function rateLimit(key: string): { ok: boolean; remaining: number } {
return { ok: true, remaining: RATE_LIMIT_MAX - b.count }; return { ok: true, remaining: RATE_LIMIT_MAX - b.count };
} }
const SYSTEM_PROMPT = `You are BB AI, Broadcast Beat's intelligent research assistant. const SYSTEM_PROMPT = `You are BB AI, AV Beat's intelligent research assistant.
You help readers — broadcast engineers, post-production supervisors, You help readers — broadcast engineers, post-production supervisors,
streaming architects, and broadcast executives — find information from the streaming architects, and broadcast executives — find information from the
Broadcast Beat archive. AV Beat archive.
Tone: trade-press, factual, lightly conversational. Plain English. Tone: trade-press, factual, lightly conversational. Plain English.

View File

@@ -85,7 +85,7 @@ export async function POST(request: NextRequest) {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: profile.full_name || user.email, term: bannedMatch.term }), body: JSON.stringify({ username: profile.full_name || user.email, term: bannedMatch.term }),
}).catch(() => {}); }).catch(() => {});
return NextResponse.json({ blocked: true, error: 'This post could not be submitted. Please contact the editorial team at editor@broadcastbeat.com for assistance.' }, { status: 422 }); return NextResponse.json({ blocked: true, error: 'This post could not be submitted. Please contact the editorial team at editor@avbeat.com for assistance.' }, { status: 422 });
} }
// Soft ban: flag but allow through // Soft ban: flag but allow through
// Will be tagged below // Will be tagged below

View File

@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
.eq('domain', sendingDomain) .eq('domain', sendingDomain)
.single(); .single();
const site = domainRecord?.site ?? 'broadcastbeat'; const site = domainRecord?.site ?? 'avbeat';
// Load active keywords // Load active keywords
const { data: keywords } = await supabase const { data: keywords } = await supabase

View File

@@ -22,7 +22,7 @@ interface Row {
export async function GET() { export async function GET() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL || ""; const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ""; const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
const schema = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb"; const schema = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "av";
if (!url || !key) return NextResponse.json({ events: [] }, { status: 200 }); if (!url || !key) return NextResponse.json({ events: [] }, { status: 200 });
const sb = createClient(url, key, { const sb = createClient(url, key, {

View File

@@ -19,7 +19,7 @@ export async function POST(req: NextRequest) {
[ [
{ {
role: 'system', role: 'system',
content: `You are the Broadcast Beat AI Assistant — a knowledgeable, helpful assistant for broadcast engineering professionals. You have deep expertise in broadcast technology including live production, IP workflows, SMPTE ST 2110, audio engineering, cameras, streaming, post-production, MAM systems, and broadcast industry trends. content: `You are the AV Beat AI Assistant — a knowledgeable, helpful assistant for broadcast engineering professionals. You have deep expertise in broadcast technology including live production, IP workflows, SMPTE ST 2110, audio engineering, cameras, streaming, post-production, MAM systems, and broadcast industry trends.
You are responding in a community forum. Be helpful, technically accurate, and conversational. Acknowledge when a question is complex or when professional consultation is advisable. Keep responses focused and practical — broadcast engineers value concise, actionable information. You are responding in a community forum. Be helpful, technically accurate, and conversational. Acknowledge when a question is complex or when professional consultation is advisable. Keep responses focused and practical — broadcast engineers value concise, actionable information.
@@ -51,7 +51,7 @@ Please provide a helpful, technically accurate response for this broadcast engin
.insert({ .insert({
thread_id, thread_id,
body: aiResponseText, body: aiResponseText,
author_name: 'Broadcast Beat AI', author_name: 'AV Beat AI',
is_ai_response: true, is_ai_response: true,
}) })
.select() .select()

View File

@@ -20,7 +20,7 @@ export async function POST(req: NextRequest) {
}); });
await transporter.sendMail({ await transporter.sendMail({
from: `"${process.env.SMTP_FROM_NAME || 'Broadcast Beat'}" <${process.env.SMTP_FROM_EMAIL}>`, from: `"${process.env.SMTP_FROM_NAME || 'AV Beat'}" <${process.env.SMTP_FROM_EMAIL}>`,
to, to,
subject, subject,
html, html,

View File

@@ -35,7 +35,7 @@ export async function POST(req: NextRequest) {
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Article shared with you — Broadcast Beat</title> <title>Article shared with you — AV Beat</title>
</head> </head>
<body style="margin:0;padding:0;background:#0d0d0d;font-family:Arial,sans-serif;"> <body style="margin:0;padding:0;background:#0d0d0d;font-family:Arial,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0d0d0d;padding:32px 16px;"> <table width="100%" cellpadding="0" cellspacing="0" style="background:#0d0d0d;padding:32px 16px;">
@@ -45,7 +45,7 @@ export async function POST(req: NextRequest) {
<!-- Header --> <!-- Header -->
<tr> <tr>
<td style="background:#0a0a0a;border-bottom:1px solid #1a1a1a;padding:20px 28px;"> <td style="background:#0a0a0a;border-bottom:1px solid #1a1a1a;padding:20px 28px;">
<p style="margin:0;color:#3b82f6;font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;">Broadcast Beat</p> <p style="margin:0;color:#3b82f6;font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;">AV Beat</p>
</td> </td>
</tr> </tr>
<!-- Body --> <!-- Body -->
@@ -62,8 +62,8 @@ export async function POST(req: NextRequest) {
<!-- Footer --> <!-- Footer -->
<tr> <tr>
<td style="background:#0a0a0a;border-top:1px solid #1a1a1a;padding:16px 28px;"> <td style="background:#0a0a0a;border-top:1px solid #1a1a1a;padding:16px 28px;">
<p style="margin:0;color:#444;font-size:11px;">You received this because someone shared a Broadcast Beat article with you.</p> <p style="margin:0;color:#444;font-size:11px;">You received this because someone shared a AV Beat article with you.</p>
<p style="margin:6px 0 0;color:#333;font-size:11px;">© ${new Date().getFullYear()} Broadcast Beat. All rights reserved.</p> <p style="margin:6px 0 0;color:#333;font-size:11px;">© ${new Date().getFullYear()} AV Beat. All rights reserved.</p>
</td> </td>
</tr> </tr>
</table> </table>
@@ -74,7 +74,7 @@ export async function POST(req: NextRequest) {
</html>`; </html>`;
await transporter.sendMail({ await transporter.sendMail({
from: `"${process.env.SMTP_FROM_NAME || "Broadcast Beat"}" <${process.env.SMTP_FROM_EMAIL}>`, from: `"${process.env.SMTP_FROM_NAME || "AV Beat"}" <${process.env.SMTP_FROM_EMAIL}>`,
to, to,
subject: `Shared article: ${articleTitle}`, subject: `Shared article: ${articleTitle}`,
html, html,

View File

@@ -68,8 +68,8 @@ async function sendWelcome(email: string, topics?: string[]) {
const smtpUser = process.env.SMTP_USER; const smtpUser = process.env.SMTP_USER;
const smtpPass = process.env.SMTP_PASS; const smtpPass = process.env.SMTP_PASS;
const fromEmail = process.env.SMTP_FROM_EMAIL || smtpUser; const fromEmail = process.env.SMTP_FROM_EMAIL || smtpUser;
const fromName = process.env.SMTP_FROM_NAME || 'Broadcast Beat'; const fromName = process.env.SMTP_FROM_NAME || 'AV Beat';
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
if (!smtpHost || !smtpUser || !smtpPass || !fromEmail) { if (!smtpHost || !smtpUser || !smtpPass || !fromEmail) {
// SMTP not configured — signal that to logs but don't blow up. // SMTP not configured — signal that to logs but don't blow up.
@@ -92,18 +92,18 @@ async function sendWelcome(email: string, topics?: string[]) {
await transporter.sendMail({ await transporter.sendMail({
from: `"${fromName}" <${fromEmail}>`, from: `"${fromName}" <${fromEmail}>`,
to: email, to: email,
subject: 'Welcome to Broadcast Beat', subject: 'Welcome to AV Beat',
html: ` html: `
<!DOCTYPE html> <!DOCTYPE html>
<html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>Welcome to Broadcast Beat</title></head> <html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>Welcome to AV Beat</title></head>
<body style="margin:0;padding:0;background:#0a0e15;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;"> <body style="margin:0;padding:0;background:#0a0e15;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#0a0e15;padding:40px 16px;"> <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#0a0e15;padding:40px 16px;">
<tr><td align="center"> <tr><td align="center">
<table role="presentation" cellpadding="0" cellspacing="0" width="600" style="max-width:600px;background:#11192a;border:1px solid #1e3a5f;border-radius:10px;overflow:hidden;"> <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="max-width:600px;background:#11192a;border:1px solid #1e3a5f;border-radius:10px;overflow:hidden;">
<tr><td style="padding:32px 36px 8px;"> <tr><td style="padding:32px 36px 8px;">
<div style="font-family:'Courier New',monospace;font-size:10px;font-weight:700;letter-spacing:0.22em;text-transform:uppercase;color:#3b82f6;">Broadcast Beat Newsletter</div> <div style="font-family:'Courier New',monospace;font-size:10px;font-weight:700;letter-spacing:0.22em;text-transform:uppercase;color:#3b82f6;">AV Beat Newsletter</div>
<h1 style="margin:14px 0 8px;font-size:24px;line-height:1.2;color:#ffffff;font-weight:800;">You're in</h1> <h1 style="margin:14px 0 8px;font-size:24px;line-height:1.2;color:#ffffff;font-weight:800;">You're in</h1>
<p style="margin:0;color:#cfd6e1;font-size:15px;line-height:1.55;">Thanks for subscribing to <strong style="color:#ffffff;">Broadcast Beat</strong> — the digital platform for broadcast engineering professionals.</p> <p style="margin:0;color:#cfd6e1;font-size:15px;line-height:1.55;">Thanks for subscribing to <strong style="color:#ffffff;">AV Beat</strong> — the digital platform for broadcast engineering professionals.</p>
${topicsBlock} ${topicsBlock}
</td></tr> </td></tr>
<tr><td style="padding:18px 36px 8px;"> <tr><td style="padding:18px 36px 8px;">
@@ -113,7 +113,7 @@ async function sendWelcome(email: string, topics?: string[]) {
<a href="${siteUrl}/news" style="display:inline-block;background:#3b82f6;color:#ffffff;text-decoration:none;padding:11px 22px;border-radius:6px;font-weight:700;font-size:13px;letter-spacing:0.05em;text-transform:uppercase;">Browse Industry News</a> <a href="${siteUrl}/news" style="display:inline-block;background:#3b82f6;color:#ffffff;text-decoration:none;padding:11px 22px;border-radius:6px;font-weight:700;font-size:13px;letter-spacing:0.05em;text-transform:uppercase;">Browse Industry News</a>
</td></tr> </td></tr>
<tr><td style="padding:18px 36px 28px;border-top:1px solid #1e3a5f;"> <tr><td style="padding:18px 36px 28px;border-top:1px solid #1e3a5f;">
<p style="margin:0;color:#5a6470;font-size:11px;line-height:1.5;">You're receiving this because you signed up at <a href="${siteUrl}" style="color:#3b82f6;text-decoration:none;">broadcastbeat.com</a>. <a href="${siteUrl}/unsubscribe?email=${encodeURIComponent(email)}" style="color:#98a3b3;">Unsubscribe anytime</a>.</p> <p style="margin:0;color:#5a6470;font-size:11px;line-height:1.5;">You're receiving this because you signed up at <a href="${siteUrl}" style="color:#3b82f6;text-decoration:none;">avbeat.com</a>. <a href="${siteUrl}/unsubscribe?email=${encodeURIComponent(email)}" style="color:#98a3b3;">Unsubscribe anytime</a>.</p>
</td></tr> </td></tr>
</table> </table>
</td></tr> </td></tr>

View File

@@ -95,8 +95,8 @@ export async function POST(req: NextRequest) {
const smtpUser = process.env.SMTP_USER; const smtpUser = process.env.SMTP_USER;
const smtpPass = process.env.SMTP_PASS; const smtpPass = process.env.SMTP_PASS;
const fromEmail = process.env.SMTP_FROM_EMAIL || smtpUser; const fromEmail = process.env.SMTP_FROM_EMAIL || smtpUser;
const fromName = process.env.SMTP_FROM_NAME || 'Broadcast Beat'; const fromName = process.env.SMTP_FROM_NAME || 'AV Beat';
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://broadcastbeat.com'; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com';
if (!smtpHost || !smtpUser || !smtpPass) { if (!smtpHost || !smtpUser || !smtpPass) {
return NextResponse.json({ error: 'SMTP credentials not configured' }, { status: 500 }); return NextResponse.json({ error: 'SMTP credentials not configured' }, { status: 500 });
@@ -140,7 +140,7 @@ export async function POST(req: NextRequest) {
<div style="background:#0d1520;padding:0;font-family:sans-serif;max-width:600px;margin:0 auto;"> <div style="background:#0d1520;padding:0;font-family:sans-serif;max-width:600px;margin:0 auto;">
<!-- Header --> <!-- Header -->
<div style="background:#111;border-bottom:2px solid #3b82f6;padding:24px 32px;"> <div style="background:#111;border-bottom:2px solid #3b82f6;padding:24px 32px;">
<h1 style="color:#3b82f6;font-size:22px;margin:0;font-weight:bold;">Broadcast Beat</h1> <h1 style="color:#3b82f6;font-size:22px;margin:0;font-weight:bold;">AV Beat</h1>
<p style="color:#555;font-size:12px;margin:4px 0 0;">News & Intelligence for Broadcast, Post-Production & Streaming Technology</p> <p style="color:#555;font-size:12px;margin:4px 0 0;">News & Intelligence for Broadcast, Post-Production & Streaming Technology</p>
</div> </div>
<!-- Body --> <!-- Body -->
@@ -151,9 +151,9 @@ export async function POST(req: NextRequest) {
</div> </div>
<!-- Footer --> <!-- Footer -->
<div style="background:#0a0f1a;padding:24px 32px;border-top:1px solid #1e3a5f;"> <div style="background:#0a0f1a;padding:24px 32px;border-top:1px solid #1e3a5f;">
<p style="color:#555;font-size:12px;margin:0;">You're receiving this because you subscribed at broadcastbeat.com.</p> <p style="color:#555;font-size:12px;margin:0;">You're receiving this because you subscribed at avbeat.com.</p>
<p style="color:#555;font-size:12px;margin:8px 0 0;"> <p style="color:#555;font-size:12px;margin:8px 0 0;">
<a href="${siteUrl}" style="color:#3b82f6;">Visit Broadcast Beat</a> · <a href="${siteUrl}" style="color:#3b82f6;">Visit AV Beat</a> ·
<a href="${siteUrl}/unsubscribe?email={{email}}" style="color:#3b82f6;">Unsubscribe</a> <a href="${siteUrl}/unsubscribe?email={{email}}" style="color:#3b82f6;">Unsubscribe</a>
</p> </p>
</div> </div>

View File

@@ -42,7 +42,7 @@ export async function GET(_req: Request, { params }: { params: Promise<{ slug: s
const { count } = await advSvc const { count } = await advSvc
.from("active_advertisers_by_property") .from("active_advertisers_by_property")
.select("client_id", { count: "exact", head: true }) .select("client_id", { count: "exact", head: true })
.eq("property", "broadcastbeat") .eq("property", "avbeat")
.eq("company_name_lower", needle.toLowerCase()); .eq("company_name_lower", needle.toLowerCase());
isActiveAdvertiser = (count || 0) > 0; isActiveAdvertiser = (count || 0) > 0;
} catch { /* tolerate adv lookup failures */ } } catch { /* tolerate adv lookup failures */ }

View File

@@ -35,7 +35,7 @@ export async function GET(request: Request) {
// Decorate + reorder so premium and current-advertiser stories surface // Decorate + reorder so premium and current-advertiser stories surface
// at the top of the feed. // at the top of the feed.
const decorated = await decorateHomepageFeed("broadcastbeat", base); const decorated = await decorateHomepageFeed("avbeat", base);
const payload = decorated.map((d) => ({ const payload = decorated.map((d) => ({
...d.article, ...d.article,
promoted_kind: d.promoted_kind, promoted_kind: d.promoted_kind,

View File

@@ -6,7 +6,7 @@ export const dynamic = "force-dynamic";
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!; const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_ANON = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; const SUPABASE_ANON = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const SCHEMA = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb"; const SCHEMA = process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "av";
function client(schema = SCHEMA) { function client(schema = SCHEMA) {
return createClient(SUPABASE_URL, SUPABASE_ANON, { return createClient(SUPABASE_URL, SUPABASE_ANON, {
@@ -75,7 +75,7 @@ export async function GET(req: Request) {
const { data: ads } = await adv const { data: ads } = await adv
.from("active_advertisers_by_property") .from("active_advertisers_by_property")
.select("company_name_lower") .select("company_name_lower")
.eq("property", "broadcastbeat"); .eq("property", "avbeat");
sponsors = new Set((ads || []).map((r: any) => String(r.company_name_lower))); sponsors = new Set((ads || []).map((r: any) => String(r.company_name_lower)));
} catch { /* tolerate */ } } catch { /* tolerate */ }

View File

@@ -58,7 +58,7 @@ export async function POST(request: NextRequest) {
const featuredImage = body?.featured_image_url || null; const featuredImage = body?.featured_image_url || null;
const attachments = Array.isArray(body?.attachments) ? body.attachments : []; const attachments = Array.isArray(body?.attachments) ? body.attachments : [];
const submissionId = body?.submission_id || null; const submissionId = body?.submission_id || null;
const property = body?.property || 'broadcastbeat'; const property = body?.property || 'avbeat';
// Defense in depth: re-check the contributor's approval status against // Defense in depth: re-check the contributor's approval status against
// distribute.users. distribute-rmp's submit route already gates, but a // distribute.users. distribute-rmp's submit route already gates, but a

View File

@@ -3,7 +3,7 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '../../../lib/supabase/server'; import { createClient } from '../../../lib/supabase/server';
const WP_BASE_URL = 'https://www.broadcastbeat.com/wp-json/wp/v2'; const WP_BASE_URL = 'https://www.avbeat.com/wp-json/wp/v2';
const PER_PAGE = 20; const PER_PAGE = 20;
const IMAGE_BUCKET = 'post-images'; const IMAGE_BUCKET = 'post-images';
@@ -29,7 +29,7 @@ function stripHtml(html: string): string {
async function fetchWPPosts(page: number): Promise<{ posts: WPPost[]; totalPages: number }> { async function fetchWPPosts(page: number): Promise<{ posts: WPPost[]; totalPages: number }> {
const url = `${WP_BASE_URL}/posts?page=${page}&per_page=${PER_PAGE}&_embed=1&status=publish`; const url = `${WP_BASE_URL}/posts?page=${page}&per_page=${PER_PAGE}&_embed=1&status=publish`;
const res = await fetch(url, { const res = await fetch(url, {
headers: { 'User-Agent': 'Broadcast Beat-Importer/1.0' }, headers: { 'User-Agent': 'AV Beat-Importer/1.0' },
next: { revalidate: 0 }, next: { revalidate: 0 },
}); });
@@ -54,7 +54,7 @@ async function downloadAndStoreImage(
try { try {
// Fetch the image // Fetch the image
const response = await fetch(imageUrl, { const response = await fetch(imageUrl, {
headers: { 'User-Agent': 'Broadcast Beat-Importer/1.0' }, headers: { 'User-Agent': 'AV Beat-Importer/1.0' },
signal: AbortSignal.timeout(15000), signal: AbortSignal.timeout(15000),
}); });

View File

@@ -229,9 +229,9 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{/* DO NOT OVERRIDE — article breadcrumb and back navigation */} {/* DO NOT OVERRIDE — article breadcrumb and back navigation */}
<div className="bg-[#0d0d0d] border-b border-[#1a1a1a]"> <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]"> <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-[#E67E22] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#3b82f6] transition-colors">Home</Link>
<span>/</span> <span>/</span>
<Link href={sectionHref} className="hover:text-[#E67E22] transition-colors">{sectionLabel}</Link> <Link href={sectionHref} className="hover:text-[#3b82f6] transition-colors">{sectionLabel}</Link>
<span>/</span> <span>/</span>
<span className="text-[#888] truncate max-w-[300px]">{article.title}</span> <span className="text-[#888] truncate max-w-[300px]">{article.title}</span>
</div> </div>
@@ -245,7 +245,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{/* Article Header */} {/* Article Header */}
<div className="mb-8"> <div className="mb-8">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<Link href={sectionHref} className="text-xs font-bold uppercase tracking-widest text-[#E67E22] hover:underline"> <Link href={sectionHref} className="text-xs font-bold uppercase tracking-widest text-[#3b82f6] hover:underline">
{article.category} {article.category}
</Link> </Link>
<span className="text-xs text-[#777]">{article.date}</span> <span className="text-xs text-[#777]">{article.date}</span>
@@ -271,7 +271,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<div> <div>
<Link <Link
href={`/authors/${article.authorSlug}`} href={`/authors/${article.authorSlug}`}
className="font-heading font-bold text-sm hover:text-[#E67E22] transition-colors text-[#e0e0e0]"> className="font-heading font-bold text-sm hover:text-[#3b82f6] transition-colors text-[#e0e0e0]">
{article.author} {article.author}
</Link> </Link>
<p className="font-body text-xs text-[#777]">{article.authorTitle}</p> <p className="font-body text-xs text-[#777]">{article.authorTitle}</p>
@@ -286,7 +286,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<> <>
<a href={`mailto:?subject=${encodeURIComponent(article.title)}&body=${encodeURIComponent(articleUrl)}`} <a href={`mailto:?subject=${encodeURIComponent(article.title)}&body=${encodeURIComponent(articleUrl)}`}
aria-label="Share via email" title="Email" aria-label="Share via email" title="Email"
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]"> 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]">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <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" /> <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" /> <path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
@@ -297,7 +297,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#0a66c2] hover:bg-[#1a1a1a] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] 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> <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>
<a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=AVBeat`} <a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=BroadcastBeat`}
target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)" target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-white hover:bg-[#1a1a1a] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-white hover:bg-[#1a1a1a] transition-colors">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
@@ -320,7 +320,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<button type="button" <button type="button"
onClick={async () => { try { await navigator.clipboard.writeText(articleUrl); } catch {} }} onClick={async () => { try { await navigator.clipboard.writeText(articleUrl); } catch {} }}
aria-label="Copy article link" title="Copy link" aria-label="Copy article link" title="Copy link"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#E67E22] hover:bg-[#1a1a1a] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#3b82f6] hover:bg-[#1a1a1a] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <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="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"/> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
@@ -334,7 +334,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
onClick={handleSaveToReadingList} onClick={handleSaveToReadingList}
disabled={savingState !== "idle"} disabled={savingState !== "idle"}
aria-label={isSaved ? "Remove from reading list" : "Save to reading list"} aria-label={isSaved ? "Remove from reading list" : "Save to reading list"}
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]"> 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]">
{savingState === "saving" ? "Saving..." : savingState === "removing" ? "Removing..." : isSaved ? "✓ Saved" : "Save"} {savingState === "saving" ? "Saving..." : savingState === "removing" ? "Removing..." : isSaved ? "✓ Saved" : "Save"}
</button> </button>
</div> </div>
@@ -368,7 +368,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{article.tags.map((tag) => ( {article.tags.map((tag) => (
<span <span
key={tag} key={tag}
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"> 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">
{tag} {tag}
</span> </span>
))} ))}
@@ -380,7 +380,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<div className="pt-6 border-t border-[#222]"> <div className="pt-6 border-t border-[#222]">
<Link <Link
href={sectionHref} href={sectionHref}
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]"> 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]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<polyline points="15 18 9 12 15 6" /> <polyline points="15 18 9 12 15 6" />
</svg> </svg>
@@ -398,7 +398,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{/* Related Articles */} {/* Related Articles */}
{/* DO NOT OVERRIDE — related articles sidebar */} {/* DO NOT OVERRIDE — related articles sidebar */}
<div className="bg-[#111] border border-[#222] p-5"> <div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
Related Articles Related Articles
</h3> </h3>
<div className="space-y-4"> <div className="space-y-4">
@@ -417,10 +417,10 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
/> />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[#E67E22] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5"> <p className="text-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5">
{related.category} {related.category}
</p> </p>
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#E67E22] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug">
{related.title} {related.title}
</p> </p>
</div> </div>
@@ -435,19 +435,19 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
recently-active threads so the box is never empty. */} recently-active threads so the box is never empty. */}
{relatedForumThreads.length > 0 && ( {relatedForumThreads.length > 0 && (
<div className="bg-[#111] border border-[#222] p-5"> <div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
Related Forum Posts Related Forum Posts
</h3> </h3>
<ul className="space-y-3.5"> <ul className="space-y-3.5">
{relatedForumThreads.map((t) => ( {relatedForumThreads.map((t) => (
<li key={t.id}> <li key={t.id}>
<Link href={`/forum/thread/${t.id}`} className="group block"> <Link href={`/forum/thread/${t.id}`} className="group block">
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#E67E22] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug">
{t.title} {t.title}
</p> </p>
<p className="flex items-center gap-2 mt-1 text-[10px] text-[#666]"> <p className="flex items-center gap-2 mt-1 text-[10px] text-[#666]">
{t.category_name && ( {t.category_name && (
<span className="text-[#E67E22] font-body font-bold uppercase tracking-wider"> <span className="text-[#3b82f6] font-body font-bold uppercase tracking-wider">
{t.category_name} {t.category_name}
</span> </span>
)} )}
@@ -461,7 +461,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
</ul> </ul>
<Link <Link
href="/forum" href="/forum"
className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#E67E22] hover:underline font-body font-semibold uppercase tracking-wider" className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#3b82f6] hover:underline font-body font-semibold uppercase tracking-wider"
> >
Browse all forum threads Browse all forum threads
</Link> </Link>
@@ -479,7 +479,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<div <div
role="status" role="status"
aria-live="polite" aria-live="polite"
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"> 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">
{saveToast} {saveToast}
</div> </div>
)} )}

View File

@@ -47,7 +47,7 @@ interface Stats {
} }
const PUBLICATIONS = [ const PUBLICATIONS = [
{ id: 'broadcast-beat', name: 'Broadcast Beat', short: 'BB' }, { id: 'broadcast-beat', name: 'AV Beat', short: 'BB' },
{ id: 'av-beat', name: 'AV Beat', short: 'AV' }, { id: 'av-beat', name: 'AV Beat', short: 'AV' },
]; ];
@@ -218,7 +218,7 @@ export default function AdOpsDashboard() {
</button> </button>
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-orange-900/50 text-orange-300 border border-orange-700"> <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-orange-900/50 text-orange-300 border border-orange-700">
<span className="w-1.5 h-1.5 rounded-full bg-orange-400" /> <span className="w-1.5 h-1.5 rounded-full bg-orange-400" />
adops@broadcastbeat.com adops@avbeat.com
</span> </span>
</div> </div>
</div> </div>

View File

@@ -191,9 +191,9 @@ const QUICK_ACTIONS = [
]; ];
const QUICK_ACTION_TEMPLATES: Record<string, string> = { const QUICK_ACTION_TEMPLATES: Record<string, string> = {
url_story: `Write a news story for [Broadcast Beat / AV Beat] based on this URL: [URL]. Use the byline [pen name]. Target length: [400-600] words. Focus angle: [product launch / company news / industry trend].`, url_story: `Write a news story for [AV Beat / AV Beat] based on this URL: [URL]. Use the byline [pen name]. Target length: [400-600] words. Focus angle: [product launch / company news / industry trend].`,
exhibitor_preview: `Write a pre-show exhibitor preview for [company name] at [NAB Show / InfoComm] [year]. They will be showing [product/technology]. Booth: [number if known]. Use [pen name] byline. 400-500 words.`, exhibitor_preview: `Write a pre-show exhibitor preview for [company name] at [NAB Show / InfoComm] [year]. They will be showing [product/technology]. Booth: [number if known]. Use [pen name] byline. 400-500 words.`,
press_release: `Rewrite the following press release as a real news story for [Broadcast Beat / AV Beat]. Remove marketing language, lead with the news hook, add industry context. Byline: [pen name]. press_release: `Rewrite the following press release as a real news story for [AV Beat / AV Beat]. Remove marketing language, lead with the news hook, add industry context. Byline: [pen name].
[PASTE PRESS RELEASE HERE]`, [PASTE PRESS RELEASE HERE]`,
nab_story: `Write a [story type: preview / announcement / recap] story about [topic/company] for NAB Show [year]. Include relevant SEO keywords. Byline: James Mercer. [400-800] words.`, nab_story: `Write a [story type: preview / announcement / recap] story about [topic/company] for NAB Show [year]. Include relevant SEO keywords. Byline: James Mercer. [400-800] words.`,
@@ -203,7 +203,7 @@ Story headline: [HEADLINE]
Story summary: [SUMMARY]`, Story summary: [SUMMARY]`,
headlines: `Generate 8 headline options for this story. Include 2 SEO-optimized versions targeting [keyword], 2 social media-friendly versions, 2 trade publication style versions, and 2 alternatives. Story summary: [SUMMARY]`, headlines: `Generate 8 headline options for this story. Include 2 SEO-optimized versions targeting [keyword], 2 social media-friendly versions, 2 trade publication style versions, and 2 alternatives. Story summary: [SUMMARY]`,
translate: `Translate the following story into [language]. Maintain the professional trade publication tone. Keep technical terms in their internationally recognized form. [PASTE STORY HERE]`, translate: `Translate the following story into [language]. Maintain the professional trade publication tone. Keep technical terms in their internationally recognized form. [PASTE STORY HERE]`,
company_bio: `Write a 150-word company bio for [company name] suitable for use as a boilerplate paragraph at the end of news stories on [Broadcast Beat / AV Beat]. The company makes [products/services]. Website: [URL].`, company_bio: `Write a 150-word company bio for [company name] suitable for use as a boilerplate paragraph at the end of news stories on [AV Beat / AV Beat]. The company makes [products/services]. Website: [URL].`,
product_desc: `Write a 200-word product description for [product name] by [company]. Key features: [features]. Target audience: [integrators / broadcast engineers / etc]. Suitable for use in a product announcement story.`, product_desc: `Write a 200-word product description for [product name] by [company]. Key features: [features]. Target audience: [integrators / broadcast engineers / etc]. Suitable for use in a product announcement story.`,
}; };
@@ -243,7 +243,7 @@ function DraftPreviewModal({ content, title, penName, siteId, onClose, onSave }:
<div className="flex items-center justify-between px-5 py-4 border-b border-[#1e2a3a]"> <div className="flex items-center justify-between px-5 py-4 border-b border-[#1e2a3a]">
<div> <div>
<h2 className="text-white font-semibold text-sm">Draft Preview</h2> <h2 className="text-white font-semibold text-sm">Draft Preview</h2>
<p className="text-[#555] text-xs mt-0.5">Review before saving to {siteId === 1 ? 'Broadcast Beat' : 'AV Beat'}</p> <p className="text-[#555] text-xs mt-0.5">Review before saving to {siteId === 1 ? 'AV Beat' : 'AV Beat'}</p>
</div> </div>
<button onClick={onClose} className="text-[#555] hover:text-white transition-colors text-lg leading-none"></button> <button onClick={onClose} className="text-[#555] hover:text-white transition-colors text-lg leading-none"></button>
</div> </div>
@@ -256,7 +256,7 @@ function DraftPreviewModal({ content, title, penName, siteId, onClose, onSave }:
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[#555] text-xs">Site:</span> <span className="text-[#555] text-xs">Site:</span>
<span className={`text-xs font-bold px-1.5 py-0.5 rounded ${siteId === 2 ? 'bg-orange-900/40 text-orange-400' : 'bg-blue-900/40 text-blue-400'}`}> <span className={`text-xs font-bold px-1.5 py-0.5 rounded ${siteId === 2 ? 'bg-orange-900/40 text-orange-400' : 'bg-blue-900/40 text-blue-400'}`}>
{siteId === 2 ? 'AV Beat' : 'Broadcast Beat'} {siteId === 2 ? 'AV Beat' : 'AV Beat'}
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -487,7 +487,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
onClick={() => setFilterSite(f)} onClick={() => setFilterSite(f)}
className={`text-xs px-2.5 py-1 rounded transition-colors ${filterSite === f ? 'bg-[#3b82f6] text-white' : 'bg-[#1e2a3a] text-[#aaa] hover:text-white'}`} className={`text-xs px-2.5 py-1 rounded transition-colors ${filterSite === f ? 'bg-[#3b82f6] text-white' : 'bg-[#1e2a3a] text-[#aaa] hover:text-white'}`}
> >
{f === 'ALL' ? 'All Writers' : f === 'BB' ? 'Broadcast Beat' : 'AV Beat'} {f === 'ALL' ? 'All Writers' : f === 'BB' ? 'AV Beat' : 'AV Beat'}
</button> </button>
))} ))}
</div> </div>
@@ -670,7 +670,7 @@ export default function AIConsolePage() {
const penNames = selectedSite === 1 ? BB_PEN_NAMES : AV_PEN_NAMES; const penNames = selectedSite === 1 ? BB_PEN_NAMES : AV_PEN_NAMES;
const buildSystemPrompt = () => { const buildSystemPrompt = () => {
const siteName = selectedSite === 1 ? 'Broadcast Beat' : 'AV Beat'; const siteName = selectedSite === 1 ? 'AV Beat' : 'AV Beat';
const siteDesc = selectedSite === 1 const siteDesc = selectedSite === 1
? 'a professional B2B trade publication for broadcast engineers, production professionals, and industry executives' ? 'a professional B2B trade publication for broadcast engineers, production professionals, and industry executives'
: 'a professional B2B trade publication for AV integrators, technology managers, and AV industry professionals'; : 'a professional B2B trade publication for AV integrators, technology managers, and AV industry professionals';
@@ -1068,7 +1068,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
onChange={e => setSelectedSite(Number(e.target.value) as 1 | 2)} onChange={e => setSelectedSite(Number(e.target.value) as 1 | 2)}
className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#3b82f6]" className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#3b82f6]"
> >
<option value={1}>Broadcast Beat</option> <option value={1}>AV Beat</option>
<option value={2}>AV Beat</option> <option value={2}>AV Beat</option>
</select> </select>
</div> </div>

View File

@@ -303,12 +303,12 @@ export default function AIConsoleSettingsPage() {
onChange={e => update('default_site_id', Number(e.target.value))} onChange={e => update('default_site_id', Number(e.target.value))}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
> >
<option value={1}>Broadcast Beat</option> <option value={1}>AV Beat</option>
<option value={2}>AV Beat</option> <option value={2}>AV Beat</option>
</select> </select>
</div> </div>
<div> <div>
<label className="block text-[#aaa] text-xs font-medium mb-1.5">Default Pen Name (Broadcast Beat)</label> <label className="block text-[#aaa] text-xs font-medium mb-1.5">Default Pen Name (AV Beat)</label>
<select <select
value={settings.default_pen_name_bb} value={settings.default_pen_name_bb}
onChange={e => update('default_pen_name_bb', e.target.value)} onChange={e => update('default_pen_name_bb', e.target.value)}

View File

@@ -256,7 +256,7 @@ export default function ShowCalendarPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#E67E22] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -294,7 +294,7 @@ export default function ShowCalendarPage() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
onClick={() => { setEditingEvent({ site_id: 1, year: new Date().getFullYear(), seo_tier: 3, event_type: 'trade_show' }); setShowEventModal(true); }} onClick={() => { setEditingEvent({ site_id: 1, year: new Date().getFullYear(), seo_tier: 3, event_type: 'trade_show' }); setShowEventModal(true); }}
className="px-3 py-1.5 bg-[#E67E22] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors" className="px-3 py-1.5 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors"
> >
+ Add Event + Add Event
</button> </button>
@@ -316,7 +316,7 @@ export default function ShowCalendarPage() {
onClick={() => setSiteFilter(s)} onClick={() => setSiteFilter(s)}
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${ className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
siteFilter === s siteFilter === s
? 'bg-[#E67E22] text-white' ? 'bg-[#3b82f6] text-white'
: 'bg-[#1a1a1a] text-[#888] hover:text-white border border-[#333]' : 'bg-[#1a1a1a] text-[#888] hover:text-white border border-[#333]'
}`} }`}
> >
@@ -333,7 +333,7 @@ export default function ShowCalendarPage() {
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${ className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
activeTab === tab.id activeTab === tab.id
? 'border-[#E67E22] text-white' ? 'border-[#3b82f6] text-white'
: 'border-transparent text-[#666] hover:text-white' : 'border-transparent text-[#666] hover:text-white'
}`} }`}
> >
@@ -347,7 +347,7 @@ export default function ShowCalendarPage() {
<div className="max-w-7xl mx-auto px-4 py-6"> <div className="max-w-7xl mx-auto px-4 py-6">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<div className="w-8 h-8 border-2 border-[#E67E22] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
<> <>
@@ -383,7 +383,7 @@ export default function ShowCalendarPage() {
<span>Engine: {formatDate(event.story_engine_start_date)} {formatDate(event.story_engine_end_date)}</span> <span>Engine: {formatDate(event.story_engine_start_date)} {formatDate(event.story_engine_end_date)}</span>
{event.official_site && ( {event.official_site && (
<a href={`https://${event.official_site}`} target="_blank" rel="noopener noreferrer" <a href={`https://${event.official_site}`} target="_blank" rel="noopener noreferrer"
className="text-[#E67E22] hover:underline">{event.official_site}</a> className="text-[#3b82f6] hover:underline">{event.official_site}</a>
)} )}
</div> </div>
<div className="flex items-center gap-3 mt-2 text-xs text-[#555]"> <div className="flex items-center gap-3 mt-2 text-xs text-[#555]">
@@ -440,7 +440,7 @@ export default function ShowCalendarPage() {
onClick={() => setQueueStatus(s)} onClick={() => setQueueStatus(s)}
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${ className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
queueStatus === s queueStatus === s
? 'bg-[#E67E22] text-white' ? 'bg-[#3b82f6] text-white'
: 'bg-[#1a1a1a] text-[#888] hover:text-white border border-[#333]' : 'bg-[#1a1a1a] text-[#888] hover:text-white border border-[#333]'
}`} }`}
> >
@@ -779,7 +779,7 @@ export default function ShowCalendarPage() {
<button <button
type="submit" type="submit"
disabled={actionLoading === 'save-event'} disabled={actionLoading === 'save-event'}
className="flex-1 py-2 bg-[#E67E22] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50" className="flex-1 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
> >
{actionLoading === 'save-event' ? 'Saving...' : 'Save Event'} {actionLoading === 'save-event' ? 'Saving...' : 'Save Event'}
</button> </button>
@@ -857,7 +857,7 @@ export default function ShowCalendarPage() {
<button <button
type="submit" type="submit"
disabled={actionLoading === 'generate'} disabled={actionLoading === 'generate'}
className="flex-1 py-2 bg-[#E67E22] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50" className="flex-1 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
> >
{actionLoading === 'generate' ? 'Generating...' : 'Generate Story'} {actionLoading === 'generate' ? 'Generating...' : 'Generate Story'}
</button> </button>

View File

@@ -54,7 +54,7 @@ function ForumLoginInner() {
<main className="min-h-screen bg-gradient-to-b from-[#0d0d0d] via-[#111111] to-[#0d0d0d]"> <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="max-w-md mx-auto px-4 py-16">
<div className="text-center mb-6"> <div className="text-center mb-6">
<div className="text-[10px] uppercase tracking-widest text-[#E67E22] font-mono mb-2">Welcome back</div> <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 AV Beat</h1> <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"> <p className="text-[#888] font-body text-sm mt-2">
Your account covers the forum, newsletter preferences, and saved articles. Your account covers the forum, newsletter preferences, and saved articles.
@@ -79,7 +79,7 @@ function ForumLoginInner() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
required 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-[#E67E22]" 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]"
/> />
</div> </div>
<div> <div>
@@ -92,20 +92,20 @@ function ForumLoginInner() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password" autoComplete="current-password"
required 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-[#E67E22]" 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]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
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" 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"
> >
{loading ? 'Signing in…' : 'Sign In'} {loading ? 'Signing in…' : 'Sign In'}
</button> </button>
<div className="text-right text-xs font-body"> <div className="text-right text-xs font-body">
<Link <Link
href={`/forum/forgot-password?email=${encodeURIComponent(email)}`} href={`/forum/forgot-password?email=${encodeURIComponent(email)}`}
className="text-[#888] hover:text-[#E67E22] hover:underline" className="text-[#888] hover:text-[#3b82f6] hover:underline"
> >
Forgot password? Forgot password?
</Link> </Link>
@@ -115,7 +115,7 @@ function ForumLoginInner() {
No account yet?{' '} No account yet?{' '}
<Link <Link
href={`/forum/register?next=${encodeURIComponent(next)}`} href={`/forum/register?next=${encodeURIComponent(next)}`}
className="text-[#E67E22] hover:underline" className="text-[#3b82f6] hover:underline"
> >
Create one Create one
</Link> </Link>

View File

@@ -103,7 +103,7 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
const getMetricValue = (thread: ForumThread) => { const getMetricValue = (thread: ForumThread) => {
if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' }; 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-[#E67E22]' }; if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#3b82f6]' };
return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' }; return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' };
}; };
@@ -123,7 +123,7 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
onClick={() => setActiveMetric(m.value)} onClick={() => setActiveMetric(m.value)}
className={`px-3 py-1 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${ className={`px-3 py-1 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
activeMetric === m.value activeMetric === m.value
? 'bg-[#E67E22] text-white' ? 'bg-[#3b82f6] text-white'
: 'text-[#666] hover:text-[#aaa]' : 'text-[#666] hover:text-[#aaa]'
}`} }`}
> >
@@ -171,13 +171,13 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
</span> </span>
{/* Thread info */} {/* Thread info */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-white font-body text-sm font-semibold group-hover:text-[#E67E22] transition-colors truncate leading-snug"> <p className="text-white font-body text-sm font-semibold group-hover:text-[#3b82f6] transition-colors truncate leading-snug">
{thread.title} {thread.title}
</p> </p>
<p className="text-[#555] font-body text-xs mt-0.5"> <p className="text-[#555] font-body text-xs mt-0.5">
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#E67E22] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#3b82f6] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{thread.forum_categories && ( {thread.forum_categories && (
<> · <span className="text-[#E67E22]">{thread.forum_categories.name}</span></> <> · <span className="text-[#3b82f6]">{thread.forum_categories.name}</span></>
)} )}
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
@@ -301,7 +301,7 @@ export default function ForumIndexPage() {
{currentUserId ? ( {currentUserId ? (
<Link <Link
href="/forum/new" href="/forum/new"
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" 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"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<path d="M12 5v14M5 12h14" /> <path d="M12 5v14M5 12h14" />
@@ -311,7 +311,7 @@ export default function ForumIndexPage() {
) : ( ) : (
<Link <Link
href="/forum/login?next=/forum" href="/forum/login?next=/forum"
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" 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"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <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" /> <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 <button
onClick={() => setFeedMode('discover')} onClick={() => setFeedMode('discover')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${ className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${
feedMode === 'discover' ? 'bg-[#E67E22] text-white' : 'text-[#888] hover:text-white' feedMode === 'discover' ? 'bg-[#3b82f6] 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"> <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 <button
onClick={() => setFeedMode('following')} onClick={() => setFeedMode('following')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${ className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${
feedMode === 'following' ? 'bg-[#E67E22] text-white' : 'text-[#888] hover:text-white' feedMode === 'following' ? 'bg-[#3b82f6] 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"> <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> <p className="text-[#aab4c4] font-body text-sm mb-3">Sign in to see threads from people you follow.</p>
<Link <Link
href="/login" href="/login"
className="inline-block bg-[#E67E22] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#C8651B] transition-colors" className="inline-block bg-[#3b82f6] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#2563eb] transition-colors"
> >
Sign In Sign In
</Link> </Link>
@@ -384,7 +384,7 @@ export default function ForumIndexPage() {
<Link <Link
href="/forum" href="/forum"
onClick={() => setFeedMode('discover')} onClick={() => setFeedMode('discover')}
className="inline-block bg-[#E67E22] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#C8651B] transition-colors" className="inline-block bg-[#3b82f6] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#2563eb] transition-colors"
> >
Discover Members Discover Members
</Link> </Link>
@@ -398,10 +398,10 @@ export default function ForumIndexPage() {
<Link <Link
key={thread.id} key={thread.id}
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
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" 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"
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#E67E22] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#3b82f6] transition-colors truncate">
{thread.title} {thread.title}
</h3> </h3>
<p className="text-[#666] font-body text-xs mt-0.5"> <p className="text-[#666] font-body text-xs mt-0.5">
@@ -409,7 +409,7 @@ export default function ForumIndexPage() {
<Link <Link
href={`/profile/${thread.author_id}`} href={`/profile/${thread.author_id}`}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
className="text-[#E67E22] hover:underline" className="text-[#3b82f6] hover:underline"
> >
{thread.author_name} {thread.author_name}
</Link> </Link>
@@ -450,7 +450,7 @@ export default function ForumIndexPage() {
placeholder="Search threads..." placeholder="Search threads..."
value={search} value={search}
onChange={e => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}
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" 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"
/> />
{search && ( {search && (
<button <button
@@ -470,7 +470,7 @@ export default function ForumIndexPage() {
<select <select
value={selectedCategory} value={selectedCategory}
onChange={e => setSelectedCategory(e.target.value)} onChange={e => setSelectedCategory(e.target.value)}
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]" 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]"
> >
<option value="">All Categories</option> <option value="">All Categories</option>
{categories.map(cat => ( {categories.map(cat => (
@@ -490,7 +490,7 @@ export default function ForumIndexPage() {
onClick={() => setSort(opt.value)} onClick={() => setSort(opt.value)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${ className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
sort === opt.value sort === opt.value
? 'bg-[#E67E22] text-white' ? 'bg-[#3b82f6] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -523,16 +523,16 @@ export default function ForumIndexPage() {
<Link <Link
key={thread.id} key={thread.id}
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
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" 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"
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#E67E22] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#3b82f6] transition-colors truncate">
{thread.title} {thread.title}
</h3> </h3>
<p className="text-[#666] font-body text-xs mt-0.5"> <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-[#E67E22] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> 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>
{thread.forum_categories && ( {thread.forum_categories && (
<> · <span className="text-[#E67E22]">{thread.forum_categories.name}</span></> <> · <span className="text-[#3b82f6]">{thread.forum_categories.name}</span></>
)} )}
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
@@ -584,11 +584,11 @@ export default function ForumIndexPage() {
<Link <Link
key={cat.id} key={cat.id}
href={`/forum/${cat.slug}`} href={`/forum/${cat.slug}`}
className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#E67E22] rounded-lg p-4 transition-all group" className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#3b82f6] rounded-lg p-4 transition-all group"
> >
<span className="text-3xl flex-shrink-0 w-10 text-center">{cat.icon}</span> <span className="text-3xl flex-shrink-0 w-10 text-center">{cat.icon}</span>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h2 className="text-white font-heading font-semibold text-lg group-hover:text-[#E67E22] transition-colors"> <h2 className="text-white font-heading font-semibold text-lg group-hover:text-[#3b82f6] transition-colors">
{cat.name} {cat.name}
</h2> </h2>
<p className="text-[#888] font-body text-sm mt-0.5 truncate">{cat.description}</p> <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-white font-body font-semibold text-sm">{cat.post_count}</div>
<div className="text-[#666] font-body text-xs">posts</div> <div className="text-[#666] font-body text-xs">posts</div>
</div> </div>
<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"> <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">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg> </svg>
</Link> </Link>
@@ -629,7 +629,7 @@ export default function ForumIndexPage() {
<ul className="text-[#aab4c4] font-body text-sm space-y-1"> <ul className="text-[#aab4c4] font-body text-sm space-y-1">
<li> Be respectful and professional this is a community of peers</li> <li> Be respectful and professional this is a community of peers</li>
<li> Share real-world experience and cite sources when possible</li> <li> Share real-world experience and cite sources when possible</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> The <span className="text-[#3b82f6]">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> <li> No spam, self-promotion, or vendor pitches without disclosure</li>
</ul> </ul>
</div> </div>

View File

@@ -105,7 +105,7 @@ 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"> <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 */} {/* Left rail — what membership gets you */}
<aside className="hidden lg:block"> <aside className="hidden lg:block">
<div className="text-[10px] uppercase tracking-widest text-[#E67E22] font-mono mb-3">Membership</div> <div className="text-[10px] uppercase tracking-widest text-[#3b82f6] font-mono mb-3">Membership</div>
<h1 className="font-heading text-white text-4xl xl:text-5xl font-bold leading-tight mb-4"> <h1 className="font-heading text-white text-4xl xl:text-5xl font-bold leading-tight mb-4">
Join AV Beat Join AV Beat
</h1> </h1>
@@ -131,7 +131,7 @@ function ForumRegisterInner() {
<div className="mt-6 text-[11px] text-[#666] leading-relaxed max-w-md border-t border-[#252525] pt-4"> <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 &amp; contributors:</strong> a separate contributor application is required to submit press releases. <strong className="text-[#888]">PR firms &amp; contributors:</strong> a separate contributor application is required to submit press releases.
{" "} {" "}
<Link href="/about/advertise" className="text-[#E67E22] hover:underline">Contact us</Link> if you represent a manufacturer or agency. <Link href="/about/advertise" className="text-[#3b82f6] hover:underline">Contact us</Link> if you represent a manufacturer or agency.
</div> </div>
</aside> </aside>
@@ -148,7 +148,7 @@ function ForumRegisterInner() {
className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4 shadow-2xl" className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4 shadow-2xl"
> >
<div className="hidden lg:block mb-2"> <div className="hidden lg:block mb-2">
<div className="text-[10px] uppercase tracking-widest text-[#E67E22] font-mono mb-1">Create your account</div> <div className="text-[10px] uppercase tracking-widest text-[#3b82f6] font-mono mb-1">Create your account</div>
<div className="text-[#888] text-xs">Takes 30 seconds. Free, no card needed.</div> <div className="text-[#888] text-xs">Takes 30 seconds. Free, no card needed.</div>
</div> </div>
{error && ( {error && (
@@ -171,7 +171,7 @@ function ForumRegisterInner() {
onChange={(e) => setFullName(e.target.value)} onChange={(e) => setFullName(e.target.value)}
autoComplete="name" autoComplete="name"
required 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-[#E67E22]" 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]"
/> />
</div> </div>
<div> <div>
@@ -184,7 +184,7 @@ function ForumRegisterInner() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
required 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-[#E67E22]" 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]"
/> />
</div> </div>
<div> <div>
@@ -198,7 +198,7 @@ function ForumRegisterInner() {
autoComplete="new-password" autoComplete="new-password"
required required
minLength={6} 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-[#E67E22]" 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]"
/> />
</div> </div>
<div> <div>
@@ -211,21 +211,21 @@ function ForumRegisterInner() {
onChange={(e) => setConfirm(e.target.value)} onChange={(e) => setConfirm(e.target.value)}
autoComplete="new-password" autoComplete="new-password"
required 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-[#E67E22]" 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]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
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" 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"
> >
{loading ? 'Creating account…' : 'Create Free Account'} {loading ? 'Creating account…' : 'Create Free Account'}
</button> </button>
<p className="text-[10px] text-[#666] leading-relaxed text-center px-2"> <p className="text-[10px] text-[#666] leading-relaxed text-center px-2">
By creating an account you agree to our{' '} By creating an account you agree to our{' '}
<Link href="/terms" className="text-[#888] hover:text-[#E67E22] underline">terms</Link> <Link href="/terms" className="text-[#888] hover:text-[#3b82f6] underline">terms</Link>
{' '}and{' '} {' '}and{' '}
<Link href="/privacy" className="text-[#888] hover:text-[#E67E22] underline">privacy policy</Link>. <Link href="/privacy" className="text-[#888] hover:text-[#3b82f6] underline">privacy policy</Link>.
We&rsquo;ll never sell your email. We&rsquo;ll never sell your email.
</p> </p>
<SocialAuthButtons next={next} /> <SocialAuthButtons next={next} />
@@ -233,7 +233,7 @@ function ForumRegisterInner() {
Already have an account?{' '} Already have an account?{' '}
<Link <Link
href={`/forum/login?next=${encodeURIComponent(next)}`} href={`/forum/login?next=${encodeURIComponent(next)}`}
className="text-[#E67E22] hover:underline font-semibold" className="text-[#3b82f6] hover:underline font-semibold"
> >
Sign in Sign in
</Link> </Link>

View File

@@ -67,8 +67,8 @@ function timeAgo(dateStr: string): string {
function Avatar({ name, isAI }: { name: string; isAI: boolean }) { function Avatar({ name, isAI }: { name: string; isAI: boolean }) {
if (isAI) { if (isAI) {
return ( return (
<div className="w-9 h-9 rounded-full bg-[#1e3a5f] border border-[#E67E22] flex items-center justify-center flex-shrink-0"> <div className="w-9 h-9 rounded-full bg-[#1e3a5f] border border-[#3b82f6] flex items-center justify-center flex-shrink-0">
<span className="text-[#E67E22] text-xs font-bold">AI</span> <span className="text-[#3b82f6] text-xs font-bold">AI</span>
</div> </div>
); );
} }
@@ -133,7 +133,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
} }
}; };
const scoreColor = counts.vote_score > 0 ? 'text-[#E67E22]' : counts.vote_score < 0 ? 'text-red-400' : 'text-[#666]'; const scoreColor = counts.vote_score > 0 ? 'text-[#3b82f6]' : counts.vote_score < 0 ? 'text-red-400' : 'text-[#666]';
return ( return (
<div className="flex items-center gap-1 mt-2"> <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'} 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 ${ className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-body font-semibold transition-colors border ${
currentVote === 1 currentVote === 1
? 'bg-[#E67E22]/20 border-[#E67E22] text-[#E67E22]' ? 'bg-[#3b82f6]/20 border-[#3b82f6] text-[#3b82f6]'
: 'bg-transparent border-[#2a2a2a] text-[#666] hover:border-[#E67E22] hover:text-[#E67E22]' : 'bg-transparent border-[#2a2a2a] text-[#666] hover:border-[#3b82f6] hover:text-[#3b82f6]'
} disabled:opacity-40 disabled:cursor-not-allowed`} } disabled:opacity-40 disabled:cursor-not-allowed`}
aria-label="Upvote" aria-label="Upvote"
> >
@@ -178,7 +178,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
</button> </button>
{!isAuthenticated && ( {!isAuthenticated && (
<Link href="/login" className="text-xs font-body text-[#555] hover:text-[#E67E22] ml-1 transition-colors"> <Link href="/login" className="text-xs font-body text-[#555] hover:text-[#3b82f6] ml-1 transition-colors">
Sign in to vote Sign in to vote
</Link> </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"> <div className="bg-red-900/20 border border-red-800 rounded-lg p-4 text-red-400 font-body">
{error || 'Thread not found.'} {error || 'Thread not found.'}
</div> </div>
<Link href="/forum" className="mt-4 inline-block text-[#E67E22] hover:underline font-body text-sm"> <Link href="/forum" className="mt-4 inline-block text-[#3b82f6] hover:underline font-body text-sm">
Back to Forum Back to Forum
</Link> </Link>
</div> </div>
@@ -342,11 +342,11 @@ export default function ForumThreadPage() {
<div className="bg-[#1a2535] border-b border-[#2a3a50]"> <div className="bg-[#1a2535] border-b border-[#2a3a50]">
<div className="max-w-container mx-auto px-4 py-4"> <div className="max-w-container mx-auto px-4 py-4">
<nav className="text-sm font-body text-[#666]"> <nav className="text-sm font-body text-[#666]">
<Link href="/forum" className="hover:text-[#E67E22] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#3b82f6] transition-colors">Forum</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
{category && ( {category && (
<> <>
<Link href={`/forum/${category.slug}`} className="hover:text-[#E67E22] transition-colors">{category.name}</Link> <Link href={`/forum/${category.slug}`} className="hover:text-[#3b82f6] transition-colors">{category.name}</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
</> </>
)} )}
@@ -366,7 +366,7 @@ export default function ForumThreadPage() {
<Avatar name={thread.author_name} isAI={false} /> <Avatar name={thread.author_name} isAI={false} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2"> <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-[#E67E22] 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-[#3b82f6] hover:underline">{thread.author_name}</Link>
<span className="text-[#555] font-body text-xs">·</span> <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">{timeAgo(thread.created_at)}</span>
<span className="text-[#555] font-body text-xs">·</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} /> <Avatar name={reply.author_name} isAI={reply.is_ai_response} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2 flex-wrap"> <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-[#E67E22] 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-[#3b82f6] hover:underline">{reply.author_name}</Link>
{reply.is_ai_response && ( {reply.is_ai_response && (
<span className="text-xs bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/30 px-2 py-0.5 rounded font-body font-semibold"> <span className="text-xs bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/30 px-2 py-0.5 rounded font-body font-semibold">
AI Assistant AI Assistant
</span> </span>
)} )}
@@ -443,12 +443,12 @@ export default function ForumThreadPage() {
{/* AI Assistant Panel */} {/* AI Assistant Panel */}
<div className="bg-[#0d1f35] border border-[#1e3a5f] rounded-lg p-5 mb-6"> <div className="bg-[#0d1f35] border border-[#1e3a5f] rounded-lg p-5 mb-6">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<div className="w-7 h-7 rounded-full bg-[#1e3a5f] border border-[#E67E22] flex items-center justify-center"> <div className="w-7 h-7 rounded-full bg-[#1e3a5f] border border-[#3b82f6] flex items-center justify-center">
<span className="text-[#E67E22] text-xs font-bold">AI</span> <span className="text-[#3b82f6] text-xs font-bold">AI</span>
</div> </div>
<div> <div>
<span className="text-white font-body font-semibold text-sm">AV Beat AI Assistant</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> <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>
</div> </div>
</div> </div>
<p className="text-[#7a9ab8] font-body text-xs mb-3"> <p className="text-[#7a9ab8] font-body text-xs mb-3">
@@ -461,12 +461,12 @@ export default function ForumThreadPage() {
value={aiQuestion} value={aiQuestion}
onChange={e => setAiQuestion(e.target.value)} onChange={e => setAiQuestion(e.target.value)}
disabled={aiLoading} 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-[#E67E22] 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-[#3b82f6] disabled:opacity-50"
/> />
<button <button
type="submit" type="submit"
disabled={aiLoading || !aiQuestion.trim()} disabled={aiLoading || !aiQuestion.trim()}
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" 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"
> >
{aiLoading ? ( {aiLoading ? (
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
@@ -495,7 +495,7 @@ export default function ForumThreadPage() {
onChange={e => setReplyBody(e.target.value)} onChange={e => setReplyBody(e.target.value)}
required required
rows={5} 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-[#E67E22] 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-[#3b82f6] resize-none"
/> />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<ImageAttachButton onInsert={(md) => setReplyBody((b) => (b || "") + md)} /> <ImageAttachButton onInsert={(md) => setReplyBody((b) => (b || "") + md)} />
@@ -509,7 +509,7 @@ export default function ForumThreadPage() {
<button <button
type="submit" type="submit"
disabled={submitting || !replyBody.trim()} disabled={submitting || !replyBody.trim()}
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" 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"
> >
{submitting ? 'Posting...' : 'Post Reply'} {submitting ? 'Posting...' : 'Post Reply'}
</button> </button>
@@ -526,13 +526,13 @@ export default function ForumThreadPage() {
<div className="flex items-center justify-center gap-3"> <div className="flex items-center justify-center gap-3">
<Link <Link
href={`/forum/login?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`} href={`/forum/login?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`}
className="bg-[#E67E22] hover:bg-[#C8651B] text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
Sign In Sign In
</Link> </Link>
<Link <Link
href={`/forum/register?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`} href={`/forum/register?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`}
className="border border-[#E67E22] text-[#E67E22] hover:bg-[#E67E22]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="border border-[#3b82f6] text-[#3b82f6] hover:bg-[#3b82f6]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
Register Register
</Link> </Link>

View File

@@ -49,7 +49,7 @@ export default async function GearPage() {
<Link <Link
key={article.slug} key={article.slug}
href={`/articles/${article.slug}`} href={`/articles/${article.slug}`}
className="bg-[#111] border border-[#222] overflow-hidden group hover:border-[#E67E22] transition-colors"> className="bg-[#111] border border-[#222] overflow-hidden group hover:border-[#3b82f6] transition-colors">
<div className="relative h-[200px] overflow-hidden"> <div className="relative h-[200px] overflow-hidden">
<AppImage <AppImage
src={article.image} src={article.image}
@@ -59,7 +59,7 @@ export default async function GearPage() {
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
/> />
<div className="absolute top-2 left-2"> <div className="absolute top-2 left-2">
<span className="bg-[#E67E22] text-white font-body text-[9px] font-bold px-2 py-0.5 tracking-wider uppercase"> <span className="bg-[#3b82f6] text-white font-body text-[9px] font-bold px-2 py-0.5 tracking-wider uppercase">
{article.category} {article.category}
</span> </span>
</div> </div>
@@ -74,7 +74,7 @@ export default async function GearPage() {
)} )}
<span className="text-[#555] font-body text-[11px]">{article.readTime}</span> <span className="text-[#555] font-body text-[11px]">{article.readTime}</span>
</div> </div>
<h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-2 group-hover:text-[#E67E22] transition-colors line-clamp-3"> <h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-2 group-hover:text-[#3b82f6] transition-colors line-clamp-3">
{article.title} {article.title}
</h2> </h2>
<p className="text-[#666] font-body text-xs leading-relaxed line-clamp-2">{article.excerpt}</p> <p className="text-[#666] font-body text-xs leading-relaxed line-clamp-2">{article.excerpt}</p>

View File

@@ -22,7 +22,7 @@ export default function GlobalError({
)} )}
<button <button
onClick={() => reset()} onClick={() => reset()}
style={{ padding: "8px 16px", background: "#E67E22", color: "white", border: 0, cursor: "pointer", borderRadius: 2 }} style={{ padding: "8px 16px", background: "#3b82f6", color: "white", border: 0, cursor: "pointer", borderRadius: 2 }}
> >
Try again Try again
</button> </button>

View File

@@ -314,7 +314,7 @@ export default function ArticleFeed() {
value={sortOrder} value={sortOrder}
onChange={(e) => { setSortOrder(e.target.value as SortOrder); setPage(1); setInfiniteCount(ARTICLES_PER_PAGE); }} onChange={(e) => { setSortOrder(e.target.value as SortOrder); setPage(1); setInfiniteCount(ARTICLES_PER_PAGE); }}
aria-label="Sort articles" 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-[#E67E22] focus:ring-1 focus:ring-[#E67E22]/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-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]"
> >
<option value="recent">Recent</option> <option value="recent">Recent</option>
<option value="trending">Trending</option> <option value="trending">Trending</option>
@@ -333,8 +333,8 @@ export default function ArticleFeed() {
onClick={() => handleFeedModeChange("pagination")} onClick={() => handleFeedModeChange("pagination")}
aria-pressed={feedMode === "pagination"} aria-pressed={feedMode === "pagination"}
title="Pagination mode" 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-[#E67E22] ${ 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-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" feedMode === "pagination" ?"bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}> }`}>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true"> <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"/> <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")} onClick={() => handleFeedModeChange("infinite")}
aria-pressed={feedMode === "infinite"} aria-pressed={feedMode === "infinite"}
title="Infinite scroll mode" 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-[#E67E22] ${ 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-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" feedMode === "infinite" ?"bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}> }`}>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true"> <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"/> <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} value={dateFrom}
onChange={(e) => { setDateFrom(e.target.value); setPage(1); }} onChange={(e) => { setDateFrom(e.target.value); setPage(1); }}
max={dateTo || undefined} 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-[#E67E22] 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-[#3b82f6] transition-colors [color-scheme:dark]"
title="From date" title="From date"
aria-label="Filter articles from date" aria-label="Filter articles from date"
/> />
@@ -382,7 +382,7 @@ export default function ArticleFeed() {
value={dateTo} value={dateTo}
onChange={(e) => { setDateTo(e.target.value); setPage(1); }} onChange={(e) => { setDateTo(e.target.value); setPage(1); }}
min={dateFrom || undefined} 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-[#E67E22] 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-[#3b82f6] transition-colors [color-scheme:dark]"
title="To date" title="To date"
aria-label="Filter articles to date" aria-label="Filter articles to date"
/> />
@@ -401,9 +401,9 @@ export default function ArticleFeed() {
onClick={() => setShowAdvanced((v) => !v)} onClick={() => setShowAdvanced((v) => !v)}
aria-expanded={showAdvanced} aria-expanded={showAdvanced}
aria-controls={`${feedId}-advanced`} 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-[#E67E22] 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-[#3b82f6] whitespace-nowrap ${
showAdvanced || hasAdvancedActive showAdvanced || hasAdvancedActive
? "bg-[#E67E22]/15 text-[#E67E22] border-[#E67E22]/40" ? "bg-[#3b82f6]/15 text-[#3b82f6] border-[#3b82f6]/40"
: "bg-[#111] text-[#666] border-[#2a2a2a] hover:text-[#aaa] hover:border-[#3a3a3a]" : "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"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
@@ -411,7 +411,7 @@ export default function ArticleFeed() {
</svg> </svg>
More More
{hasAdvancedActive && ( {hasAdvancedActive && (
<span className="w-1.5 h-1.5 rounded-full bg-[#E67E22] inline-block ml-0.5" aria-label="Active filters" /> <span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] inline-block ml-0.5" aria-label="Active filters" />
)} )}
</button> </button>
</div> </div>
@@ -428,9 +428,9 @@ export default function ArticleFeed() {
type="button" type="button"
onClick={() => { setActiveCategory(cat); setPage(1); }} onClick={() => { setActiveCategory(cat); setPage(1); }}
aria-pressed={isActive} 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-[#E67E22] ${ 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] ${
isActive isActive
? "bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/50" ? "bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50"
: "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc] hover:border-[#3a3a3a]" : "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc] hover:border-[#3a3a3a]"
}`}> }`}>
{cat} {cat}
@@ -458,7 +458,7 @@ export default function ArticleFeed() {
id={`${feedId}-author`} id={`${feedId}-author`}
value={authorFilter} value={authorFilter}
onChange={(e) => { setAuthorFilter(e.target.value); setPage(1); }} 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-[#E67E22] focus:ring-1 focus:ring-[#E67E22]/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-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]/30 transition-colors [color-scheme:dark]"
aria-label="Filter by author"> aria-label="Filter by author">
<option value="All">All Authors</option> <option value="All">All Authors</option>
{uniqueAuthors.map((author) => ( {uniqueAuthors.map((author) => (
@@ -483,10 +483,10 @@ export default function ArticleFeed() {
key={sf} key={sf}
onClick={() => { setSourceFilter(sf); setPage(1); }} onClick={() => { setSourceFilter(sf); setPage(1); }}
aria-pressed={isActive} 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-[#E67E22] ${ 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] ${
isActive isActive
? sf === "Imported" ?"bg-amber-500/20 text-amber-400 border border-amber-500/40" ? 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-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" : 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} {sf}
{sf === "Imported" && importedCount > 0 && ( {sf === "Imported" && importedCount > 0 && (
@@ -507,7 +507,7 @@ export default function ArticleFeed() {
<button <button
type="button" type="button"
onClick={() => { setAuthorFilter("All"); setDateFrom(""); setDateTo(""); setPage(1); }} onClick={() => { setAuthorFilter("All"); setDateFrom(""); setDateTo(""); setPage(1); }}
className="font-body text-[11px] text-[#E67E22] hover:underline focus:outline-none focus-visible:underline"> className="font-body text-[11px] text-[#3b82f6] hover:underline focus:outline-none focus-visible:underline">
Clear advanced filters Clear advanced filters
</button> </button>
</div> </div>
@@ -537,7 +537,7 @@ export default function ArticleFeed() {
{sortedFiltered.length === 0 && ( {sortedFiltered.length === 0 && (
<button <button
onClick={clearAllFilters} onClick={clearAllFilters}
className="ml-1 text-[#E67E22] hover:underline focus:outline-none focus-visible:underline"> className="ml-1 text-[#3b82f6] hover:underline focus:outline-none focus-visible:underline">
Clear filters Clear filters
</button> </button>
)} )}
@@ -557,7 +557,7 @@ export default function ArticleFeed() {
</p> </p>
<button <button
onClick={clearAllFilters} onClick={clearAllFilters}
className="mt-4 font-body text-sm text-[#E67E22] hover:underline focus:outline-none focus-visible:underline"> className="mt-4 font-body text-sm text-[#3b82f6] hover:underline focus:outline-none focus-visible:underline">
Clear filters Clear filters
</button> </button>
</div> </div>
@@ -585,7 +585,7 @@ export default function ArticleFeed() {
const promotedClass = promoted === 'premium' 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' ? 'border-l-4 border-amber-400 bg-gradient-to-r from-amber-500/10 to-transparent pl-3 -ml-2 mr-0'
: promoted === 'client' : promoted === 'client'
? 'border-l-4 border-[#E67E22] bg-gradient-to-r from-[#E67E22]/8 to-transparent pl-3 -ml-2 mr-0' ? 'border-l-4 border-[#3b82f6] bg-gradient-to-r from-[#3b82f6]/8 to-transparent pl-3 -ml-2 mr-0'
: ''; : '';
return ( return (
<Fragment key={`feed-${i}`}> <Fragment key={`feed-${i}`}>
@@ -593,7 +593,7 @@ export default function ArticleFeed() {
<Link <Link
href={articleHref} href={articleHref}
{...linkProps} {...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-[#E67E22]" 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]"
tabIndex={-1} tabIndex={-1}
aria-hidden="true" aria-hidden="true"
> >
@@ -614,21 +614,21 @@ export default function ArticleFeed() {
</span> </span>
)} )}
{promoted === 'client' && ( {promoted === 'client' && (
<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'}> <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'}>
Advertiser Advertiser
</span> </span>
)} )}
{article?.category && article.category.toLowerCase() !== "legacy" && ( {article?.category && article.category.toLowerCase() !== "legacy" && (
<span className="font-body text-[10px] font-bold text-[#E67E22] uppercase tracking-wide"> <span className="font-body text-[10px] font-bold text-[#3b82f6] uppercase tracking-wide">
{article.category} {article.category}
</span> </span>
)} )}
</div> </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-[#E67E22] 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-[#3b82f6] transition-colors duration-200 line-clamp-2">
<Link <Link
href={articleHref} href={articleHref}
{...linkProps} {...linkProps}
className="focus:outline-none focus-visible:text-[#E67E22] focus-visible:underline"> className="focus:outline-none focus-visible:text-[#3b82f6] focus-visible:underline">
{article?.title} {article?.title}
</Link> </Link>
</h3> </h3>
@@ -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"> 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> <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>
<a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}&via=AVBeat`} <a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}&via=BroadcastBeat`}
target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)" target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)"
onClick={(e) => e.stopPropagation()} 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"> 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)}`} <a href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`}
aria-label="Share via email" title="Email" aria-label="Share via email" title="Email"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#E67E22] hover:bg-[#1a1a1a] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#3b82f6] hover:bg-[#1a1a1a] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <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" /> <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> </svg>
@@ -735,9 +735,9 @@ export default function ArticleFeed() {
{isLoadingMore && ( {isLoadingMore && (
<div className="flex items-center justify-center gap-2 py-6" aria-live="polite" aria-label="Loading more articles"> <div className="flex items-center justify-center gap-2 py-6" aria-live="polite" aria-label="Loading more articles">
<div className="flex gap-1"> <div className="flex gap-1">
<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-[#3b82f6] 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-[#3b82f6] animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#E67E22] animate-bounce" style={{ animationDelay: "300ms" }} /> <span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] animate-bounce" style={{ animationDelay: "300ms" }} />
</div> </div>
<span className="font-body text-xs text-[#555]">Loading more articles</span> <span className="font-body text-xs text-[#555]">Loading more articles</span>
</div> </div>
@@ -762,7 +762,7 @@ export default function ArticleFeed() {
</p> </p>
<div className="flex items-center gap-1" role="navigation" aria-label="Pagination"> <div className="flex items-center gap-1" role="navigation" aria-label="Pagination">
<button <button
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]" className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"
disabled={page === 1} disabled={page === 1}
onClick={() => { setPage(Math.max(1, page - 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }} onClick={() => { setPage(Math.max(1, page - 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label="Previous page"> aria-label="Previous page">
@@ -786,7 +786,7 @@ export default function ArticleFeed() {
) : ( ) : (
<button <button
key={p} key={p}
className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] ${page === p ? "active" : ""}`} className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${page === p ? "active" : ""}`}
onClick={() => { setPage(p as number); window.scrollTo({ top: 0, behavior: "smooth" }); }} onClick={() => { setPage(p as number); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label={`Page ${p}`} aria-label={`Page ${p}`}
aria-current={page === p ? "page" : undefined}> aria-current={page === p ? "page" : undefined}>
@@ -796,7 +796,7 @@ export default function ArticleFeed() {
); );
})()} })()}
<button <button
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]" className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"
disabled={page === totalPages} disabled={page === totalPages}
onClick={() => { setPage(Math.min(totalPages, page + 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }} onClick={() => { setPage(Math.min(totalPages, page + 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label="Next page"> aria-label="Next page">

View File

@@ -9,7 +9,7 @@ const hero = {
title: "Sony BRC-AM7 + HXC-FZ90: Better Together in the Field", title: "Sony BRC-AM7 + HXC-FZ90: Better Together in the Field",
category: "BROADCAST", category: "BROADCAST",
excerpt: "Sony's latest camera pairing delivers unprecedented flexibility for field production teams, combining 4K HDR capture with compact form factors designed for the realities of live broadcast.", excerpt: "Sony's latest camera pairing delivers unprecedented flexibility for field production teams, combining 4K HDR capture with compact form factors designed for the realities of live broadcast.",
image: "https://www.broadcastbeat.com/wp-content/uploads/BRC-AM7_012-Mid.png", image: "https://www.avbeat.com/wp-content/uploads/BRC-AM7_012-Mid.png",
alt: "Sony BRC-AM7 broadcast camera in field setting", alt: "Sony BRC-AM7 broadcast camera in field setting",
slug: "sony-brc-am7-hxc-fz90", slug: "sony-brc-am7-hxc-fz90",
}; };
@@ -19,28 +19,28 @@ const rotatingStories = [
{ {
title: "Sony\'s BVM-HX1710: The Compact Powerhouse Changing How Broadcast & Post See Their Work", title: "Sony\'s BVM-HX1710: The Compact Powerhouse Changing How Broadcast & Post See Their Work",
category: "POST PRODUCTION", category: "POST PRODUCTION",
image: "https://www.broadcastbeat.com/wp-content/uploads/bvmhx1710_3q_250131_02-copy-scaled.png", image: "https://www.avbeat.com/wp-content/uploads/bvmhx1710_3q_250131_02-copy-scaled.png",
alt: "Sony BVM-HX1710 broadcast monitor three-quarter view", alt: "Sony BVM-HX1710 broadcast monitor three-quarter view",
slug: "sony-bvm-hx1710", slug: "sony-bvm-hx1710",
}, },
{ {
title: "The Top 5 Challenges in MCRs & NOCs—And How to Stay Ahead", title: "The Top 5 Challenges in MCRs & NOCs—And How to Stay Ahead",
category: "BROADCAST", category: "BROADCAST",
image: "https://www.broadcastbeat.com/wp-content/uploads/data-insight-ip-monitoring-1024x597.pngw3_.webp", image: "https://www.avbeat.com/wp-content/uploads/data-insight-ip-monitoring-1024x597.pngw3_.webp",
alt: "IP monitoring dashboard showing data insights for MCR operations", alt: "IP monitoring dashboard showing data insights for MCR operations",
slug: "top-5-challenges-mcrs-nocs", slug: "top-5-challenges-mcrs-nocs",
}, },
{ {
title: "TESSERA SQ200: THE NEW PACE-SETTER IN LED PROCESSING", title: "TESSERA SQ200: THE NEW PACE-SETTER IN LED PROCESSING",
category: "FEATURED", category: "FEATURED",
image: "https://www.broadcastbeat.com/wp-content/uploads/Hero-Image_SQ200-Gen-3_Sep2025_CMYK.jpg", image: "https://www.avbeat.com/wp-content/uploads/Hero-Image_SQ200-Gen-3_Sep2025_CMYK.jpg",
alt: "Tessera SQ200 Gen 3 LED processor hero image", alt: "Tessera SQ200 Gen 3 LED processor hero image",
slug: "tessera-sq200-led-processing", slug: "tessera-sq200-led-processing",
}, },
{ {
title: "Backlight adds real-time collaboration to Iconik, AI-powered livestreams to Wildmoka at IBC2025", title: "Backlight adds real-time collaboration to Iconik, AI-powered livestreams to Wildmoka at IBC2025",
category: "BROADCAST", category: "BROADCAST",
image: "https://www.broadcastbeat.com/wp-content/uploads/Iconik-scaled.jpeg", image: "https://www.avbeat.com/wp-content/uploads/Iconik-scaled.jpeg",
alt: "Iconik media asset management platform interface", alt: "Iconik media asset management platform interface",
slug: "backlight-iconik-wildmoka-ibc2025", slug: "backlight-iconik-wildmoka-ibc2025",
}, },

View File

@@ -8,7 +8,7 @@ const spotlights = [
{ {
name: "Vizrt", name: "Vizrt",
title: "From Start-Up to Global Powerhouse in Video in Just Two Decades", title: "From Start-Up to Global Powerhouse in Video in Just Two Decades",
image: "https://www.broadcastbeat.com/wp-content/uploads/michael-320x220.jpg", image: "https://www.avbeat.com/wp-content/uploads/michael-320x220.jpg",
alt: "Vizrt company spotlight profile image", alt: "Vizrt company spotlight profile image",
tag: "Company", tag: "Company",
href: "/news" href: "/news"
@@ -16,7 +16,7 @@ const spotlights = [
{ {
name: "Scott Freeman", name: "Scott Freeman",
title: "The Sherlock Holmes of Video Post-Production", title: "The Sherlock Holmes of Video Post-Production",
image: "https://www.broadcastbeat.com/wp-content/uploads/scott-freeman.jpg", image: "https://www.avbeat.com/wp-content/uploads/scott-freeman.jpg",
alt: "Scott Freeman video post-production professional headshot", alt: "Scott Freeman video post-production professional headshot",
tag: "Profile", tag: "Profile",
href: "/news" href: "/news"
@@ -24,7 +24,7 @@ const spotlights = [
{ {
name: "Al Roker", name: "Al Roker",
title: "A Weatherman Who Wears Many Hats", title: "A Weatherman Who Wears Many Hats",
image: "https://www.broadcastbeat.com/wp-content/uploads/al-roker2-320x220.jpg", image: "https://www.avbeat.com/wp-content/uploads/al-roker2-320x220.jpg",
alt: "Al Roker television weatherman and broadcaster portrait", alt: "Al Roker television weatherman and broadcaster portrait",
tag: "Profile", tag: "Profile",
href: "/news" href: "/news"
@@ -32,7 +32,7 @@ const spotlights = [
{ {
name: "Michael Kammes", name: "Michael Kammes",
title: "Shaping the Best Workflow for Media Organizations", title: "Shaping the Best Workflow for Media Organizations",
image: "https://www.broadcastbeat.com/wp-content/uploads/kammes-2-320x220.jpg", image: "https://www.avbeat.com/wp-content/uploads/kammes-2-320x220.jpg",
alt: "Michael Kammes media workflow specialist headshot", alt: "Michael Kammes media workflow specialist headshot",
tag: "Profile", tag: "Profile",
href: "/news" href: "/news"
@@ -40,7 +40,7 @@ const spotlights = [
{ {
name: "Gary Trenda", name: "Gary Trenda",
title: "Top Frequency \'Traffic Cop\' for Wireless Microphones", title: "Top Frequency \'Traffic Cop\' for Wireless Microphones",
image: "https://www.broadcastbeat.com/wp-content/uploads/gary-320x220.jpg", image: "https://www.avbeat.com/wp-content/uploads/gary-320x220.jpg",
alt: "Gary Trenda wireless microphone frequency coordinator portrait", alt: "Gary Trenda wireless microphone frequency coordinator portrait",
tag: "Profile", tag: "Profile",
href: "/news" href: "/news"
@@ -48,7 +48,7 @@ const spotlights = [
{ {
name: "Jim Anderson & Ulrike Schwarz", name: "Jim Anderson & Ulrike Schwarz",
title: "Partners in Immersive Sound", title: "Partners in Immersive Sound",
image: "https://www.broadcastbeat.com/wp-content/uploads/urike-320x220.jpg", image: "https://www.avbeat.com/wp-content/uploads/urike-320x220.jpg",
alt: "Jim Anderson and Ulrike Schwarz immersive sound designers", alt: "Jim Anderson and Ulrike Schwarz immersive sound designers",
tag: "Profile", tag: "Profile",
href: "/news" href: "/news"
@@ -56,7 +56,7 @@ const spotlights = [
{ {
name: "Sound Effects", name: "Sound Effects",
title: "Creating Cutting-Edge New Sound Effects — Film by Film", title: "Creating Cutting-Edge New Sound Effects — Film by Film",
image: "https://www.broadcastbeat.com/wp-content/uploads/mark-spot-320x215.jpg", image: "https://www.avbeat.com/wp-content/uploads/mark-spot-320x215.jpg",
alt: "Sound effects creation for film and broadcast production", alt: "Sound effects creation for film and broadcast production",
tag: "Feature", tag: "Feature",
href: "/gear" href: "/gear"
@@ -64,7 +64,7 @@ const spotlights = [
{ {
name: "Libby Casey", name: "Libby Casey",
title: "Pioneering Television Broadcasting at the Washington Post", title: "Pioneering Television Broadcasting at the Washington Post",
image: "https://www.broadcastbeat.com/wp-content/uploads/spotlight-320x215.jpg", image: "https://www.avbeat.com/wp-content/uploads/spotlight-320x215.jpg",
alt: "Libby Casey television broadcaster Washington Post studio", alt: "Libby Casey television broadcaster Washington Post studio",
tag: "Profile", tag: "Profile",
href: "/news" href: "/news"
@@ -72,7 +72,7 @@ const spotlights = [
{ {
name: "NBCU Academy", name: "NBCU Academy",
title: "Preparing Students for Careers in News, Technology and Broadcast", title: "Preparing Students for Careers in News, Technology and Broadcast",
image: "https://www.broadcastbeat.com/wp-content/uploads/nbcu-spotlight-320x215.jpg", image: "https://www.avbeat.com/wp-content/uploads/nbcu-spotlight-320x215.jpg",
alt: "NBCU Academy students learning broadcast journalism and technology", alt: "NBCU Academy students learning broadcast journalism and technology",
tag: "Education", tag: "Education",
href: "/about" href: "/about"
@@ -80,7 +80,7 @@ const spotlights = [
{ {
name: "BirdDog", name: "BirdDog",
title: "Simplifying Global Television Production", title: "Simplifying Global Television Production",
image: "https://www.broadcastbeat.com/wp-content/uploads/bird-sptfeat.jpg", image: "https://www.avbeat.com/wp-content/uploads/bird-sptfeat.jpg",
alt: "BirdDog broadcast production technology company spotlight", alt: "BirdDog broadcast production technology company spotlight",
tag: "Company", tag: "Company",
href: "/news" href: "/news"
@@ -88,7 +88,7 @@ const spotlights = [
{ {
name: "Michael Cioni", name: "Michael Cioni",
title: "Speeding Digital Cinema with Camera to Cloud", title: "Speeding Digital Cinema with Camera to Cloud",
image: "https://www.broadcastbeat.com/wp-content/uploads/michael-copy-320x220.jpg", image: "https://www.avbeat.com/wp-content/uploads/michael-copy-320x220.jpg",
alt: "Michael Cioni digital cinema and camera to cloud technology expert", alt: "Michael Cioni digital cinema and camera to cloud technology expert",
tag: "Profile", tag: "Profile",
href: "/technology" href: "/technology"
@@ -96,7 +96,7 @@ const spotlights = [
{ {
name: "Jeff Greenberg", name: "Jeff Greenberg",
title: "Finding Workarounds for Gaps in the Video Production Workflow", title: "Finding Workarounds for Gaps in the Video Production Workflow",
image: "https://www.broadcastbeat.com/wp-content/uploads/greenberg-320x215.jpg", image: "https://www.avbeat.com/wp-content/uploads/greenberg-320x215.jpg",
alt: "Jeff Greenberg video production workflow consultant headshot", alt: "Jeff Greenberg video production workflow consultant headshot",
tag: "Profile", tag: "Profile",
href: "/technology" href: "/technology"

View File

@@ -13,14 +13,14 @@ import SponsorLogoStrip from "@/components/SponsorLogoStrip";
import CompanyMentionsHover from "@/components/CompanyMentionsHover"; import CompanyMentionsHover from "@/components/CompanyMentionsHover";
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'AV Beat — Where AV Meets IT', title: 'AV Beat — Broadcast Engineering News & Insights',
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.', description: 'The digital platform for broadcast engineering professionals. Breaking news, deep-dive features, and industry spotlights from NAB to IBC and beyond.',
alternates: { alternates: {
canonical: '/', canonical: '/',
}, },
openGraph: { openGraph: {
title: 'AV Beat — Where AV Meets IT', title: 'AV Beat — Broadcast News',
description: 'Breaking news and insights at the intersection of pro AV and IT.', description: 'Breaking news and insights for broadcast engineering professionals.',
url: '/', url: '/',
type: 'website', type: 'website',
images: [ images: [
@@ -28,14 +28,14 @@ export const metadata: Metadata = {
url: '/assets/images/og-image.png', url: '/assets/images/og-image.png',
width: 1200, width: 1200,
height: 630, height: 630,
alt: 'AV Beat — Where AV Meets IT', alt: 'AV Beat — Broadcast Engineering News & Insights',
}, },
], ],
}, },
twitter: { twitter: {
card: 'summary_large_image', card: 'summary_large_image',
title: 'AV Beat — Where AV Meets IT', title: 'AV Beat — Broadcast News',
description: 'Breaking news and insights at the intersection of pro AV and IT.', description: 'Breaking news and insights for broadcast engineering professionals.',
images: ['/assets/images/og-image.png'], images: ['/assets/images/og-image.png'],
}, },
}; };
@@ -138,7 +138,7 @@ export default function HomePage() {
{/* H1 Hero Heading */} {/* H1 Hero Heading */}
<div className="sr-only"> <div className="sr-only">
<h1>AV Beat Where AV Meets IT</h1> <h1>AV Beat Broadcast Engineering News & Insights</h1>
</div> </div>
{/* Featured: cinematic hero + 4-up rail below (FeaturedBentoFromDb) */} {/* Featured: cinematic hero + 4-up rail below (FeaturedBentoFromDb) */}

View File

@@ -5,7 +5,7 @@ export const dynamic = "force-dynamic";
export const revalidate = 0; export const revalidate = 0;
/** /**
* Image proxy: serves files from Supabase Storage under a clean broadcastbeat.com * Image proxy: serves files from Supabase Storage under a clean avbeat.com
* URL so article HTML doesn't ship the long `https://supabase.onsethost.com/ * URL so article HTML doesn't ship the long `https://supabase.onsethost.com/
* storage/v1/object/public/...` path. Better for SEO + makes images look * storage/v1/object/public/...` path. Better for SEO + makes images look
* first-party. * first-party.

View File

@@ -34,8 +34,8 @@ export const viewport: Viewport = {
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'), metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'),
title: 'AV Beat — Where AV Meets IT', title: 'AV Beat — Broadcast Engineering News & Insights',
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.', description: 'The digital platform for broadcast engineering professionals. Breaking news, deep-dive features, and industry spotlights from NAB to IBC and beyond.',
icons: { icons: {
icon: [ icon: [
{ url: '/favicon.ico', type: 'image/x-icon', sizes: 'any' }, { url: '/favicon.ico', type: 'image/x-icon', sizes: 'any' },
@@ -59,24 +59,24 @@ export const metadata: Metadata = {
locale: 'en_US', locale: 'en_US',
url: '/', url: '/',
siteName: 'AV Beat', siteName: 'AV Beat',
title: 'AV Beat — Where AV Meets IT', title: 'AV Beat — Broadcast News',
description: 'Breaking news and insights at the intersection of pro AV and IT.', description: 'Breaking news and insights for broadcast engineering professionals.',
images: [ images: [
{ {
url: '/assets/images/og-image.png', url: '/assets/images/og-image.png',
width: 1200, width: 1200,
height: 630, height: 630,
alt: 'AV Beat — Where AV Meets IT', alt: 'AV Beat — Broadcast Engineering News & Insights',
type: 'image/png' type: 'image/png'
}] }]
}, },
twitter: { twitter: {
card: 'summary_large_image', card: 'summary_large_image',
site: '@AVBeat', site: '@AV Beat',
creator: '@AVBeat', creator: '@AV Beat',
title: 'AV Beat — Where AV Meets IT', title: 'AV Beat — Broadcast News',
description: 'Breaking news and insights at the intersection of pro AV and IT.', description: 'Breaking news and insights for broadcast engineering professionals.',
images: ['/assets/images/og-image.png'] images: ['/assets/images/og-image.png']
}, },
robots: { robots: {
@@ -125,7 +125,7 @@ export default function RootLayout({
name: 'AV Beat', name: 'AV Beat',
url: 'https://avbeat.com', url: 'https://avbeat.com',
logo: "https://avbeat.com/assets/images/logo.png", 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.', description: 'The digital platform for broadcast engineering professionals. Breaking news, deep-dive features, and industry spotlights from NAB to IBC and beyond.',
sameAs: [ sameAs: [
'https://linkedin.com', 'https://linkedin.com',
'https://twitter.com', 'https://twitter.com',
@@ -144,7 +144,7 @@ export default function RootLayout({
{/* Skip to main content — WCAG 2.4.1 */} {/* Skip to main content — WCAG 2.4.1 */}
<a <a
href="#main-content" 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-[#E67E22] 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-[#3b82f6] focus:text-white focus:rounded focus:font-bold focus:text-sm focus:outline-none"
> >
Skip to main content Skip to main content
</a> </a>

View File

@@ -77,7 +77,7 @@ function LoginInner() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
required 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-[#E67E22]" 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]"
/> />
</div> </div>
<div> <div>
@@ -90,19 +90,19 @@ function LoginInner() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password" autoComplete="current-password"
required 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-[#E67E22]" 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]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#E67E22] hover:bg-[#C8651B] disabled:opacity-50 text-white font-body font-semibold py-2 rounded-lg transition-colors" className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold py-2 rounded-lg transition-colors"
> >
{loading ? 'Signing in…' : 'Sign in'} {loading ? 'Signing in…' : 'Sign in'}
</button> </button>
<p className="text-xs text-[#666] font-body text-center pt-2"> <p className="text-xs text-[#666] font-body text-center pt-2">
PR firm / contributor?{' '} PR firm / contributor?{' '}
<Link href="/client-login" className="text-[#E67E22] hover:underline"> <Link href="/client-login" className="text-[#3b82f6] hover:underline">
Use the contributor portal Use the contributor portal
</Link> </Link>
</p> </p>

View File

@@ -206,7 +206,7 @@ export default async function ManufacturerProfile({
</span> </span>
)} )}
{company.phone && ( {company.phone && (
<a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#888] hover:text-[#E67E22]"> <a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#888] hover:text-[#3b82f6]">
{company.phone} {company.phone}
</a> </a>
)} )}
@@ -217,7 +217,7 @@ export default async function ManufacturerProfile({
href={company.company_website} href={company.company_website}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#E67E22] hover:underline" className="text-[#3b82f6] hover:underline"
> >
Website Website
</a> </a>
@@ -227,7 +227,7 @@ export default async function ManufacturerProfile({
href={company.press_url} href={company.press_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#E67E22] hover:underline" className="text-[#3b82f6] hover:underline"
> >
Newsroom Newsroom
</a> </a>
@@ -237,7 +237,7 @@ export default async function ManufacturerProfile({
href={company.contact_url} href={company.contact_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#E67E22] hover:underline" className="text-[#3b82f6] hover:underline"
> >
Contact Contact
</a> </a>
@@ -247,7 +247,7 @@ export default async function ManufacturerProfile({
href={company.linkedin_url} href={company.linkedin_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#888] hover:text-[#E67E22]" className="text-[#888] hover:text-[#3b82f6]"
> >
LinkedIn LinkedIn
</a> </a>
@@ -257,7 +257,7 @@ export default async function ManufacturerProfile({
href={`https://twitter.com/${company.twitter_handle}`} href={`https://twitter.com/${company.twitter_handle}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#888] hover:text-[#E67E22]" className="text-[#888] hover:text-[#3b82f6]"
> >
X / Twitter X / Twitter
</a> </a>
@@ -306,7 +306,7 @@ export default async function ManufacturerProfile({
href={e.linkedin_url} href={e.linkedin_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[11px] text-[#E67E22] hover:underline" className="text-[11px] text-[#3b82f6] hover:underline"
> >
LinkedIn LinkedIn
</a> </a>
@@ -328,14 +328,14 @@ export default async function ManufacturerProfile({
)} )}
{company.phone && ( {company.phone && (
<div className="mt-1"> <div className="mt-1">
<a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#E67E22] hover:underline"> <a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#3b82f6] hover:underline">
{company.phone} {company.phone}
</a> </a>
</div> </div>
)} )}
{company.contact_email && ( {company.contact_email && (
<div> <div>
<a href={`mailto:${company.contact_email}`} className="text-[#E67E22] hover:underline"> <a href={`mailto:${company.contact_email}`} className="text-[#3b82f6] hover:underline">
{company.contact_email} {company.contact_email}
</a> </a>
</div> </div>
@@ -401,7 +401,7 @@ export default async function ManufacturerProfile({
<section className="mb-10"> <section className="mb-10">
<header className="flex items-baseline justify-between mb-3"> <header className="flex items-baseline justify-between mb-3">
<h2 className="text-lg font-display font-bold text-[#e0e0e0]">Recent coverage</h2> <h2 className="text-lg font-display font-bold text-[#e0e0e0]">Recent coverage</h2>
<Link href={`/news?search=${encodeURIComponent(company.company_name)}`} className="text-xs text-[#E67E22] hover:underline"> <Link href={`/news?search=${encodeURIComponent(company.company_name)}`} className="text-xs text-[#3b82f6] hover:underline">
All stories All stories
</Link> </Link>
</header> </header>
@@ -410,7 +410,7 @@ export default async function ManufacturerProfile({
<Link <Link
key={r.wp_slug} key={r.wp_slug}
href={`/news/${r.wp_slug}`} href={`/news/${r.wp_slug}`}
className="group bg-[#0d0d0d] border border-[#252525] rounded-md p-3 hover:border-[#E67E22] hover:shadow-sm transition-all flex gap-3" className="group bg-[#0d0d0d] border border-[#252525] rounded-md p-3 hover:border-[#3b82f6] hover:shadow-sm transition-all flex gap-3"
> >
{/* Image fallback cascade — Ryan rule: never show an empty {/* Image fallback cascade — Ryan rule: never show an empty
gray box on a coverage card. Falls through: gray box on a coverage card. Falls through:
@@ -423,7 +423,7 @@ export default async function ManufacturerProfile({
loading="lazy" loading="lazy"
/> />
<div className="min-w-0"> <div className="min-w-0">
<h3 className="text-xs font-semibold text-[#e0e0e0] leading-snug group-hover:text-[#E67E22] line-clamp-3"> <h3 className="text-xs font-semibold text-[#e0e0e0] leading-snug group-hover:text-[#3b82f6] line-clamp-3">
{r.title} {r.title}
</h3> </h3>
{r.wp_published_at && ( {r.wp_published_at && (
@@ -447,7 +447,7 @@ export default async function ManufacturerProfile({
{p.slug ? ( {p.slug ? (
<Link <Link
href={`/news/${p.slug}`} href={`/news/${p.slug}`}
className="text-[#E67E22] hover:underline" className="text-[#3b82f6] hover:underline"
> >
{p.title} {p.title}
</Link> </Link>

View File

@@ -102,7 +102,7 @@ export default async function ManufacturersIndex() {
</p> </p>
<p className="mt-2"> <p className="mt-2">
Are you a manufacturer and want to update your listing?{" "} Are you a manufacturer and want to update your listing?{" "}
<a href="mailto:editors@avbeat.com?subject=Manufacturer%20Directory%20Update" className="text-[#E67E22] hover:underline"> <a href="mailto:editors@avbeat.com?subject=Manufacturer%20Directory%20Update" className="text-[#3b82f6] hover:underline">
Contact our editors Contact our editors
</a> </a>
. .

View File

@@ -62,7 +62,7 @@ export default async function NabHub() {
const { data: ads } = await adv const { data: ads } = await adv
.from("active_advertisers_by_property") .from("active_advertisers_by_property")
.select("company_name_lower") .select("company_name_lower")
.eq("property", "broadcastbeat"); .eq("property", "avbeat");
sponsors = new Set((ads || []).map((r: any) => String(r.company_name_lower))); sponsors = new Set((ads || []).map((r: any) => String(r.company_name_lower)));
} catch { /* tolerate */ } } catch { /* tolerate */ }
@@ -153,7 +153,7 @@ export default async function NabHub() {
<h2 className="text-xl font-display font-bold text-[#e0e0e0]"> <h2 className="text-xl font-display font-bold text-[#e0e0e0]">
Latest from the show floor Latest from the show floor
</h2> </h2>
<Link href="/news?search=NAB+2026" className="text-xs text-[#E67E22] hover:underline"> <Link href="/news?search=NAB+2026" className="text-xs text-[#3b82f6] hover:underline">
See all NAB coverage See all NAB coverage
</Link> </Link>
</header> </header>
@@ -162,7 +162,7 @@ export default async function NabHub() {
<Link <Link
key={a.wp_slug} key={a.wp_slug}
href={`/news/${a.wp_slug}`} href={`/news/${a.wp_slug}`}
className="group bg-[#0d0d0d] border border-[#252525] rounded-lg overflow-hidden hover:border-[#E67E22] hover:shadow-sm transition-all" className="group bg-[#0d0d0d] border border-[#252525] rounded-lg overflow-hidden hover:border-[#3b82f6] hover:shadow-sm transition-all"
> >
{a.featured_image && ( {a.featured_image && (
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
@@ -174,7 +174,7 @@ export default async function NabHub() {
/> />
)} )}
<div className="p-3"> <div className="p-3">
<h3 className="font-semibold text-[#e0e0e0] text-sm leading-snug group-hover:text-[#E67E22] line-clamp-3 mb-1"> <h3 className="font-semibold text-[#e0e0e0] text-sm leading-snug group-hover:text-[#3b82f6] line-clamp-3 mb-1">
{a.title} {a.title}
</h3> </h3>
{a.wp_published_at && ( {a.wp_published_at && (
@@ -195,7 +195,7 @@ export default async function NabHub() {
<h2 className="text-xl font-display font-bold text-[#e0e0e0]"> <h2 className="text-xl font-display font-bold text-[#e0e0e0]">
All 2026 NAB Show exhibitors All 2026 NAB Show exhibitors
</h2> </h2>
<Link href="/manufacturers" className="text-xs text-[#E67E22] hover:underline"> <Link href="/manufacturers" className="text-xs text-[#3b82f6] hover:underline">
Full directory Full directory
</Link> </Link>
</header> </header>
@@ -205,7 +205,7 @@ export default async function NabHub() {
key={e.slug} key={e.slug}
href={`/manufacturers/${e.slug}`} href={`/manufacturers/${e.slug}`}
data-company-slug={e.slug} data-company-slug={e.slug}
className="bg-[#0d0d0d] border border-[#252525] rounded-md p-2.5 hover:border-[#E67E22] transition-all flex items-center gap-2.5" className="bg-[#0d0d0d] border border-[#252525] rounded-md p-2.5 hover:border-[#3b82f6] transition-all flex items-center gap-2.5"
> >
{e.logo_url ? ( {e.logo_url ? (
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
@@ -222,7 +222,7 @@ export default async function NabHub() {
{otherRows.length > 200 && ( {otherRows.length > 200 && (
<p className="text-center text-xs text-[#888] mt-4"> <p className="text-center text-xs text-[#888] mt-4">
Showing 200 of {otherRows.length.toLocaleString()} exhibitors.{" "} Showing 200 of {otherRows.length.toLocaleString()} exhibitors.{" "}
<Link href="/manufacturers" className="text-[#E67E22] hover:underline">Browse the full directory </Link> <Link href="/manufacturers" className="text-[#3b82f6] hover:underline">Browse the full directory </Link>
</p> </p>
)} )}
</section> </section>

View File

@@ -175,7 +175,7 @@ export default function NewsPage({
placeholder="Search articles, topics, authors…" placeholder="Search articles, topics, authors…"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} 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-[#E67E22] 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-[#3b82f6] transition-colors"
/> />
{search && ( {search && (
<button <button
@@ -196,7 +196,7 @@ export default function NewsPage({
type="date" type="date"
value={dateFrom} value={dateFrom}
onChange={(e) => setDateFrom(e.target.value)} 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-[#E67E22] 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-[#3b82f6] transition-colors [color-scheme:dark]"
title="From date" title="From date"
/> />
<span className="text-[#444] text-xs"></span> <span className="text-[#444] text-xs"></span>
@@ -204,7 +204,7 @@ export default function NewsPage({
type="date" type="date"
value={dateTo} value={dateTo}
onChange={(e) => setDateTo(e.target.value)} 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-[#E67E22] 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-[#3b82f6] transition-colors [color-scheme:dark]"
title="To date" title="To date"
/> />
</div> </div>
@@ -214,7 +214,7 @@ export default function NewsPage({
<select <select
value={sortBy} value={sortBy}
onChange={(e) => setSortBy(e.target.value)} 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-[#E67E22] 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-[#3b82f6] transition-colors cursor-pointer"
> >
{SORT_OPTIONS.map((opt) => ( {SORT_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option> <option key={opt.value} value={opt.value}>{opt.label}</option>
@@ -236,8 +236,8 @@ export default function NewsPage({
onClick={() => setActiveCategory(cat)} onClick={() => setActiveCategory(cat)}
className={`px-2.5 py-1 text-[11px] font-body rounded-sm border transition-colors ${ className={`px-2.5 py-1 text-[11px] font-body rounded-sm border transition-colors ${
activeCategory === cat activeCategory === cat
? "bg-[#E67E22] border-[#E67E22] text-white" ? "bg-[#3b82f6] border-[#3b82f6] text-white"
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#E67E22] hover:text-[#E67E22]" : "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]"
}`} }`}
> >
{cat} {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" /> <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> </svg>
<p className="text-[#555] font-body text-sm">No articles match your filters.</p> <p className="text-[#555] font-body text-sm">No articles match your filters.</p>
<button onClick={clearFilters} className="mt-3 text-[#E67E22] text-xs font-body hover:underline">Clear filters</button> <button onClick={clearFilters} className="mt-3 text-[#3b82f6] text-xs font-body hover:underline">Clear filters</button>
</div> </div>
) : ( ) : (
<div className="space-y-0"> <div className="space-y-0">
@@ -297,9 +297,9 @@ export default function NewsPage({
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1.5"> <div className="flex items-center gap-2 mb-1.5">
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider">{article.category || "NEWS"}</span> <span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">{article.category || "NEWS"}</span>
</div> </div>
<h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#E67E22] transition-colors line-clamp-2"> <h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#3b82f6] transition-colors line-clamp-2">
{article.title || "Untitled"} {article.title || "Untitled"}
</h2> </h2>
<p className="text-[#777] font-body text-sm leading-relaxed line-clamp-2">{article.excerpt || ""}</p> <p className="text-[#777] font-body text-sm leading-relaxed line-clamp-2">{article.excerpt || ""}</p>
@@ -326,7 +326,7 @@ export default function NewsPage({
{!hideAiSuggestions && <AISuggestedArticles variant="full" />} {!hideAiSuggestions && <AISuggestedArticles variant="full" />}
<div className="bg-[#111] border border-[#222] p-4"> <div className="bg-[#111] border border-[#222] p-4">
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-3 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-3 pb-2 border-b border-[#222]">
Popular Tags Popular Tags
</h3> </h3>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
@@ -336,8 +336,8 @@ export default function NewsPage({
onClick={() => setActiveCategory(tag)} onClick={() => setActiveCategory(tag)}
className={`px-2 py-1 border text-xs font-body rounded-sm transition-colors ${ className={`px-2 py-1 border text-xs font-body rounded-sm transition-colors ${
activeCategory === tag activeCategory === tag
? "bg-[#E67E22] border-[#E67E22] text-white" ? "bg-[#3b82f6] border-[#3b82f6] text-white"
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#E67E22] hover:text-[#E67E22]" : "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]"
}`} }`}
> >
{tag} {tag}
@@ -346,7 +346,7 @@ export default function NewsPage({
</div> </div>
</div> </div>
<div className="bg-[#0d1520] border border-[#1e3a5f] p-4"> <div className="bg-[#0d1520] border border-[#1e3a5f] p-4">
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-2">Newsletter</h3> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-2">Newsletter</h3>
<p className="text-[#777] text-xs font-body mb-3">Get broadcast news delivered to your inbox every week.</p> <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> <Link href="/home-page#newsletter" className="btn-subscribe text-xs py-2 px-4 inline-block">Subscribe Free</Link>
</div> </div>

View File

@@ -278,11 +278,11 @@ export default function NewsArticleDetailClient({
{/* Breadcrumb */} {/* Breadcrumb */}
<div className="bg-[#0d0d0d] border-b border-[#1a1a1a]"> <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]"> <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-[#E67E22] transition-colors"> <Link href="/home-page" className="hover:text-[#3b82f6] transition-colors">
Home Home
</Link> </Link>
<span>/</span> <span>/</span>
<Link href="/news" className="hover:text-[#E67E22] transition-colors"> <Link href="/news" className="hover:text-[#3b82f6] transition-colors">
News News
</Link> </Link>
<span>/</span> <span>/</span>
@@ -300,7 +300,7 @@ export default function NewsArticleDetailClient({
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<Link <Link
href="/news" href="/news"
className="text-xs font-bold uppercase tracking-widest text-[#E67E22] hover:underline"> className="text-xs font-bold uppercase tracking-widest text-[#3b82f6] hover:underline">
{article.category} {article.category}
</Link> </Link>
<span className="text-xs text-[#777]">{article.readTime}</span> <span className="text-xs text-[#777]">{article.readTime}</span>
@@ -329,7 +329,7 @@ export default function NewsArticleDetailClient({
<div> <div>
<Link <Link
href={`/authors/${article.authorSlug}`} href={`/authors/${article.authorSlug}`}
className="font-heading font-bold text-sm hover:text-[#E67E22] transition-colors text-[#e0e0e0]"> className="font-heading font-bold text-sm hover:text-[#3b82f6] transition-colors text-[#e0e0e0]">
{article.author} {article.author}
</Link> </Link>
<p className="font-body text-xs text-[#777]">{article.authorTitle}</p> <p className="font-body text-xs text-[#777]">{article.authorTitle}</p>
@@ -343,7 +343,7 @@ export default function NewsArticleDetailClient({
onClick={() => setEmailShareOpen(true)} onClick={() => setEmailShareOpen(true)}
aria-label="Share via email" aria-label="Share via email"
title="Email" title="Email"
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]"> 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]">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <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" /> <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" /> <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 */} {/* X / Twitter */}
<a <a
href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=AVBeat`} href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=BroadcastBeat`}
target="_blank" rel="noopener noreferrer" target="_blank" rel="noopener noreferrer"
aria-label="Share on X" aria-label="Share on X"
title="X (Twitter)" title="X (Twitter)"
@@ -418,7 +418,7 @@ export default function NewsArticleDetailClient({
}} }}
aria-label="Copy article link" aria-label="Copy article link"
title="Copy link" title="Copy link"
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]"> 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]">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <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="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"/> <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} onClick={handleSaveToReadingList}
disabled={savingState !== "idle"} disabled={savingState !== "idle"}
aria-label={isSaved ? "Remove from reading list" : "Save to reading list"} aria-label={isSaved ? "Remove from reading list" : "Save to reading list"}
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]"> 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]">
{savingState === "saving" ?"Saving..." {savingState === "saving" ?"Saving..."
: savingState === "removing" ?"Removing..." : savingState === "removing" ?"Removing..."
: isSaved : isSaved
@@ -469,7 +469,7 @@ export default function NewsArticleDetailClient({
{article.tags.map((tag) => ( {article.tags.map((tag) => (
<span <span
key={tag} key={tag}
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"> 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">
{tag} {tag}
</span> </span>
))} ))}
@@ -483,7 +483,7 @@ export default function NewsArticleDetailClient({
<p className="font-body text-sm text-[#777]">Found this article useful?</p> <p className="font-body text-sm text-[#777]">Found this article useful?</p>
<button <button
onClick={() => setEmailShareOpen(true)} onClick={() => setEmailShareOpen(true)}
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]"> 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]">
<svg <svg
width="13" width="13"
height="13" height="13"
@@ -504,7 +504,7 @@ export default function NewsArticleDetailClient({
<div className="pt-6 border-t border-[#222]"> <div className="pt-6 border-t border-[#222]">
<Link <Link
href="/news" href="/news"
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]"> 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]">
<svg <svg
width="14" width="14"
height="14" height="14"
@@ -534,7 +534,7 @@ export default function NewsArticleDetailClient({
{/* Related Articles Sidebar */} {/* Related Articles Sidebar */}
<div className="bg-[#111] border border-[#222] p-5"> <div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
Related Articles Related Articles
</h3> </h3>
<div className="space-y-4"> <div className="space-y-4">
@@ -553,10 +553,10 @@ export default function NewsArticleDetailClient({
/> />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[#E67E22] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5"> <p className="text-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5">
{related.category} {related.category}
</p> </p>
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#E67E22] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug">
{related.title} {related.title}
</p> </p>
</div> </div>
@@ -568,19 +568,19 @@ export default function NewsArticleDetailClient({
{/* Related Forum Posts — same widget as /articles/[slug] */} {/* Related Forum Posts — same widget as /articles/[slug] */}
{relatedForumThreads.length > 0 && ( {relatedForumThreads.length > 0 && (
<div className="bg-[#111] border border-[#222] p-5"> <div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
Related Forum Posts Related Forum Posts
</h3> </h3>
<ul className="space-y-3.5"> <ul className="space-y-3.5">
{relatedForumThreads.map((t) => ( {relatedForumThreads.map((t) => (
<li key={t.id}> <li key={t.id}>
<Link href={`/forum/thread/${t.id}`} className="group block"> <Link href={`/forum/thread/${t.id}`} className="group block">
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#E67E22] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug">
{t.title} {t.title}
</p> </p>
<p className="flex items-center gap-2 mt-1 text-[10px] text-[#666]"> <p className="flex items-center gap-2 mt-1 text-[10px] text-[#666]">
{t.category_name && ( {t.category_name && (
<span className="text-[#E67E22] font-body font-bold uppercase tracking-wider"> <span className="text-[#3b82f6] font-body font-bold uppercase tracking-wider">
{t.category_name} {t.category_name}
</span> </span>
)} )}
@@ -594,7 +594,7 @@ export default function NewsArticleDetailClient({
</ul> </ul>
<Link <Link
href="/forum" href="/forum"
className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#E67E22] hover:underline font-body font-semibold uppercase tracking-wider" className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#3b82f6] hover:underline font-body font-semibold uppercase tracking-wider"
> >
Browse all forum threads Browse all forum threads
</Link> </Link>
@@ -639,7 +639,7 @@ export default function NewsArticleDetailClient({
onClick={prevCarousel} onClick={prevCarousel}
disabled={carouselIndex === 0} disabled={carouselIndex === 0}
aria-label="Previous articles" aria-label="Previous articles"
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]"> 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]">
<svg <svg
width="14" width="14"
height="14" height="14"
@@ -655,7 +655,7 @@ export default function NewsArticleDetailClient({
onClick={nextCarousel} onClick={nextCarousel}
disabled={carouselIndex >= maxIndex} disabled={carouselIndex >= maxIndex}
aria-label="Next articles" aria-label="Next articles"
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]"> 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]">
<svg <svg
width="14" width="14"
height="14" height="14"
@@ -694,11 +694,11 @@ export default function NewsArticleDetailClient({
/> />
</div> </div>
<div className="flex items-center gap-2 mb-1.5"> <div className="flex items-center gap-2 mb-1.5">
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider"> <span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">
{related.category} {related.category}
</span> </span>
</div> </div>
<h3 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug group-hover:text-[#E67E22] transition-colors line-clamp-2 mb-1.5"> <h3 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug group-hover:text-[#3b82f6] transition-colors line-clamp-2 mb-1.5">
{related.title} {related.title}
</h3> </h3>
<p className="text-[#666] font-body text-xs line-clamp-2 leading-relaxed"> <p className="text-[#666] font-body text-xs line-clamp-2 leading-relaxed">
@@ -723,7 +723,7 @@ export default function NewsArticleDetailClient({
onClick={() => setCarouselIndex(i)} onClick={() => setCarouselIndex(i)}
aria-label={`Go to slide ${i + 1}`} aria-label={`Go to slide ${i + 1}`}
className={`w-1.5 h-1.5 rounded-full transition-colors focus:outline-none ${ className={`w-1.5 h-1.5 rounded-full transition-colors focus:outline-none ${
i === carouselIndex ? "bg-[#E67E22]" : "bg-[#333]" i === carouselIndex ? "bg-[#3b82f6]" : "bg-[#333]"
}`} }`}
/> />
))} ))}
@@ -756,7 +756,7 @@ export default function NewsArticleDetailClient({
setEmailStatus("idle"); setEmailStatus("idle");
}} }}
aria-label="Close share dialog" 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-[#E67E22]"> className="absolute top-4 right-4 text-[#555] hover:text-[#e0e0e0] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
<svg <svg
width="18" width="18"
height="18" height="18"
@@ -777,7 +777,7 @@ export default function NewsArticleDetailClient({
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="#E67E22" stroke="#3b82f6"
strokeWidth="2" strokeWidth="2"
aria-hidden="true"> aria-hidden="true">
<rect x="2" y="4" width="20" height="16" rx="2" /> <rect x="2" y="4" width="20" height="16" rx="2" />
@@ -823,7 +823,7 @@ export default function NewsArticleDetailClient({
<label <label
htmlFor="email-to" htmlFor="email-to"
className="block font-body text-xs text-[#888] uppercase tracking-wider mb-1.5"> className="block font-body text-xs text-[#888] uppercase tracking-wider mb-1.5">
Recipient Email <span className="text-[#E67E22]">*</span> Recipient Email <span className="text-[#3b82f6]">*</span>
</label> </label>
<input <input
id="email-to" id="email-to"
@@ -831,7 +831,7 @@ export default function NewsArticleDetailClient({
value={emailTo} value={emailTo}
onChange={(e) => setEmailTo(e.target.value)} onChange={(e) => setEmailTo(e.target.value)}
placeholder="colleague@example.com" placeholder="colleague@example.com"
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" 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"
/> />
</div> </div>
<div> <div>
@@ -846,7 +846,7 @@ export default function NewsArticleDetailClient({
onChange={(e) => setEmailNote(e.target.value)} onChange={(e) => setEmailNote(e.target.value)}
placeholder="Thought you might find this interesting..." placeholder="Thought you might find this interesting..."
rows={3} rows={3}
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" 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"
/> />
</div> </div>
@@ -890,7 +890,7 @@ export default function NewsArticleDetailClient({
<div <div
role="status" role="status"
aria-live="polite" aria-live="polite"
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"> 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">
{saveToast} {saveToast}
</div> </div>
)} )}

View File

@@ -63,7 +63,7 @@ function NewsletterCard({ newsletter }: { newsletter: Newsletter }) {
const blocks = Array.isArray(newsletter.article_blocks) const blocks = Array.isArray(newsletter.article_blocks)
? newsletter.article_blocks.filter((b) => b.title) ? newsletter.article_blocks.filter((b) => b.title)
: []; : [];
const accent = newsletter.accent_color || "#E67E22"; const accent = newsletter.accent_color || "#3b82f6";
const layoutLabel: Record<string, string> = { const layoutLabel: Record<string, string> = {
featured: "Weekly Digest", 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="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"> <span className="font-body text-[#888] text-xs leading-relaxed line-clamp-1">
{block.category && ( {block.category && (
<span className="text-[#E67E22] font-bold mr-1">{block.category}:</span> <span className="text-[#3b82f6] font-bold mr-1">{block.category}:</span>
)} )}
{block.title} {block.title}
</span> </span>
@@ -132,7 +132,7 @@ function NewsletterCard({ newsletter }: { newsletter: Newsletter }) {
<span className="font-body text-[#555] text-xs"> <span className="font-body text-[#555] text-xs">
{blocks.length} {blocks.length === 1 ? "story" : "stories"} {blocks.length} {blocks.length === 1 ? "story" : "stories"}
</span> </span>
<span className="font-body text-[#E67E22] text-xs font-medium"> <span className="font-body text-[#3b82f6] text-xs font-medium">
Issue #{newsletter.id.slice(0, 6).toUpperCase()} Issue #{newsletter.id.slice(0, 6).toUpperCase()}
</span> </span>
</div> </div>
@@ -197,9 +197,9 @@ function SignupCTA() {
{status === "success" ? ( {status === "success" ? (
<div className="text-center py-4" role="status" aria-live="polite"> <div className="text-center py-4" role="status" aria-live="polite">
<div className="w-10 h-10 rounded-full bg-[#E67E22]/20 flex items-center justify-center mx-auto mb-3"> <div className="w-10 h-10 rounded-full bg-[#3b82f6]/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"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M4 10l4 4 8-8" stroke="#E67E22" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M4 10l4 4 8-8" stroke="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<p className="font-heading text-[#e0e0e0] font-bold mb-1">You&apos;re subscribed!</p> <p className="font-heading text-[#e0e0e0] font-bold mb-1">You&apos;re subscribed!</p>
@@ -228,7 +228,7 @@ function SignupCTA() {
/> />
<span <span
aria-hidden="true" aria-hidden="true"
className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#E67E22] border-[#E67E22]" : "border-[#444]"}`}> className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#3b82f6] border-[#3b82f6]" : "border-[#444]"}`}>
{checked && ( {checked && (
<svg viewBox="0 0 12 12" fill="none" className="w-full h-full" aria-hidden="true"> <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" /> <path d="M2 6l3 3 5-5" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
@@ -262,7 +262,7 @@ function SignupCTA() {
<button <button
type="submit" type="submit"
disabled={status === "loading"} 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-[#E67E22] ${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-[#3b82f6] ${status === "loading" ? "opacity-70 cursor-not-allowed" : ""}`}>
{status === "loading" ? "Subscribing..." : "Subscribe Free"} {status === "loading" ? "Subscribing..." : "Subscribe Free"}
</button> </button>
</div> </div>
@@ -275,7 +275,7 @@ function SignupCTA() {
<p className="font-body text-[#555] text-xs mt-3 text-center"> <p className="font-body text-[#555] text-xs mt-3 text-center">
No spam. Unsubscribe anytime.{" "} No spam. Unsubscribe anytime.{" "}
<Link href="/privacy" className="text-[#E67E22] hover:underline"> <Link href="/privacy" className="text-[#3b82f6] hover:underline">
Privacy Policy Privacy Policy
</Link> </Link>
</p> </p>
@@ -419,9 +419,9 @@ export default function NewsletterArchivePage() {
<button <button
key={topic} key={topic}
onClick={() => setSelectedTopic(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-[#E67E22] ${ 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] ${
selectedTopic === topic selectedTopic === topic
? "bg-[#E67E22] border-[#E67E22] text-white" ? "bg-[#3b82f6] border-[#3b82f6] text-white"
: "bg-transparent border-[#333] text-[#888] hover:border-[#555] hover:text-[#ccc]" : "bg-transparent border-[#333] text-[#888] hover:border-[#555] hover:text-[#ccc]"
}`} }`}
aria-pressed={selectedTopic === topic}> aria-pressed={selectedTopic === topic}>
@@ -462,7 +462,7 @@ export default function NewsletterArchivePage() {
{hasActiveFilters && ( {hasActiveFilters && (
<button <button
onClick={handleClearFilters} onClick={handleClearFilters}
className="font-body text-xs text-[#E67E22] hover:text-[#E67E22] transition-colors underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]"> className="font-body text-xs text-[#3b82f6] hover:text-[#60a5fa] transition-colors underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]">
Clear filters Clear filters
</button> </button>
)} )}
@@ -498,7 +498,7 @@ export default function NewsletterArchivePage() {
<p className="font-body text-red-400 text-sm mb-3">{error}</p> <p className="font-body text-red-400 text-sm mb-3">{error}</p>
<button <button
onClick={fetchArchive} onClick={fetchArchive}
className="font-body text-xs text-[#E67E22] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]"> className="font-body text-xs text-[#3b82f6] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]">
Try again Try again
</button> </button>
</div> </div>
@@ -521,7 +521,7 @@ export default function NewsletterArchivePage() {
</p> </p>
<button <button
onClick={handleClearFilters} onClick={handleClearFilters}
className="font-body text-xs text-[#E67E22] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22]"> className="font-body text-xs text-[#3b82f6] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]">
Clear all filters Clear all filters
</button> </button>
</div> </div>
@@ -555,9 +555,9 @@ export default function NewsletterArchivePage() {
<li key={topic}> <li key={topic}>
<button <button
onClick={() => setSelectedTopic(topic)} 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-[#E67E22] ${ 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] ${
selectedTopic === topic selectedTopic === topic
? "text-[#E67E22] bg-[#E67E22]/10" ? "text-[#3b82f6] bg-[#3b82f6]/10"
: "text-[#888] hover:text-[#ccc] hover:bg-[#1a1a1a]" : "text-[#888] hover:text-[#ccc] hover:bg-[#1a1a1a]"
}`}> }`}>
{topic} {topic}
@@ -578,9 +578,9 @@ export default function NewsletterArchivePage() {
<li key={year}> <li key={year}>
<button <button
onClick={() => { setSelectedYear(String(year)); setSelectedMonth(""); }} 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-[#E67E22] ${ 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] ${
selectedYear === String(year) selectedYear === String(year)
? "text-[#E67E22] bg-[#E67E22]/10" ? "text-[#3b82f6] bg-[#3b82f6]/10"
: "text-[#888] hover:text-[#ccc] hover:bg-[#1a1a1a]" : "text-[#888] hover:text-[#ccc] hover:bg-[#1a1a1a]"
}`}> }`}>
{year} {year}

View File

@@ -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]">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> <li><strong className="text-[#ccc]">Unsubscribe:</strong> Opt out of marketing emails at any time via the unsubscribe link</li>
</ul> </ul>
<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> <p className="mt-3">To exercise any of these rights, contact us at <a href="mailto:privacy@avbeat.com" className="text-[#3b82f6] underline hover:text-blue-400 transition-colors">privacy@avbeat.com</a>. We will respond within 30 days.</p>
</section> </section>
<section aria-labelledby="section-6"> <section aria-labelledby="section-6">
@@ -127,7 +127,7 @@ export default function PrivacyPage() {
<address className="not-italic mt-3 space-y-1 text-[#999]"> <address className="not-italic mt-3 space-y-1 text-[#999]">
<p><strong className="text-[#ccc]">Relevant Media Properties, LLC</strong></p> <p><strong className="text-[#ccc]">Relevant Media Properties, LLC</strong></p>
<p>Privacy Officer</p> <p>Privacy Officer</p>
<p>Email: <a href="mailto:privacy@avbeat.com" className="text-[#E67E22] underline hover:text-blue-400 transition-colors">privacy@avbeat.com</a></p> <p>Email: <a href="mailto:privacy@avbeat.com" className="text-[#3b82f6] underline hover:text-blue-400 transition-colors">privacy@avbeat.com</a></p>
</address> </address>
</section> </section>

View File

@@ -82,7 +82,7 @@ export default function RegisterPage() {
<div className="min-h-screen bg-[#111111]"> <div className="min-h-screen bg-[#111111]">
<Header /> <Header />
<div className="flex items-center justify-center py-24"> <div className="flex items-center justify-center py-24">
<div className="w-6 h-6 border-2 border-[#E67E22] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
</div> </div>
<Footer /> <Footer />
</div> </div>
@@ -97,7 +97,7 @@ export default function RegisterPage() {
<div className="bg-[#0d0d0d] border-b border-[#1e1e1e]"> <div className="bg-[#0d0d0d] border-b border-[#1e1e1e]">
<div className="max-w-container mx-auto px-4 py-2.5"> <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]"> <nav aria-label="Breadcrumb" className="flex items-center gap-2 text-xs font-body text-[#555]">
<Link href="/home-page" className="hover:text-[#E67E22] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#3b82f6] transition-colors">Home</Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<span className="text-[#888]">Create Account</span> <span className="text-[#888]">Create Account</span>
</nav> </nav>
@@ -142,7 +142,7 @@ export default function RegisterPage() {
onChange={(e) => setFullName(e.target.value)} onChange={(e) => setFullName(e.target.value)}
placeholder="Jane Smith" placeholder="Jane Smith"
disabled={loading} 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-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] 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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
/> />
</div> </div>
@@ -159,7 +159,7 @@ export default function RegisterPage() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com" placeholder="you@example.com"
disabled={loading} 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-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] 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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
/> />
</div> </div>
@@ -176,7 +176,7 @@ export default function RegisterPage() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="Min. 6 characters" placeholder="Min. 6 characters"
disabled={loading} 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-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] 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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
/> />
</div> </div>
@@ -193,7 +193,7 @@ export default function RegisterPage() {
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Re-enter password" placeholder="Re-enter password"
disabled={loading} 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-[#E67E22] focus-visible:ring-1 focus-visible:ring-[#E67E22] 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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50"
/> />
</div> </div>
@@ -201,7 +201,7 @@ export default function RegisterPage() {
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
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"> 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">
{loading ? ( {loading ? (
<> <>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> <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 */} {/* Login Link */}
<Link <Link
href="/login" href="/login"
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"> 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">
Sign In Sign In
</Link> </Link>
</div> </div>

View File

@@ -51,7 +51,7 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
{query && results.length === 0 && ( {query && results.length === 0 && (
<div className="py-16 text-center"> <div className="py-16 text-center">
<p className="text-[#555] font-body text-sm mb-3">No articles match your search.</p> <p className="text-[#555] font-body text-sm mb-3">No articles match your search.</p>
<Link href="/news" className="text-[#E67E22] text-xs font-body hover:underline"> <Link href="/news" className="text-[#3b82f6] text-xs font-body hover:underline">
Browse all news Browse all news
</Link> </Link>
</div> </div>
@@ -77,11 +77,11 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1.5"> <div className="flex items-center gap-2 mb-1.5">
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider"> <span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">
{article.category || "NEWS"} {article.category || "NEWS"}
</span> </span>
</div> </div>
<h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#E67E22] transition-colors line-clamp-2"> <h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#3b82f6] transition-colors line-clamp-2">
{article.title || "Untitled"} {article.title || "Untitled"}
</h2> </h2>
<p className="text-[#777] font-body text-sm leading-relaxed line-clamp-2"> <p className="text-[#777] font-body text-sm leading-relaxed line-clamp-2">

View File

@@ -170,7 +170,7 @@ export default async function EventCoveragePage({
/> />
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-8"> <div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-8">
<main> <main>
<Link href="/show-coverage" className="text-sm text-[var(--color-text-info,#E67E22)] hover:underline"> <Link href="/show-coverage" className="text-sm text-[var(--color-text-info,#60a5fa)] hover:underline">
All events All events
</Link> </Link>
@@ -182,7 +182,7 @@ export default async function EventCoveragePage({
</span> </span>
)} )}
{!isLive && !isPast && ( {!isLive && !isPast && (
<span className="inline-flex items-center gap-1.5 text-[11px] font-mono uppercase tracking-wider text-[var(--color-text-info,#E67E22)]"> <span className="inline-flex items-center gap-1.5 text-[11px] font-mono uppercase tracking-wider text-[var(--color-text-info,#60a5fa)]">
T-{daysUntil}d T-{daysUntil}d
</span> </span>
)} )}
@@ -203,7 +203,7 @@ export default async function EventCoveragePage({
{[event.venue, event.city, event.country].filter(Boolean).join(" · ")} {[event.venue, event.city, event.country].filter(Boolean).join(" · ")}
</div> </div>
{event.url && ( {event.url && (
<div className="text-xs font-mono text-[var(--color-text-info,#E67E22)] mt-2"> <div className="text-xs font-mono text-[var(--color-text-info,#60a5fa)] mt-2">
<a href={event.url} target="_blank" rel="noopener noreferrer"> <a href={event.url} target="_blank" rel="noopener noreferrer">
{new URL(event.url).hostname.replace(/^www\./, "")} {new URL(event.url).hostname.replace(/^www\./, "")}
</a> </a>

View File

@@ -123,7 +123,7 @@ function EventCard({ e }: { e: Row }) {
<li> <li>
<Link <Link
href={`/show-coverage/${e.slug}`} href={`/show-coverage/${e.slug}`}
className="block p-5 rounded border border-[#252525] hover:border-[var(--color-text-info,#E67E22)] bg-[#0b0f17] transition-colors" className="block p-5 rounded border border-[#252525] hover:border-[var(--color-text-info,#60a5fa)] bg-[#0b0f17] transition-colors"
> >
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<h3 className="font-serif text-xl font-semibold">{e.name}</h3> <h3 className="font-serif text-xl font-semibold">{e.name}</h3>
@@ -137,7 +137,7 @@ function EventCard({ e }: { e: Row }) {
{e.url && ( {e.url && (
<> <>
{" · "} {" · "}
<span className="text-[var(--color-text-info,#E67E22)]">{new URL(e.url).hostname.replace(/^www\./, "")}</span> <span className="text-[var(--color-text-info,#60a5fa)]">{new URL(e.url).hostname.replace(/^www\./, "")}</span>
</> </>
)} )}
</div> </div>

View File

@@ -87,7 +87,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
<span className="section-label">Technology</span> <span className="section-label">Technology</span>
<div className="flex-1 h-px bg-[#2a2a2a]" /> <div className="flex-1 h-px bg-[#2a2a2a]" />
{filter && ( {filter && (
<Link href="/technology" className="text-[#666] hover:text-[#E67E22] font-body text-[11px] underline"> <Link href="/technology" className="text-[#666] hover:text-[#3b82f6] font-body text-[11px] underline">
clear filter clear filter
</Link> </Link>
)} )}
@@ -101,7 +101,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
href="/technology" href="/technology"
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all ${ className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all ${
!filterSlug !filterSlug
? "bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/50" ? "bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50"
: "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc]" : "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc]"
}`}> }`}>
All All
@@ -114,7 +114,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
href={`/technology?category=${slug}`} href={`/technology?category=${slug}`}
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all ${ className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all ${
isActive isActive
? "bg-[#E67E22]/20 text-[#E67E22] border border-[#E67E22]/50" ? "bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50"
: "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc]" : "bg-[#111] text-[#888] border border-[#2a2a2a] hover:text-[#ccc]"
}`}> }`}>
{def.title} {def.title}
@@ -133,7 +133,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
No articles found{filter ? ` for "${filter.title}"` : ""}. No articles found{filter ? ` for "${filter.title}"` : ""}.
</p> </p>
{filter && ( {filter && (
<Link href="/technology" className="inline-block mt-3 text-[#E67E22] hover:underline font-body text-sm"> <Link href="/technology" className="inline-block mt-3 text-[#3b82f6] hover:underline font-body text-sm">
Back to all technology coverage Back to all technology coverage
</Link> </Link>
)} )}
@@ -145,7 +145,7 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
<div className="mb-10"> <div className="mb-10">
<Link <Link
href={`/articles/${articles[0].slug}`} href={`/articles/${articles[0].slug}`}
className="flex flex-col md:flex-row gap-6 bg-[#111] border border-[#222] overflow-hidden group hover:border-[#E67E22] transition-colors p-5"> className="flex flex-col md:flex-row gap-6 bg-[#111] border border-[#222] overflow-hidden group hover:border-[#3b82f6] transition-colors p-5">
<div className="flex-shrink-0 relative w-full md:w-[320px] h-[200px] md:h-[200px] overflow-hidden rounded-sm"> <div className="flex-shrink-0 relative w-full md:w-[320px] h-[200px] md:h-[200px] overflow-hidden rounded-sm">
<AppImage <AppImage
src={articles[0].image} src={articles[0].image}
@@ -157,13 +157,13 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="text-[#E67E22] font-body text-[10px] font-bold uppercase tracking-wider">{articles[0].category}</span> <span className="text-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">{articles[0].category}</span>
<span className="text-[#444] text-[10px]">·</span> <span className="text-[#444] text-[10px]">·</span>
<span className="text-[#555] font-body text-[11px]">{articles[0].date}</span> <span className="text-[#555] font-body text-[11px]">{articles[0].date}</span>
<span className="text-[#444] text-[10px]">·</span> <span className="text-[#444] text-[10px]">·</span>
<span className="text-[#555] font-body text-[11px]">{articles[0].readTime}</span> <span className="text-[#555] font-body text-[11px]">{articles[0].readTime}</span>
</div> </div>
<h2 className="font-heading text-[#e0e0e0] text-xl font-bold leading-tight mb-3 group-hover:text-[#E67E22] transition-colors"> <h2 className="font-heading text-[#e0e0e0] text-xl font-bold leading-tight mb-3 group-hover:text-[#3b82f6] transition-colors">
{articles[0].title} {articles[0].title}
</h2> </h2>
<p className="text-[#777] font-body text-sm leading-relaxed mb-4">{articles[0].excerpt}</p> <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 <Link
key={article.slug} key={article.slug}
href={`/articles/${article.slug}`} href={`/articles/${article.slug}`}
className="flex gap-4 bg-[#111] border border-[#222] p-4 group hover:border-[#E67E22] transition-colors"> className="flex gap-4 bg-[#111] border border-[#222] p-4 group hover:border-[#3b82f6] transition-colors">
<div className="flex-shrink-0 w-[120px] h-[80px] relative overflow-hidden rounded-sm"> <div className="flex-shrink-0 w-[120px] h-[80px] relative overflow-hidden rounded-sm">
<AppImage <AppImage
src={article.image} src={article.image}
@@ -197,11 +197,11 @@ export default async function TechnologyPage({ searchParams }: PageProps) {
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<span className="text-[#E67E22] font-body text-[9px] font-bold uppercase tracking-wider">{article.category}</span> <span className="text-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider">{article.category}</span>
<span className="text-[#444] text-[9px]">·</span> <span className="text-[#444] text-[9px]">·</span>
<span className="text-[#555] font-body text-[10px]">{article.readTime}</span> <span className="text-[#555] font-body text-[10px]">{article.readTime}</span>
</div> </div>
<h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-1 group-hover:text-[#E67E22] transition-colors line-clamp-2"> <h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-1 group-hover:text-[#3b82f6] transition-colors line-clamp-2">
{article.title} {article.title}
</h2> </h2>
{article.category !== "Featured" && ( {article.category !== "Featured" && (

View File

@@ -46,7 +46,7 @@ export default function TermsPage() {
</section> </section>
<section> <section>
<h2 className="font-heading text-[#e0e0f0] text-xl font-bold mb-3">7. Contact</h2> <h2 className="font-heading text-[#e0e0f0] text-xl font-bold mb-3">7. Contact</h2>
<p>Questions about these Terms? Contact us at <a href="mailto:legal@avbeat.com" className="text-[#E67E22] hover:underline">legal@avbeat.com</a>.</p> <p>Questions about these Terms? Contact us at <a href="mailto:legal@avbeat.com" className="text-[#3b82f6] hover:underline">legal@avbeat.com</a>.</p>
</section> </section>
</div> </div>
</main> </main>

View File

@@ -0,0 +1,180 @@
import type { Metadata } from "next";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export const metadata: Metadata = {
title: "From the Vault — On-Camera Archive · AV Beat",
description:
"Highlights from a decade of AV Beat on-camera coverage — booth walkthroughs, product demos, and exec interviews from NAB Show, IBC, SVG, and the trade circuit (20142021). Today our coverage runs through written reporting; this page is a permanent archive of the video era.",
alternates: { canonical: "/video-archive" },
openGraph: {
title: "From the Vault — On-Camera Archive · AV Beat",
description:
"A decade of on-camera coverage from the NAB Show floor, IBC, SVG, and beyond — archived, not deprecated.",
type: "website",
url: "https://avbeat.com/video-archive",
},
};
// Server-side fetch with revalidation so we don't hammer YouTube and the page
// stays static-fast. RSS only returns ~15 latest videos but that's fine for
// an archive landing — "See full library" link takes them to YouTube.
export const revalidate = 21600; // 6 hours
type Video = {
videoId: string;
title: string;
description: string;
publishedYear: number;
thumbnail: string;
};
async function fetchArchive(): Promise<Video[]> {
try {
const r = await fetch(
"https://www.youtube.com/feeds/videos.xml?channel_id=UC_eyQjdiEtNjNE67KDWzQug",
{ next: { revalidate: 21600 }, headers: { "User-Agent": "Mozilla/5.0 avbeat.com archive" } },
);
if (!r.ok) return [];
const xml = await r.text();
// Lightweight XML parsing — every <entry>…</entry> block is one video.
const entries = xml.split("<entry>").slice(1);
return entries.map((block) => {
const get = (re: RegExp) => (block.match(re)?.[1] ?? "").trim();
const videoId = get(/<yt:videoId>([^<]+)<\/yt:videoId>/);
const title = get(/<title>([^<]+)<\/title>/);
const description = get(/<media:description>([\s\S]*?)<\/media:description>/);
const published = get(/<published>([^<]+)<\/published>/);
const publishedYear = published ? new Date(published).getFullYear() : 2018;
return {
videoId,
title,
description: description.slice(0, 200),
publishedYear,
thumbnail: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`,
};
}).filter(v => v.videoId);
} catch {
return [];
}
}
export default async function VideoArchive() {
const videos = await fetchArchive();
return (
<>
<Header />
<main className="bg-[var(--color-bg,#0a0a0c)] text-[var(--color-text,#e7e7ea)] min-h-screen">
{/* Archive intro — frames the section explicitly as historical so users
don't read "old videos" as "stale publication". */}
<section className="border-b border-white/8 bg-gradient-to-b from-[#11111a] to-transparent">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="inline-flex items-center gap-2 bg-amber-500/10 border border-amber-500/30 text-amber-300 text-[10px] font-bold uppercase tracking-[0.2em] px-3 py-1 rounded mb-5">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400" /> From the Vault · 2014 2021
</div>
<h1 className="text-3xl md:text-5xl font-black text-white tracking-tight mb-4" style={{ fontFamily: "'Barlow Condensed', sans-serif", letterSpacing: '-0.01em' }}>
ON-CAMERA ARCHIVE
</h1>
<p className="text-white/65 text-base md:text-lg leading-relaxed max-w-3xl">
For seven years our team carried cameras through NAB Show, IBC, SVG, and dozens of regional production gatherings,
capturing booth walkthroughs, product unveils, and exec interviews on the show floor. Today our coverage is
all written reporting but the video archive lives on, preserved as a record of an industry that moves
faster than memory.
</p>
<p className="text-white/40 text-sm mt-4">
These clips are kept for reference. We&apos;re not producing new video at this time.
</p>
</div>
</section>
{/* Video grid */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
{videos.length === 0 ? (
<div className="bg-[#111118] border border-white/8 rounded-xl p-12 text-center">
<p className="text-white/40 text-sm">Archive feed is temporarily unavailable. Browse the full library directly on{" "}
<a href="https://youtube.com/@avbeat" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-info,#60a5fa)] hover:underline">YouTube</a>.
</p>
</div>
) : (
<>
<div className="flex items-center justify-between mb-6">
<h2 className="text-[11px] uppercase tracking-[0.2em] text-white/40 font-bold">Selected clips</h2>
<a
href="https://youtube.com/@avbeat"
target="_blank"
rel="noopener noreferrer"
className="text-xs text-white/50 hover:text-white transition uppercase tracking-wider"
>
See full library on YouTube
</a>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
{videos.map((v) => (
<a
key={v.videoId}
href={`https://www.youtube.com/watch?v=${v.videoId}`}
target="_blank"
rel="noopener noreferrer"
className="group bg-[#111118] border border-white/8 rounded-lg overflow-hidden hover:border-white/25 transition-all"
>
<div className="aspect-video bg-black relative overflow-hidden">
<img
src={v.thumbnail}
alt={v.title}
loading="lazy"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 opacity-90 group-hover:opacity-100"
/>
{/* Subtle aged-archive tint so the thumbnail signals "vault" */}
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent pointer-events-none" />
{/* Play badge */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="w-14 h-14 rounded-full bg-black/60 border border-white/30 flex items-center justify-center group-hover:bg-[var(--color-brand,#cc0000)]/80 transition">
<svg className="w-5 h-5 text-white ml-0.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
</div>
</div>
</div>
<div className="p-4">
<h3 className="text-white font-semibold text-sm leading-snug line-clamp-2 group-hover:text-[var(--color-text-info,#60a5fa)] transition">
{v.title}
</h3>
<p className="text-white/35 text-xs mt-2 uppercase tracking-wider">From the BB vault</p>
</div>
</a>
))}
</div>
</>
)}
</section>
{/* Why no recent videos — meta-explanation that turns "old" into "deliberate choice" */}
<section className="border-t border-white/8 bg-[#0d0d14]">
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h2 className="text-white font-bold text-xl mb-3" style={{ fontFamily: "'Barlow Condensed', sans-serif" }}>
Why we stopped shooting video
</h2>
<p className="text-white/55 text-sm leading-relaxed mb-3">
The video era bullpens of camera crews on the NAB floor, B-roll runs through manufacturer booths,
same-day edits uploaded from press rooms produced thousands of hours of footage. It also produced a
very specific kind of coverage: surface-level, sponsor-friendly, optimized for trade-show buzz.
</p>
<p className="text-white/55 text-sm leading-relaxed mb-3">
We made the call to step away from on-camera coverage and rebuild AV Beat as a written publication
deeper reporting, more original analysis, fewer canned booth tours. Most of our peers in the trade press
are doing the opposite. We think the readers (and the industry) are better served this way.
</p>
<p className="text-white/55 text-sm leading-relaxed">
The vault stays online so the work isn&apos;t lost. If you&apos;re looking for what&apos;s next from BB,
head to the{" "}
<Link href="/" className="text-[var(--color-text-info,#60a5fa)] hover:underline">homepage</Link>{" "}
or subscribe to the{" "}
<Link href="/newsletter" className="text-[var(--color-text-info,#60a5fa)] hover:underline">daily newsletter</Link>.
</p>
</div>
</section>
</main>
<Footer />
</>
);
}

View File

@@ -103,16 +103,16 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<svg className="w-4 h-4 text-[#E67E22]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#3b82f6]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.347.346A3.5 3.5 0 0114.5 20h-5a3.5 3.5 0 01-2.47-1.024l-.347-.346z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.347.346A3.5 3.5 0 0114.5 20h-5a3.5 3.5 0 01-2.47-1.024l-.347-.346z" />
</svg> </svg>
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest"> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest">
AI Picks For You AI Picks For You
</h3> </h3>
</div> </div>
<button <button
onClick={() => setShowTopicPicker(!showTopicPicker)} onClick={() => setShowTopicPicker(!showTopicPicker)}
className="text-[10px] font-body text-[#555] hover:text-[#E67E22] transition-colors flex items-center gap-1" className="text-[10px] font-body text-[#555] hover:text-[#3b82f6] transition-colors flex items-center gap-1"
> >
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
@@ -132,8 +132,8 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
onClick={() => toggleInterest(topic)} onClick={() => toggleInterest(topic)}
className={`px-2 py-0.5 text-[10px] font-body rounded-sm border transition-colors ${ className={`px-2 py-0.5 text-[10px] font-body rounded-sm border transition-colors ${
interests.includes(topic) interests.includes(topic)
? 'bg-[#E67E22] border-[#E67E22] text-white' ? 'bg-[#3b82f6] border-[#3b82f6] text-white'
: 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#E67E22] hover:text-[#E67E22]' : 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]'
}`} }`}
> >
{topic} {topic}
@@ -142,7 +142,7 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
</div> </div>
<button <button
onClick={handleApplyTopics} onClick={handleApplyTopics}
className="text-[10px] font-body bg-[#E67E22] text-white px-3 py-1 rounded-sm hover:bg-[#C8651B] transition-colors" className="text-[10px] font-body bg-[#3b82f6] text-white px-3 py-1 rounded-sm hover:bg-[#2563eb] transition-colors"
> >
Update Suggestions Update Suggestions
</button> </button>
@@ -180,10 +180,10 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
/> />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[#E67E22] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5"> <p className="text-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5">
{article.category} {article.category}
</p> </p>
<p className="font-heading text-[#e0e0e0] text-xs font-semibold leading-snug line-clamp-2 group-hover:text-[#E67E22] transition-colors"> <p className="font-heading text-[#e0e0e0] text-xs font-semibold leading-snug line-clamp-2 group-hover:text-[#3b82f6] transition-colors">
{article.title} {article.title}
</p> </p>
</div> </div>
@@ -203,7 +203,7 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<svg className="w-4 h-4 text-[#E67E22]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#3b82f6]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.347.346A3.5 3.5 0 0114.5 20h-5a3.5 3.5 0 01-2.47-1.024l-.347-.346z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.347.346A3.5 3.5 0 0114.5 20h-5a3.5 3.5 0 01-2.47-1.024l-.347-.346z" />
</svg> </svg>
<span className="section-label">AI Picks For You</span> <span className="section-label">AI Picks For You</span>
@@ -212,7 +212,7 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
</div> </div>
<button <button
onClick={() => setShowTopicPicker(!showTopicPicker)} onClick={() => setShowTopicPicker(!showTopicPicker)}
className="text-[11px] font-body text-[#555] hover:text-[#E67E22] transition-colors flex items-center gap-1 ml-4 shrink-0" className="text-[11px] font-body text-[#555] hover:text-[#3b82f6] transition-colors flex items-center gap-1 ml-4 shrink-0"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
@@ -232,8 +232,8 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
onClick={() => toggleInterest(topic)} onClick={() => toggleInterest(topic)}
className={`px-2.5 py-1 text-[11px] font-body rounded-sm border transition-colors ${ className={`px-2.5 py-1 text-[11px] font-body rounded-sm border transition-colors ${
interests.includes(topic) interests.includes(topic)
? 'bg-[#E67E22] border-[#E67E22] text-white' ? 'bg-[#3b82f6] border-[#3b82f6] text-white'
: 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#E67E22] hover:text-[#E67E22]' : 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]'
}`} }`}
> >
{topic} {topic}
@@ -242,7 +242,7 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
</div> </div>
<button <button
onClick={handleApplyTopics} onClick={handleApplyTopics}
className="text-xs font-body bg-[#E67E22] text-white px-4 py-1.5 rounded-sm hover:bg-[#C8651B] transition-colors" className="text-xs font-body bg-[#3b82f6] text-white px-4 py-1.5 rounded-sm hover:bg-[#2563eb] transition-colors"
> >
Update Suggestions Update Suggestions
</button> </button>
@@ -280,11 +280,11 @@ export default function AISuggestedArticles({ variant = 'compact' }: Props) {
sizes="(max-width: 768px) 50vw, 25vw" sizes="(max-width: 768px) 50vw, 25vw"
/> />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" /> <div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<span className="absolute bottom-2 left-2 text-[9px] font-body font-bold text-white uppercase tracking-wider bg-[#E67E22] px-1.5 py-0.5 rounded-sm"> <span className="absolute bottom-2 left-2 text-[9px] font-body font-bold text-white uppercase tracking-wider bg-[#3b82f6] px-1.5 py-0.5 rounded-sm">
{article.category} {article.category}
</span> </span>
</div> </div>
<h3 className="font-heading text-[#e0e0e0] text-sm font-semibold leading-snug line-clamp-2 group-hover:text-[#E67E22] transition-colors"> <h3 className="font-heading text-[#e0e0e0] text-sm font-semibold leading-snug line-clamp-2 group-hover:text-[#3b82f6] transition-colors">
{article.title} {article.title}
</h3> </h3>
<p className="text-[#555] font-body text-[11px] mt-1">{article.readTime}</p> <p className="text-[#555] font-body text-[11px] mt-1">{article.readTime}</p>

View File

@@ -36,7 +36,7 @@ export default function AboutDropdown() {
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
aria-haspopup="true" aria-haspopup="true"
aria-expanded={open} aria-expanded={open}
className="bb-browse-item gap-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]" className="bb-browse-item gap-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"
> >
<span>ABOUT</span> <span>ABOUT</span>
<span aria-hidden="true" className="text-[10px]"></span> <span aria-hidden="true" className="text-[10px]"></span>
@@ -54,7 +54,7 @@ export default function AboutDropdown() {
href={it.href} href={it.href}
role="menuitem" role="menuitem"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
className="flex items-center justify-between px-4 py-2 text-sm hover:bg-[#1a1a1a] hover:text-[var(--color-text-info,#E67E22)] focus:outline-none focus-visible:bg-[#1a1a1a] transition-colors" className="flex items-center justify-between px-4 py-2 text-sm hover:bg-[#1a1a1a] hover:text-[var(--color-text-info,#60a5fa)] focus:outline-none focus-visible:bg-[#1a1a1a] transition-colors"
> >
<span>{it.label}</span> <span>{it.label}</span>
<span aria-hidden="true" className="text-[10px] text-[#6b7280]"></span> <span aria-hidden="true" className="text-[10px] text-[#6b7280]"></span>

View File

@@ -163,7 +163,7 @@ export default function ArticleComments({ articleSlug }: Props) {
{replyTo && ( {replyTo && (
<div className="flex items-center justify-between mb-2 text-xs text-[#888]"> <div className="flex items-center justify-between mb-2 text-xs text-[#888]">
<span>Replying to comment</span> <span>Replying to comment</span>
<button onClick={() => setReplyTo(null)} className="text-[#E67E22] hover:underline">Cancel reply</button> <button onClick={() => setReplyTo(null)} className="text-[#3b82f6] hover:underline">Cancel reply</button>
</div> </div>
)} )}
<textarea <textarea
@@ -171,7 +171,7 @@ export default function ArticleComments({ articleSlug }: Props) {
onChange={(e) => setBody(e.target.value)} onChange={(e) => setBody(e.target.value)}
placeholder={replyTo ? "Write your reply…" : "Share your take on this story…"} placeholder={replyTo ? "Write your reply…" : "Share your take on this story…"}
rows={3} rows={3}
className="w-full bg-[#111] border border-[#252525] rounded text-white text-sm font-body p-2 resize-none focus:outline-none focus:border-[#E67E22]" className="w-full bg-[#111] border border-[#252525] rounded text-white text-sm font-body p-2 resize-none focus:outline-none focus:border-[#3b82f6]"
/> />
{error && <p className="text-[11px] text-red-400 mt-1">{error}</p>} {error && <p className="text-[11px] text-red-400 mt-1">{error}</p>}
<div className="flex items-center justify-between mt-2"> <div className="flex items-center justify-between mt-2">
@@ -179,7 +179,7 @@ export default function ArticleComments({ articleSlug }: Props) {
<button <button
onClick={submit} onClick={submit}
disabled={submitting || !body.trim()} disabled={submitting || !body.trim()}
className="bg-[#E67E22] hover:bg-[#C8651B] disabled:opacity-50 text-white font-body text-xs font-semibold px-4 py-1.5 rounded transition-colors" className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body text-xs font-semibold px-4 py-1.5 rounded transition-colors"
> >
{submitting ? "Posting…" : replyTo ? "Post reply" : "Post comment"} {submitting ? "Posting…" : replyTo ? "Post reply" : "Post comment"}
</button> </button>
@@ -193,13 +193,13 @@ export default function ArticleComments({ articleSlug }: Props) {
<div className="flex gap-3 justify-center"> <div className="flex gap-3 justify-center">
<Link <Link
href={`/forum/login?next=${encodeURIComponent(typeof window !== "undefined" ? window.location.pathname : "")}`} href={`/forum/login?next=${encodeURIComponent(typeof window !== "undefined" ? window.location.pathname : "")}`}
className="bg-[#E67E22] hover:bg-[#C8651B] text-white font-body text-xs font-semibold px-5 py-2 rounded" className="bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-xs font-semibold px-5 py-2 rounded"
> >
Sign in Sign in
</Link> </Link>
<Link <Link
href={`/forum/register?next=${encodeURIComponent(typeof window !== "undefined" ? window.location.pathname : "")}`} href={`/forum/register?next=${encodeURIComponent(typeof window !== "undefined" ? window.location.pathname : "")}`}
className="border border-[#E67E22] text-[#E67E22] hover:bg-[#E67E22]/10 font-body text-xs font-semibold px-5 py-2 rounded" className="border border-[#3b82f6] text-[#3b82f6] hover:bg-[#3b82f6]/10 font-body text-xs font-semibold px-5 py-2 rounded"
> >
Create account Create account
</Link> </Link>
@@ -250,14 +250,14 @@ function CommentItem({
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
<img src={comment.author_avatar_url} alt="" className="w-7 h-7 rounded-full bg-[#1a1a1a] object-cover flex-shrink-0" /> <img src={comment.author_avatar_url} alt="" className="w-7 h-7 rounded-full bg-[#1a1a1a] object-cover flex-shrink-0" />
) : ( ) : (
<div className="w-7 h-7 rounded-full bg-[#1a2535] text-[#E67E22] text-xs font-bold flex items-center justify-center flex-shrink-0"> <div className="w-7 h-7 rounded-full bg-[#1a2535] text-[#3b82f6] text-xs font-bold flex items-center justify-center flex-shrink-0">
{(comment.author_display_name || "?").trim().charAt(0).toUpperCase()} {(comment.author_display_name || "?").trim().charAt(0).toUpperCase()}
</div> </div>
)} )}
<div className="text-sm font-semibold text-white"> <div className="text-sm font-semibold text-white">
{comment.author_display_name || "Member"} {comment.author_display_name || "Member"}
{comment.is_ai_seeded && ( {comment.is_ai_seeded && (
<span className="ml-1.5 text-[9px] uppercase tracking-widest bg-[#1e3a5f] text-[#E67E22] px-1.5 py-0.5 rounded">AI</span> <span className="ml-1.5 text-[9px] uppercase tracking-widest bg-[#1e3a5f] text-[#60a5fa] px-1.5 py-0.5 rounded">AI</span>
)} )}
</div> </div>
<span className="text-[11px] text-[#666]">· {timeAgo(comment.created_at)}</span> <span className="text-[11px] text-[#666]">· {timeAgo(comment.created_at)}</span>
@@ -288,7 +288,7 @@ function CommentItem({
</button> </button>
</div> </div>
{canInteract && ( {canInteract && (
<button onClick={() => onReply(comment.id)} className="text-[#E67E22] hover:underline text-[11px]"> <button onClick={() => onReply(comment.id)} className="text-[#3b82f6] hover:underline text-[11px]">
Reply Reply
</button> </button>
)} )}

View File

@@ -71,7 +71,7 @@ export default function AskBBAI() {
aria-label="Ask BB AI" aria-label="Ask BB AI"
> >
<span className="relative inline-block w-3 h-3"> <span className="relative inline-block w-3 h-3">
<span className="absolute inset-0 rounded-full bg-[var(--color-text-info,#E67E22)]" /> <span className="absolute inset-0 rounded-full bg-[var(--color-text-info,#60a5fa)]" />
<span className="bb-ring" /> <span className="bb-ring" />
</span> </span>
<span className="font-serif">Ask BB AI</span> <span className="font-serif">Ask BB AI</span>
@@ -88,7 +88,7 @@ export default function AskBBAI() {
<header className="flex items-center justify-between px-5 py-4 border-b border-[#252525]"> <header className="flex items-center justify-between px-5 py-4 border-b border-[#252525]">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="relative inline-block w-3 h-3"> <span className="relative inline-block w-3 h-3">
<span className="absolute inset-0 rounded-full bg-[var(--color-text-info,#E67E22)]" /> <span className="absolute inset-0 rounded-full bg-[var(--color-text-info,#60a5fa)]" />
<span className="bb-ring" /> <span className="bb-ring" />
</span> </span>
<h2 className="font-serif text-lg font-semibold text-white">BB AI</h2> <h2 className="font-serif text-lg font-semibold text-white">BB AI</h2>
@@ -109,7 +109,7 @@ export default function AskBBAI() {
className={ className={
"inline-block max-w-[85%] rounded px-3 py-2 text-sm whitespace-pre-wrap " + "inline-block max-w-[85%] rounded px-3 py-2 text-sm whitespace-pre-wrap " +
(m.role === "user" (m.role === "user"
? "bg-[var(--color-text-info,#E67E22)] text-black" ? "bg-[var(--color-text-info,#60a5fa)] text-black"
: "bg-[#111] border border-[#252525] text-[#e5e7eb]") : "bg-[#111] border border-[#252525] text-[#e5e7eb]")
} }
dangerouslySetInnerHTML={{ __html: m.role === "assistant" ? linkifyMarkdown(m.content) : escapeHtml(m.content) }} dangerouslySetInnerHTML={{ __html: m.role === "assistant" ? linkifyMarkdown(m.content) : escapeHtml(m.content) }}
@@ -133,12 +133,12 @@ export default function AskBBAI() {
onKeyDown={(e) => e.key === "Enter" && send()} onKeyDown={(e) => e.key === "Enter" && send()}
placeholder="Ask about an event, a vendor, a workflow…" placeholder="Ask about an event, a vendor, a workflow…"
disabled={busy} disabled={busy}
className="flex-1 bg-[#111] border border-[#252525] rounded px-3 py-2 text-sm text-[#e5e7eb] focus:border-[var(--color-text-info,#E67E22)] focus:outline-none" className="flex-1 bg-[#111] border border-[#252525] rounded px-3 py-2 text-sm text-[#e5e7eb] focus:border-[var(--color-text-info,#60a5fa)] focus:outline-none"
/> />
<button <button
onClick={send} onClick={send}
disabled={busy || !input.trim()} disabled={busy || !input.trim()}
className="bg-[var(--color-text-info,#E67E22)] text-black font-semibold px-4 py-2 rounded text-sm disabled:opacity-40 disabled:cursor-not-allowed" className="bg-[var(--color-text-info,#60a5fa)] text-black font-semibold px-4 py-2 rounded text-sm disabled:opacity-40 disabled:cursor-not-allowed"
> >
Send Send
</button> </button>
@@ -161,7 +161,7 @@ function linkifyMarkdown(s: string): string {
let out = escapeHtml(s); let out = escapeHtml(s);
out = out.replace( out = out.replace(
/\[([^\]]+)\]\(([^)]+)\)/g, /\[([^\]]+)\]\(([^)]+)\)/g,
'<a href="$2" class="text-[#E67E22] hover:underline">$1</a>' '<a href="$2" class="text-[#60a5fa] hover:underline">$1</a>'
); );
out = out.replace(/\n/g, "<br/>"); out = out.replace(/\n/g, "<br/>");
return out; return out;

View File

@@ -27,7 +27,7 @@ async function loadTiles(slugs: string[]): Promise<CompanyTile[]> {
const { data: ads } = await adv const { data: ads } = await adv
.from("active_advertisers_by_property") .from("active_advertisers_by_property")
.select("company_name_lower") .select("company_name_lower")
.eq("property", "broadcastbeat"); .eq("property", "avbeat");
advNames = new Set((ads || []).map((r: any) => r.company_name_lower)); advNames = new Set((ads || []).map((r: any) => r.company_name_lower));
} catch { /* tolerate */ } } catch { /* tolerate */ }
@@ -57,7 +57,7 @@ export default async function CompaniesInThisStory({ slugs }: { slugs: string[]
return ( return (
<div className="bg-[#111] border border-[#222] p-5"> <div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#E67E22] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
Companies in this story Companies in this story
</h3> </h3>
<ul className="space-y-3"> <ul className="space-y-3">
@@ -83,7 +83,7 @@ export default async function CompaniesInThisStory({ slugs }: { slugs: string[]
)} )}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-1 flex-wrap"> <div className="flex items-center gap-1 flex-wrap">
<span className="font-heading text-[#e0e0e0] text-sm font-bold group-hover:text-[#E67E22] transition-colors truncate"> <span className="font-heading text-[#e0e0e0] text-sm font-bold group-hover:text-[#3b82f6] transition-colors truncate">
{t.name} {t.name}
</span> </span>
</div> </div>

View File

@@ -112,12 +112,12 @@ export default function CompanyMentionsHover() {
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
<img src={preview.logoUrl} alt="" className="w-12 h-12 rounded bg-white p-1 object-contain flex-shrink-0" loading="lazy" /> <img src={preview.logoUrl} alt="" className="w-12 h-12 rounded bg-white p-1 object-contain flex-shrink-0" loading="lazy" />
) : ( ) : (
<div className="w-12 h-12 rounded bg-[#1a2535] flex items-center justify-center text-[#E67E22] font-bold flex-shrink-0"> <div className="w-12 h-12 rounded bg-[#1a2535] flex items-center justify-center text-[#3b82f6] font-bold flex-shrink-0">
{(preview.name || "?").trim().charAt(0).toUpperCase()} {(preview.name || "?").trim().charAt(0).toUpperCase()}
</div> </div>
)} )}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<Link href={`/manufacturers/${preview.slug}`} className="font-semibold text-white hover:text-[#E67E22] block truncate"> <Link href={`/manufacturers/${preview.slug}`} className="font-semibold text-white hover:text-[#3b82f6] block truncate">
{preview.name} {preview.name}
</Link> </Link>
<div className="flex items-center gap-1.5 mt-1 flex-wrap text-[10px]"> <div className="flex items-center gap-1.5 mt-1 flex-wrap text-[10px]">
@@ -145,7 +145,7 @@ export default function CompanyMentionsHover() {
<ul className="space-y-1"> <ul className="space-y-1">
{preview.stories.slice(0, 3).map((s) => ( {preview.stories.slice(0, 3).map((s) => (
<li key={s.slug}> <li key={s.slug}>
<Link href={`/news/${s.slug}`} className="text-xs text-[#E67E22] hover:text-[#93c5fd] hover:underline line-clamp-2"> <Link href={`/news/${s.slug}`} className="text-xs text-[#60a5fa] hover:text-[#93c5fd] hover:underline line-clamp-2">
{s.title} {s.title}
</Link> </Link>
{s.date && <span className="text-[10px] text-[#666] ml-1">· {new Date(s.date).toISOString().slice(0, 10)}</span>} {s.date && <span className="text-[10px] text-[#666] ml-1">· {new Date(s.date).toISOString().slice(0, 10)}</span>}
@@ -156,11 +156,11 @@ export default function CompanyMentionsHover() {
)} )}
<div className="border-t border-[#2a3a50] mt-3 pt-2 flex items-center justify-between"> <div className="border-t border-[#2a3a50] mt-3 pt-2 flex items-center justify-between">
<Link href={`/manufacturers/${preview.slug}`} className="text-xs font-semibold text-[#E67E22] hover:text-[#93c5fd] hover:underline"> <Link href={`/manufacturers/${preview.slug}`} className="text-xs font-semibold text-[#60a5fa] hover:text-[#93c5fd] hover:underline">
Full profile Full profile
</Link> </Link>
{preview.website && ( {preview.website && (
<a href={preview.website} target="_blank" rel="noopener noreferrer" className="text-[10px] text-[#888] hover:text-[#E67E22]"> <a href={preview.website} target="_blank" rel="noopener noreferrer" className="text-[10px] text-[#888] hover:text-[#3b82f6]">
{preview.website.replace(/^https?:\/\//, "").replace(/\/$/, "")} {preview.website.replace(/^https?:\/\//, "").replace(/\/$/, "")}
</a> </a>
)} )}

View File

@@ -76,7 +76,7 @@ export default function CookieConsent() {
<strong className="text-white">Accept All</strong>, you consent to our use of cookies.{" "} <strong className="text-white">Accept All</strong>, you consent to our use of cookies.{" "}
<Link <Link
href="/privacy" href="/privacy"
className="text-[#E67E22] underline hover:text-blue-400 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22] transition-colors" className="text-[#3b82f6] underline hover:text-blue-400 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors"
> >
Privacy Policy Privacy Policy
</Link> </Link>
@@ -86,7 +86,7 @@ export default function CookieConsent() {
<button <button
type="button" type="button"
onClick={handleDecline} onClick={handleDecline}
className="px-4 py-2 text-sm font-body font-bold text-[#888] border border-[#333] rounded hover:border-[#555] hover:text-[#aaa] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] focus-visible:ring-offset-1 focus-visible:ring-offset-[#0d1117]" className="px-4 py-2 text-sm font-body font-bold text-[#888] border border-[#333] rounded hover:border-[#555] hover:text-[#aaa] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] focus-visible:ring-offset-1 focus-visible:ring-offset-[#0d1117]"
aria-label="Decline non-essential cookies" aria-label="Decline non-essential cookies"
> >
Decline Decline
@@ -95,7 +95,7 @@ export default function CookieConsent() {
ref={acceptRef} ref={acceptRef}
type="button" type="button"
onClick={handleAccept} onClick={handleAccept}
className="px-5 py-2 text-sm font-body font-bold text-white bg-[#E67E22] rounded hover:bg-blue-500 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#E67E22] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d1117]" className="px-5 py-2 text-sm font-body font-bold text-white bg-[#3b82f6] rounded hover:bg-blue-500 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d1117]"
aria-label="Accept all cookies" aria-label="Accept all cookies"
> >
Accept All Accept All

View File

@@ -135,7 +135,7 @@ export default async function DoubleTicker() {
process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ {
db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb") as "public" }, db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "av") as "public" },
auth: { persistSession: false }, auth: { persistSession: false },
} }
); );

View File

@@ -99,7 +99,7 @@ export default function EventsDropdown() {
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
aria-haspopup="true" aria-haspopup="true"
aria-expanded={open} aria-expanded={open}
className="bb-browse-item gap-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#E67E22]" className="bb-browse-item gap-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"
> >
<span>Show coverage</span> <span>Show coverage</span>
<span aria-hidden="true" className="text-[10px]"></span> <span aria-hidden="true" className="text-[10px]"></span>
@@ -113,12 +113,12 @@ export default function EventsDropdown() {
style={{ borderRadius: "var(--border-radius-md,6px)" }} style={{ borderRadius: "var(--border-radius-md,6px)" }}
> >
<div className="relative p-4"> <div className="relative p-4">
<span aria-hidden className="absolute top-2 left-2 w-3 h-3 border-t border-l border-[var(--color-text-info,#E67E22)]"></span> <span aria-hidden className="absolute top-2 left-2 w-3 h-3 border-t border-l border-[var(--color-text-info,#60a5fa)]"></span>
<span aria-hidden className="absolute bottom-2 right-2 w-3 h-3 border-b border-r border-[var(--color-text-info,#E67E22)]"></span> <span aria-hidden className="absolute bottom-2 right-2 w-3 h-3 border-b border-r border-[var(--color-text-info,#60a5fa)]"></span>
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<h3 className="font-serif text-base font-semibold">Show coverage</h3> <h3 className="font-serif text-base font-semibold">Show coverage</h3>
<span className="inline-flex items-center gap-1 text-[10px] font-mono uppercase tracking-wider text-[var(--color-text-info,#E67E22)]"> <span className="inline-flex items-center gap-1 text-[10px] font-mono uppercase tracking-wider text-[var(--color-text-info,#60a5fa)]">
<span className="bb-pulse-dot" /> <span className="bb-pulse-dot" />
BB AI BB AI
</span> </span>
@@ -158,7 +158,7 @@ export default function EventsDropdown() {
<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" /> <polyline points="12 6 12 12 16 14" />
</svg> </svg>
<span className="text-[var(--color-text-info,#E67E22)]">T-{e.days_until}d</span> <span className="text-[var(--color-text-info,#60a5fa)]">T-{e.days_until}d</span>
</> </>
)} )}
</div> </div>
@@ -168,7 +168,7 @@ export default function EventsDropdown() {
</div> </div>
<div className="flex items-center gap-2 text-[10px] font-mono text-[#6b7280] mt-1"> <div className="flex items-center gap-2 text-[10px] font-mono text-[#6b7280] mt-1">
<span>{fmtDateRange(e.start_date, e.end_date)}</span> <span>{fmtDateRange(e.start_date, e.end_date)}</span>
{host && <span className="text-[var(--color-text-info,#E67E22)]">· {host}</span>} {host && <span className="text-[var(--color-text-info,#60a5fa)]">· {host}</span>}
</div> </div>
</Link> </Link>
</li> </li>
@@ -177,7 +177,7 @@ export default function EventsDropdown() {
</ul> </ul>
<div className="mt-3 pt-2 border-t border-[#252525] flex items-center justify-between text-[10px] font-mono text-[#6b7280]"> <div className="mt-3 pt-2 border-t border-[#252525] flex items-center justify-between text-[10px] font-mono text-[#6b7280]">
<Link href="/show-coverage" className="text-[var(--color-text-info,#E67E22)] hover:underline" onClick={() => setOpen(false)}> <Link href="/show-coverage" className="text-[var(--color-text-info,#60a5fa)] hover:underline" onClick={() => setOpen(false)}>
See all 13 events See all 13 events
</Link> </Link>
{loadedAt && <span>auto-updated · {ago(loadedAt)}</span>} {loadedAt && <span>auto-updated · {ago(loadedAt)}</span>}

View File

@@ -37,7 +37,7 @@ export default async function FeaturedBento() {
process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ {
db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb") as "public" }, db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "av") as "public" },
auth: { persistSession: false }, auth: { persistSession: false },
} }
); );
@@ -73,7 +73,7 @@ export default async function FeaturedBento() {
<div className="flex items-center gap-3 mb-4 md:mb-5 flex-wrap"> <div className="flex items-center gap-3 mb-4 md:mb-5 flex-wrap">
<span className="section-label">Staff Editorial</span> <span className="section-label">Staff Editorial</span>
<div className="flex-1 h-px bg-[#2a2a2a]" /> <div className="flex-1 h-px bg-[#2a2a2a]" />
<Link href="/news/featured" className="text-[11px] font-mono text-[var(--color-text-info,#E67E22)] hover:underline whitespace-nowrap"> <Link href="/news/featured" className="text-[11px] font-mono text-[var(--color-text-info,#60a5fa)] hover:underline whitespace-nowrap">
All featured All featured
</Link> </Link>
</div> </div>
@@ -89,7 +89,7 @@ export default async function FeaturedBento() {
{/* Cinematic 21:9 hero — fills left column, aspect ratio preserved */} {/* Cinematic 21:9 hero — fills left column, aspect ratio preserved */}
<Link <Link
href={`/news/${hero.wp_slug}`} href={`/news/${hero.wp_slug}`}
className="group block relative overflow-hidden rounded border border-[#252525] bg-[#0b0f17] hover:border-[var(--color-text-info,#E67E22)] transition-colors" className="group block relative overflow-hidden rounded border border-[#252525] bg-[#0b0f17] hover:border-[var(--color-text-info,#60a5fa)] transition-colors"
> >
<div className="relative aspect-[21/9] md:aspect-[21/8] bg-[#111]"> <div className="relative aspect-[21/9] md:aspect-[21/8] bg-[#111]">
<img <img
@@ -99,7 +99,7 @@ export default async function FeaturedBento() {
/> />
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/65 to-transparent" /> <div className="absolute inset-0 bg-gradient-to-t from-black via-black/65 to-transparent" />
<span className="absolute top-4 left-4 inline-flex items-center gap-1.5 bg-[var(--color-text-info,#E67E22)] text-black px-3 py-1 rounded font-mono text-[11px] uppercase tracking-widest font-bold shadow-md"> <span className="absolute top-4 left-4 inline-flex items-center gap-1.5 bg-[var(--color-text-info,#60a5fa)] text-black px-3 py-1 rounded font-mono text-[11px] uppercase tracking-widest font-bold shadow-md">
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 2l2.6 7.6H22l-6.2 4.5L18.4 22 12 17.3 5.6 22l2.6-7.9L2 9.6h7.4z" /> <path d="M12 2l2.6 7.6H22l-6.2 4.5L18.4 22 12 17.3 5.6 22l2.6-7.9L2 9.6h7.4z" />
</svg> </svg>
@@ -108,11 +108,11 @@ export default async function FeaturedBento() {
<div className="absolute bottom-0 left-0 right-0 p-6 md:p-10"> <div className="absolute bottom-0 left-0 right-0 p-6 md:p-10">
{hero.category && ( {hero.category && (
<div className="text-[10px] font-mono uppercase tracking-widest text-[var(--color-text-info,#E67E22)] mb-2"> <div className="text-[10px] font-mono uppercase tracking-widest text-[var(--color-text-info,#60a5fa)] mb-2">
{hero.category} {hero.category}
</div> </div>
)} )}
<h3 className="font-serif text-2xl md:text-4xl lg:text-5xl text-white leading-tight max-w-3xl group-hover:text-[var(--color-text-info,#E67E22)] transition-colors"> <h3 className="font-serif text-2xl md:text-4xl lg:text-5xl text-white leading-tight max-w-3xl group-hover:text-[var(--color-text-info,#60a5fa)] transition-colors">
{hero.title} {hero.title}
</h3> </h3>
{hero.excerpt && ( {hero.excerpt && (
@@ -123,7 +123,7 @@ export default async function FeaturedBento() {
<div className="mt-4 flex flex-wrap items-center gap-3 text-[11px] font-mono text-[#9ca3af]"> <div className="mt-4 flex flex-wrap items-center gap-3 text-[11px] font-mono text-[#9ca3af]">
<span>{hero.author_name || "AV Beat"}</span> <span>{hero.author_name || "AV Beat"}</span>
<StarRating seed={hero.wp_slug} publishedAt={hero.wp_published_at} size="sm" /> <StarRating seed={hero.wp_slug} publishedAt={hero.wp_published_at} size="sm" />
<span aria-hidden="true" className="text-[var(--color-text-info,#E67E22)] ml-2"> Read full story</span> <span aria-hidden="true" className="text-[var(--color-text-info,#60a5fa)] ml-2"> Read full story</span>
</div> </div>
</div> </div>
</div> </div>
@@ -140,7 +140,7 @@ export default async function FeaturedBento() {
<Link <Link
key={r.wp_slug} key={r.wp_slug}
href={`/news/${r.wp_slug}`} href={`/news/${r.wp_slug}`}
className="group flex flex-col h-full rounded border border-[#252525] bg-[#0b0f17] overflow-hidden hover:border-[var(--color-text-info,#E67E22)] transition-colors" className="group flex flex-col h-full rounded border border-[#252525] bg-[#0b0f17] overflow-hidden hover:border-[var(--color-text-info,#60a5fa)] transition-colors"
> >
<div className="aspect-[16/10] bg-[#111] relative flex-shrink-0"> <div className="aspect-[16/10] bg-[#111] relative flex-shrink-0">
<img <img
@@ -150,10 +150,10 @@ export default async function FeaturedBento() {
/> />
</div> </div>
<div className="p-3 flex flex-col gap-1 flex-1 min-h-0 overflow-hidden"> <div className="p-3 flex flex-col gap-1 flex-1 min-h-0 overflow-hidden">
<div className="text-[9px] font-mono uppercase tracking-wider text-[var(--color-text-info,#E67E22)]"> <div className="text-[9px] font-mono uppercase tracking-wider text-[var(--color-text-info,#60a5fa)]">
{r.category || "News"} {r.category || "News"}
</div> </div>
<h4 className="font-serif text-sm leading-tight text-[#e5e7eb] group-hover:text-[var(--color-text-info,#E67E22)] line-clamp-2"> <h4 className="font-serif text-sm leading-tight text-[#e5e7eb] group-hover:text-[var(--color-text-info,#60a5fa)] line-clamp-2">
{r.title} {r.title}
</h4> </h4>
<div className="mt-auto"> <div className="mt-auto">

Some files were not shown because too many files have changed in this diff Show More