feat(brand): AV BEAT 2026 rebrand — blue/navy/white identity

- New AvBeatLogo inline-SVG component (rounded-square emblem with
  forward-leaning AV monogram + horizontal beam detail) at
  src/components/AvBeatLogo.tsx, three variants (full/wordmark/icon).
- Header.tsx now uses the responsive AV BEAT logo lockup in the
  top-left, links to /, full lockup on desktop (emblem+wordmark+tagline),
  emblem+wordmark on tablet, emblem-only on mobile.
- Removed DualSpeedTicker (the 'RECENT FORUM POSTS' ticker strip);
  the news ticker / glow chrome does not match the new aesthetic and the
  forum row was the explicit removal target.
- Tailwind theme + globals.css now expose the AV BEAT 2026 palette as
  semantic tokens: --brand-blue (#1D4ED8), --brand-blue-bright (#38BDF8),
  --brand-navy (#0F172A), --brand-bg (#F8FAFC), --brand-surface, --brand-border,
  --brand-text, --brand-text-muted. Legacy --color-* aliases re-point to the
  new tokens so any inline-styled component re-themes for free.
- Site-wide hex sweep migrates 2,769 hardcoded color literals across 144
  files from the old dark-broadcast palette to the new tokens (orange
  -> blue, dark-brown -> white surface / navy text, cream -> navy).
- Layout body class flipped from 'bb-neon' to 'bg-brand-bg text-brand-text'
  so the dark glow chrome no longer leaks through the new light theme.
This commit is contained in:
2026-06-03 12:07:18 +00:00
parent 39d2bf9073
commit 8042024c4a
146 changed files with 2219 additions and 2081 deletions

View File

@@ -356,7 +356,7 @@ export default function AdminNewsletterPage() {
if (loading) {
return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" />
<div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div>
);
}
@@ -373,7 +373,7 @@ export default function AdminNewsletterPage() {
)}
{/* Header */}
<div className="bg-[#111] border-b border-[#3a322b] px-6 py-4">
<div className="bg-[#111] border-b border-[#DCE6F2] px-6 py-4">
<div className="max-w-6xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-3">
<Link href="/admin" className="text-[#555] hover:text-[#888] transition-colors">
@@ -389,7 +389,7 @@ export default function AdminNewsletterPage() {
<div className="flex items-center gap-3">
<Link
href="/admin/newsletter/templates"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors">
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
@@ -397,7 +397,7 @@ export default function AdminNewsletterPage() {
</Link>
<Link
href="/admin/newsletter/campaign-editor"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-white bg-[#F0A623] hover:bg-[#BA7517] rounded-lg transition-colors">
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-white bg-[#1D4ED8] hover:bg-[#1E3A8A] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
</svg>
@@ -405,18 +405,18 @@ export default function AdminNewsletterPage() {
</Link>
<Link
href="/admin/newsletter/analytics"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors">
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" />
</svg>
Analytics
</Link>
<div className="w-px h-8 bg-[#3a322b]" />
<div className="w-px h-8 bg-[#DCE6F2]" />
<div className="text-right">
<p className="text-white font-display font-bold text-xl">{activeCount}</p>
<p className="text-[#555] text-xs font-body">active subscribers</p>
</div>
<div className="w-px h-8 bg-[#3a322b]" />
<div className="w-px h-8 bg-[#DCE6F2]" />
<div className="text-right">
<p className="text-white font-display font-bold text-xl">{totalCount}</p>
<p className="text-[#555] text-xs font-body">total</p>
@@ -427,15 +427,15 @@ export default function AdminNewsletterPage() {
<div className="max-w-6xl mx-auto px-6 py-6">
{/* Tabs */}
<div className="flex gap-1 mb-6 bg-[#111] border border-[#3a322b] rounded-lg p-1 w-fit">
<div className="flex gap-1 mb-6 bg-[#111] border border-[#DCE6F2] rounded-lg p-1 w-fit">
<button
onClick={() => setActiveTab('subscribers')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'subscribers' ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white'}`}>
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'subscribers' ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'}`}>
Subscribers
</button>
<button
onClick={() => setActiveTab('send-digest')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'send-digest' ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white'}`}>
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'send-digest' ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'}`}>
Send Digest
</button>
</div>
@@ -452,12 +452,12 @@ export default function AdminNewsletterPage() {
onChange={(e) => setSearchQuery(e.target.value)}
className="search-input flex-1 py-2 px-3 text-sm"
/>
<div className="flex gap-1 bg-[#111] border border-[#3a322b] rounded-lg p-1">
<div className="flex gap-1 bg-[#111] border border-[#DCE6F2] rounded-lg p-1">
{(['all', 'active', 'unsubscribed'] as const).map((s) => (
<button
key={s}
onClick={() => setStatusFilter(s)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-colors ${statusFilter === s ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white'}`}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-colors ${statusFilter === s ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'}`}
>
{s}
</button>
@@ -467,7 +467,7 @@ export default function AdminNewsletterPage() {
<button
onClick={handleExportCSV}
disabled={subscribers.length === 0}
className="flex items-center gap-1.5 px-3 py-2 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
className="flex items-center gap-1.5 px-3 py-2 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 015.25 21h13.5A2.25 2.25 0 0121 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
@@ -478,9 +478,9 @@ export default function AdminNewsletterPage() {
{/* Bulk Actions Bar */}
{someSelected && (
<div className="flex items-center justify-between bg-[#231d18] border border-[#412402] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center justify-between bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center gap-3">
<span className="text-sm font-body font-semibold text-[#F0A623]">
<span className="text-sm font-body font-semibold text-[#1D4ED8]">
{selectedIds.size} selected
</span>
<button
@@ -512,17 +512,17 @@ export default function AdminNewsletterPage() {
Delete
</button>
{bulkLoading && (
<div className="w-4 h-4 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" />
<div className="w-4 h-4 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
)}
</div>
</div>
)}
{/* Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden">
<div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
{loadingData ? (
<div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" />
<div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div>
) : subscribers.length === 0 ? (
<div className="text-center py-16">
@@ -534,13 +534,13 @@ export default function AdminNewsletterPage() {
) : (
<table className="w-full">
<thead>
<tr className="border-b border-[#3a322b]">
<tr className="border-b border-[#DCE6F2]">
<th className="px-4 py-3 w-10">
<input
type="checkbox"
checked={allSelected}
onChange={toggleSelectAll}
className="w-4 h-4 rounded border-[#444] bg-[#231d18] accent-[#F0A623] cursor-pointer"
className="w-4 h-4 rounded border-[#444] bg-[#FFFFFF] accent-[#1D4ED8] cursor-pointer"
/>
</th>
<th className="text-left px-4 py-3 text-xs font-body font-bold text-[#555] uppercase tracking-wider">Email</th>
@@ -554,21 +554,21 @@ export default function AdminNewsletterPage() {
{subscribers.map((sub) => (
<tr
key={sub.id}
className={`border-b border-[#231d18] transition-colors ${selectedIds.has(sub.id) ? 'bg-[#0d1a2d]' : 'hover:bg-[#1c1815]'}`}
className={`border-b border-[#FFFFFF] transition-colors ${selectedIds.has(sub.id) ? 'bg-[#0d1a2d]' : 'hover:bg-[#F8FAFC]'}`}
>
<td className="px-4 py-3 w-10">
<input
type="checkbox"
checked={selectedIds.has(sub.id)}
onChange={() => toggleSelect(sub.id)}
className="w-4 h-4 rounded border-[#444] bg-[#231d18] accent-[#F0A623] cursor-pointer"
className="w-4 h-4 rounded border-[#444] bg-[#FFFFFF] accent-[#1D4ED8] cursor-pointer"
/>
</td>
<td className="px-4 py-3 text-sm font-body text-[#e0e0e0]">{sub.email}</td>
<td className="px-4 py-3 hidden md:table-cell">
<div className="flex flex-wrap gap-1">
{sub.topics?.slice(0, 3).map((t) => (
<span key={t} className="text-xs bg-[#231d18] text-[#F0A623] px-2 py-0.5 rounded-full font-body">{t}</span>
<span key={t} className="text-xs bg-[#FFFFFF] text-[#1D4ED8] px-2 py-0.5 rounded-full font-body">{t}</span>
))}
{sub.topics?.length > 3 && (
<span className="text-xs text-[#555] font-body">+{sub.topics.length - 3}</span>
@@ -576,7 +576,7 @@ export default function AdminNewsletterPage() {
</div>
</td>
<td className="px-4 py-3">
<span className={`text-xs font-body font-bold px-2 py-1 rounded-full ${sub.status === 'active' ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#231d18]'}`}>
<span className={`text-xs font-body font-bold px-2 py-1 rounded-full ${sub.status === 'active' ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#FFFFFF]'}`}>
{sub.status}
</span>
</td>
@@ -616,21 +616,21 @@ export default function AdminNewsletterPage() {
{activeTab === 'send-digest' && (
<div className="max-w-2xl">
{/* SMTP warning if not configured */}
<div className="bg-[#231d18] border border-[#412402] rounded-lg p-4 mb-6 flex gap-3">
<svg className="w-5 h-5 text-[#F0A623] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-4 mb-6 flex gap-3">
<svg className="w-5 h-5 text-[#1D4ED8] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<p className="text-[#e0e0e0] text-sm font-body font-semibold">SMTP Required</p>
<p className="text-[#777] text-xs font-body mt-0.5">
Set <code className="text-[#F0A623]">SMTP_HOST</code>, <code className="text-[#F0A623]">SMTP_USER</code>, <code className="text-[#F0A623]">SMTP_PASS</code>, and <code className="text-[#F0A623]">SMTP_FROM_EMAIL</code> in your environment variables to enable sending.
Set <code className="text-[#1D4ED8]">SMTP_HOST</code>, <code className="text-[#1D4ED8]">SMTP_USER</code>, <code className="text-[#1D4ED8]">SMTP_PASS</code>, and <code className="text-[#1D4ED8]">SMTP_FROM_EMAIL</code> in your environment variables to enable sending.
</p>
</div>
</div>
{/* Applied template badge */}
{appliedTemplate && (
<div className="flex items-center justify-between bg-[#231d18] border border-[#412402] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center justify-between bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5" style={{ backgroundColor: appliedTemplate.accent_color + '20' }}>
<span style={{ color: appliedTemplate.accent_color }}>
@@ -662,7 +662,7 @@ export default function AdminNewsletterPage() {
<p className="text-xs font-body text-[#555]">Fill in the fields below or start from a template</p>
<button
onClick={() => { fetchTemplates(); setShowTemplateDrawer(true); }}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#F0A623] border border-[#412402] rounded-lg hover:bg-[#231d18] transition-colors"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#1D4ED8] border border-[#DCE6F2] rounded-lg hover:bg-[#FFFFFF] transition-colors"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
@@ -725,14 +725,14 @@ export default function AdminNewsletterPage() {
<button
type="button"
onClick={addArticle}
className="text-xs text-[#F0A623] hover:text-blue-400 font-body font-semibold transition-colors"
className="text-xs text-[#1D4ED8] hover:text-blue-400 font-body font-semibold transition-colors"
>
+ Add Article
</button>
</div>
<div className="space-y-3">
{digestArticles.map((article, index) => (
<div key={index} className="bg-[#111] border border-[#3a322b] rounded-lg p-4">
<div key={index} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-body text-[#555]">Article {index + 1}</span>
{digestArticles.length > 1 && (
@@ -803,11 +803,11 @@ export default function AdminNewsletterPage() {
{/* Template Picker Drawer */}
{showTemplateDrawer && (
<div className="fixed inset-0 z-50 bg-black/70 flex items-end sm:items-center justify-center p-0 sm:p-4" onClick={() => setShowTemplateDrawer(false)}>
<div className="bg-[#111] border border-[#3a322b] rounded-t-2xl sm:rounded-xl w-full sm:max-w-2xl max-h-[80vh] overflow-hidden flex flex-col" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between px-6 py-4 border-b border-[#3a322b] flex-shrink-0">
<div className="bg-[#111] border border-[#DCE6F2] rounded-t-2xl sm:rounded-xl w-full sm:max-w-2xl max-h-[80vh] overflow-hidden flex flex-col" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between px-6 py-4 border-b border-[#DCE6F2] flex-shrink-0">
<h2 className="font-display font-bold text-white text-base">Choose a Template</h2>
<div className="flex items-center gap-3">
<Link href="/admin/newsletter/templates" className="text-xs text-[#F0A623] font-body hover:text-blue-400 transition-colors">
<Link href="/admin/newsletter/templates" className="text-xs text-[#1D4ED8] font-body hover:text-blue-400 transition-colors">
Manage templates
</Link>
<button onClick={() => setShowTemplateDrawer(false)} className="text-[#555] hover:text-white transition-colors">
@@ -820,12 +820,12 @@ export default function AdminNewsletterPage() {
<div className="overflow-y-auto flex-1 p-4">
{loadingTemplates ? (
<div className="flex items-center justify-center py-12">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" />
<div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div>
) : templates.length === 0 ? (
<div className="text-center py-12">
<p className="text-[#555] font-body text-sm">No templates found.</p>
<Link href="/admin/newsletter/templates" className="text-[#F0A623] text-sm font-body hover:text-blue-400 mt-2 inline-block">
<Link href="/admin/newsletter/templates" className="text-[#1D4ED8] text-sm font-body hover:text-blue-400 mt-2 inline-block">
Create your first template
</Link>
</div>
@@ -835,7 +835,7 @@ export default function AdminNewsletterPage() {
<button
key={tpl.id}
onClick={() => applyTemplate(tpl)}
className="flex items-start gap-3 p-4 bg-[#1c1815] border border-[#3a322b] rounded-xl text-left hover:border-[#F0A623] hover:bg-[#231d18] transition-colors group"
className="flex items-start gap-3 p-4 bg-[#F8FAFC] border border-[#DCE6F2] rounded-xl text-left hover:border-[#1D4ED8] hover:bg-[#FFFFFF] transition-colors group"
>
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5" style={{ backgroundColor: tpl.accent_color + '20' }}>
<span style={{ color: tpl.accent_color }}>
@@ -851,8 +851,8 @@ export default function AdminNewsletterPage() {
</div>
{tpl.description && <p className="text-xs text-[#777] font-body line-clamp-1">{tpl.description}</p>}
<div className="flex gap-1.5 mt-1.5">
<span className="text-xs bg-[#231d18] text-[#888] px-1.5 py-0.5 rounded font-body capitalize">{tpl.layout}</span>
<span className="text-xs bg-[#231d18] text-[#888] px-1.5 py-0.5 rounded font-body">{tpl.article_blocks?.length || 0} slots</span>
<span className="text-xs bg-[#FFFFFF] text-[#888] px-1.5 py-0.5 rounded font-body capitalize">{tpl.layout}</span>
<span className="text-xs bg-[#FFFFFF] text-[#888] px-1.5 py-0.5 rounded font-body">{tpl.article_blocks?.length || 0} slots</span>
</div>
</div>
</button>