AV launch: global color scheme to match logo — gold #ffb800 primary, red #d60701 secondary, warm dark bars (1a1208 / 161310) replacing the inherited BB blue palette. Swapped 21 root tokens + 112 component files.

This commit is contained in:
2026-06-02 23:24:54 +00:00
parent 54117c34d3
commit 10ecf723dc
115 changed files with 1082 additions and 1082 deletions

View File

@@ -226,7 +226,7 @@ export default async function TeamPage() {
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="font-serif text-lg font-bold leading-tight">{p.display_name}</h3> <h3 className="font-serif text-lg font-bold leading-tight">{p.display_name}</h3>
<div className="inline-block mt-1 px-2 py-0.5 rounded-sm bg-[#1a2535] border border-[#3b82f6]/30 text-[10px] font-mono uppercase tracking-[0.12em] text-[#7ba0ff]"> <div className="inline-block mt-1 px-2 py-0.5 rounded-sm bg-[#1a1208] border border-[#ffb800]/30 text-[10px] font-mono uppercase tracking-[0.12em] text-[#7ba0ff]">
{p.role} {p.role}
</div> </div>
<div className="text-xs text-[#aab1bd] mt-3 space-y-1"> <div className="text-xs text-[#aab1bd] mt-3 space-y-1">

View File

@@ -305,14 +305,14 @@ export default function AccountPage() {
{toast && ( {toast && (
<div <div
className={`fixed bottom-6 left-1/2 -translate-x-1/2 z-50 border font-body text-sm px-5 py-3 rounded-sm shadow-lg flex items-center gap-2 animate-fade-in ${ className={`fixed bottom-6 left-1/2 -translate-x-1/2 z-50 border font-body text-sm px-5 py-3 rounded-sm shadow-lg flex items-center gap-2 animate-fade-in ${
toast.type === "success" ?"bg-[#1a2535] border-[#3b82f6] text-[#e0e0e0]" :"bg-[#1a0a0a] border-[#cc0000] text-[#e0e0e0]" toast.type === "success" ?"bg-[#1a1208] border-[#ffb800] text-[#e0e0e0]" :"bg-[#1a0a0a] border-[#d60701] text-[#e0e0e0]"
}`}> }`}>
{toast.type === "success" ? ( {toast.type === "success" ? (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ffb800" strokeWidth="2.5" aria-hidden="true">
<polyline points="20 6 9 17 4 12" /> <polyline points="20 6 9 17 4 12" />
</svg> </svg>
) : ( ) : (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#cc0000" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#d60701" strokeWidth="2.5" aria-hidden="true">
<circle cx="12" cy="12" r="10" /><line x1="15" y1="9" x2="9" y2="15" /><line x1="9" y1="9" x2="15" y2="15" /> <circle cx="12" cy="12" r="10" /><line x1="15" y1="9" x2="9" y2="15" /><line x1="9" y1="9" x2="15" y2="15" />
</svg> </svg>
)} )}
@@ -324,7 +324,7 @@ export default function AccountPage() {
<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-[#3b82f6] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#ffb800] transition-colors">Home</Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<span className="text-[#888]">Account</span> <span className="text-[#888]">Account</span>
</nav> </nav>
@@ -341,7 +341,7 @@ export default function AccountPage() {
</div> </div>
<button <button
onClick={handleSignOut} onClick={handleSignOut}
className="font-body text-xs font-bold uppercase tracking-wide text-[#888] hover:text-[#cc0000] border border-[#2a2a2a] hover:border-[#cc0000] px-4 py-2 rounded-sm transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#888] hover:text-[#d60701] border border-[#2a2a2a] hover:border-[#d60701] px-4 py-2 rounded-sm transition-colors">
Sign Out Sign Out
</button> </button>
</div> </div>
@@ -354,7 +354,7 @@ export default function AccountPage() {
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`font-body text-sm font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors whitespace-nowrap ${ className={`font-body text-sm font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors whitespace-nowrap ${
activeTab === tab.id activeTab === tab.id
? "border-[#3b82f6] text-[#3b82f6]" ? "border-[#ffb800] text-[#ffb800]"
: "border-transparent text-[#666] hover:text-[#aaa]" : "border-transparent text-[#666] hover:text-[#aaa]"
}`}> }`}>
{tab.label} {tab.label}
@@ -379,7 +379,7 @@ export default function AccountPage() {
value={fullName} value={fullName}
onChange={(e) => setFullName(e.target.value)} onChange={(e) => setFullName(e.target.value)}
placeholder="Your full name" placeholder="Your full name"
className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444]" className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800] transition-colors placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -391,7 +391,7 @@ export default function AccountPage() {
onChange={(e) => setBio(e.target.value)} onChange={(e) => setBio(e.target.value)}
placeholder="Tell us a bit about yourself..." placeholder="Tell us a bit about yourself..."
rows={3} rows={3}
className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444] resize-none" className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800] transition-colors placeholder-[#444] resize-none"
/> />
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -404,7 +404,7 @@ export default function AccountPage() {
value={website} value={website}
onChange={(e) => setWebsite(e.target.value)} onChange={(e) => setWebsite(e.target.value)}
placeholder="https://yoursite.com" placeholder="https://yoursite.com"
className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444]" className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800] transition-colors placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -416,7 +416,7 @@ export default function AccountPage() {
value={location} value={location}
onChange={(e) => setLocation(e.target.value)} onChange={(e) => setLocation(e.target.value)}
placeholder="City, Country" placeholder="City, Country"
className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444]" className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800] transition-colors placeholder-[#444]"
/> />
</div> </div>
</div> </div>
@@ -445,7 +445,7 @@ export default function AccountPage() {
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="bg-[#ffb800] hover:bg-[#d99700] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
{saving ? "Saving..." : "Save Profile"} {saving ? "Saving..." : "Save Profile"}
</button> </button>
</div> </div>
@@ -471,7 +471,7 @@ export default function AccountPage() {
onChange={(e) => setNewPassword(e.target.value)} onChange={(e) => setNewPassword(e.target.value)}
placeholder="Enter new password" placeholder="Enter new password"
minLength={6} minLength={6}
className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444]" className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800] transition-colors placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -484,7 +484,7 @@ export default function AccountPage() {
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Confirm new password" placeholder="Confirm new password"
minLength={6} minLength={6}
className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444]" className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800] transition-colors placeholder-[#444]"
/> />
</div> </div>
</div> </div>
@@ -492,7 +492,7 @@ export default function AccountPage() {
<button <button
type="submit" type="submit"
disabled={changingPassword || !newPassword || !confirmPassword} disabled={changingPassword || !newPassword || !confirmPassword}
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="bg-[#ffb800] hover:bg-[#d99700] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
{changingPassword ? "Updating..." : "Update Password"} {changingPassword ? "Updating..." : "Update Password"}
</button> </button>
</div> </div>
@@ -506,7 +506,7 @@ export default function AccountPage() {
</p> </p>
<button <button
onClick={handleSignOut} onClick={handleSignOut}
className="font-body text-xs font-bold uppercase tracking-wide text-[#cc0000] border border-[#cc0000]/40 hover:border-[#cc0000] hover:bg-[#cc0000]/10 px-5 py-2 rounded-sm transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#d60701] border border-[#d60701]/40 hover:border-[#d60701] hover:bg-[#d60701]/10 px-5 py-2 rounded-sm transition-colors">
Sign Out of All Sessions Sign Out of All Sessions
</button> </button>
</div> </div>
@@ -525,7 +525,7 @@ export default function AccountPage() {
</span> </span>
)} )}
</h2> </h2>
<Link href="/reading-list" className="font-body text-xs font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> <Link href="/reading-list" className="font-body text-xs font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
Full List Full List
</Link> </Link>
</div> </div>
@@ -550,7 +550,7 @@ export default function AccountPage() {
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" /> <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
</svg> </svg>
<p className="font-body text-sm text-[#666]">No saved articles yet.</p> <p className="font-body text-sm text-[#666]">No saved articles yet.</p>
<Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> <Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
Browse News Browse News
</Link> </Link>
</div> </div>
@@ -572,10 +572,10 @@ export default function AccountPage() {
</Link> </Link>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{item.article_category && ( {item.article_category && (
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#3b82f6]">{item.article_category}</span> <span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#ffb800]">{item.article_category}</span>
)} )}
<Link href={`/articles/${item.article_slug}`}> <Link href={`/articles/${item.article_slug}`}>
<h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug mt-0.5"> <h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#ffb800] transition-colors line-clamp-2 leading-snug mt-0.5">
{item.article_title} {item.article_title}
</h3> </h3>
</Link> </Link>
@@ -639,7 +639,7 @@ export default function AccountPage() {
<circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /> <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
</svg> </svg>
<p className="font-body text-sm text-[#666]">No reading history yet.</p> <p className="font-body text-sm text-[#666]">No reading history yet.</p>
<Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> <Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
Browse News Browse News
</Link> </Link>
</div> </div>
@@ -661,10 +661,10 @@ export default function AccountPage() {
</Link> </Link>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{item.article_category && ( {item.article_category && (
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#3b82f6]">{item.article_category}</span> <span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#ffb800]">{item.article_category}</span>
)} )}
<Link href={`/articles/${item.article_slug}`}> <Link href={`/articles/${item.article_slug}`}>
<h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug mt-0.5"> <h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#ffb800] transition-colors line-clamp-2 leading-snug mt-0.5">
{item.article_title} {item.article_title}
</h3> </h3>
</Link> </Link>
@@ -703,7 +703,7 @@ export default function AccountPage() {
</svg> </svg>
<p className="font-body text-sm text-[#666]">No topic preferences tracked yet.</p> <p className="font-body text-sm text-[#666]">No topic preferences tracked yet.</p>
<p className="font-body text-xs text-[#555] mt-1">Read articles to build your interest profile.</p> <p className="font-body text-xs text-[#555] mt-1">Read articles to build your interest profile.</p>
<Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> <Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
Browse News Browse News
</Link> </Link>
</div> </div>
@@ -719,13 +719,13 @@ export default function AccountPage() {
<span className="font-body text-xs font-bold uppercase tracking-wider text-[#e0e0e0] capitalize leading-tight"> <span className="font-body text-xs font-bold uppercase tracking-wider text-[#e0e0e0] capitalize leading-tight">
{t.topic} {t.topic}
</span> </span>
<span className="font-body text-[10px] text-[#3b82f6] font-bold ml-2 flex-shrink-0"> <span className="font-body text-[10px] text-[#ffb800] font-bold ml-2 flex-shrink-0">
{t.view_count}× {t.view_count}×
</span> </span>
</div> </div>
<div className="w-full bg-[#252525] rounded-full h-1"> <div className="w-full bg-[#252525] rounded-full h-1">
<div <div
className="bg-[#3b82f6] h-1 rounded-full transition-all" className="bg-[#ffb800] h-1 rounded-full transition-all"
style={{ width: `${pct}%` }} style={{ width: `${pct}%` }}
/> />
</div> </div>
@@ -750,7 +750,7 @@ export default function AccountPage() {
</h2> </h2>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex flex-col items-center justify-center bg-[#111] border border-[#2a2a2a] rounded-sm px-6 py-4 min-w-[100px]"> <div className="flex flex-col items-center justify-center bg-[#111] border border-[#2a2a2a] rounded-sm px-6 py-4 min-w-[100px]">
<span className="font-heading text-3xl font-bold text-[#3b82f6]"> <span className="font-heading text-3xl font-bold text-[#ffb800]">
{profile?.reputation ?? 0} {profile?.reputation ?? 0}
</span> </span>
<span className="font-body text-xs text-[#666] mt-1 uppercase tracking-wider">Points</span> <span className="font-body text-xs text-[#666] mt-1 uppercase tracking-wider">Points</span>
@@ -759,7 +759,7 @@ export default function AccountPage() {
<p className="font-body text-sm text-[#aaa] leading-relaxed"> <p className="font-body text-sm text-[#aaa] leading-relaxed">
Earn reputation by posting helpful threads and replies in the community forum. Earn reputation by posting helpful threads and replies in the community forum.
</p> </p>
<Link href="/forum" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> <Link href="/forum" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
Go to Forum Go to Forum
</Link> </Link>
</div> </div>
@@ -778,7 +778,7 @@ export default function AccountPage() {
</div> </div>
<Link <Link
href="/home-page" href="/home-page"
className="font-body text-xs font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
Manage Manage
</Link> </Link>
</div> </div>
@@ -789,7 +789,7 @@ export default function AccountPage() {
</div> </div>
<Link <Link
href="/reading-list" href="/reading-list"
className="font-body text-xs font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
View List View List
</Link> </Link>
</div> </div>
@@ -808,8 +808,8 @@ export default function AccountPage() {
onClick={() => handleToggleDigest(!weeklyDigest)} onClick={() => handleToggleDigest(!weeklyDigest)}
aria-pressed={weeklyDigest} aria-pressed={weeklyDigest}
aria-label="Toggle weekly digest email" aria-label="Toggle weekly digest email"
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] disabled:opacity-50 ${ className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] disabled:opacity-50 ${
weeklyDigest ? "bg-[#3b82f6] border-[#3b82f6]" : "bg-[#2a2a2a] border-[#333]" weeklyDigest ? "bg-[#ffb800] border-[#ffb800]" : "bg-[#2a2a2a] border-[#333]"
}`}> }`}>
<span <span
className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow transition duration-200 ease-in-out mt-0.5 ${ className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow transition duration-200 ease-in-out mt-0.5 ${

View File

@@ -91,7 +91,7 @@ export default function ClientsPage() {
setForm({ company_name: '', contact_name: '', contact_email: '', contact_phone: '', billing_address: '', notes: '' }); setForm({ company_name: '', contact_name: '', contact_email: '', contact_phone: '', billing_address: '', notes: '' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -102,7 +102,7 @@ export default function ClientsPage() {
<h1 className="text-xl font-bold">Clients</h1> <h1 className="text-xl font-bold">Clients</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 transition-colors">+ New Client</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 transition-colors">+ New Client</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -116,9 +116,9 @@ export default function ClientsPage() {
{/* Filters */} {/* Filters */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
{['active','inactive','all'].map(s => ( {['active','inactive','all'].map(s => (
<button key={s} onClick={() => setFilter(s)} className={`px-3 py-1 rounded text-xs capitalize ${filter === s ? 'bg-[#3b82f6] text-white' : 'bg-[#111] border border-[#252525] text-[#888] hover:text-white'}`}>{s}</button> <button key={s} onClick={() => setFilter(s)} className={`px-3 py-1 rounded text-xs capitalize ${filter === s ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888] hover:text-white'}`}>{s}</button>
))} ))}
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search clients…" className="ml-auto px-3 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#3b82f6]" /> <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search clients…" className="ml-auto px-3 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#ffb800]" />
</div> </div>
{/* New Client Form */} {/* New Client Form */}
@@ -148,7 +148,7 @@ export default function ClientsPage() {
type={f.key === 'contact_email' ? 'email' : 'text'} type={f.key === 'contact_email' ? 'email' : 'text'}
value={(form as any)[f.key]} value={(form as any)[f.key]}
onChange={e => handleChange(f.key, e.target.value)} onChange={e => handleChange(f.key, e.target.value)}
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors[f.key] ? 'border-red-500/60' : 'border-[#252525]'}`} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors[f.key] ? 'border-red-500/60' : 'border-[#252525]'}`}
/> />
{errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>} {errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>}
</div> </div>
@@ -156,16 +156,16 @@ export default function ClientsPage() {
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Billing Address</label> <label className="text-xs text-[#888] block mb-1">Billing Address</label>
<input value={form.billing_address} onChange={e => handleChange('billing_address', e.target.value)} <input value={form.billing_address} onChange={e => handleChange('billing_address', e.target.value)}
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} <textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2}
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Client'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Client'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -197,7 +197,7 @@ export default function ClientsPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${c.status === 'active' ? 'bg-green-400/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{c.status}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${c.status === 'active' ? 'bg-green-400/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{c.status}</span>
</td> </td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<Link href={`/admin/accounting/clients/${c.id}`} className="text-xs text-[#3b82f6] hover:underline">View</Link> <Link href={`/admin/accounting/clients/${c.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -41,7 +41,7 @@ export default function CommissionsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -84,7 +84,7 @@ export default function CommissionsPage() {
<span>Tier progress</span><span>{pct}%</span> <span>Tier progress</span><span>{pct}%</span>
</div> </div>
<div className="h-1.5 bg-[#1a1a1a] rounded-full overflow-hidden"> <div className="h-1.5 bg-[#1a1a1a] rounded-full overflow-hidden">
<div className="h-full bg-[#3b82f6] rounded-full transition-all" style={{ width: `${pct}%` }} /> <div className="h-full bg-[#ffb800] rounded-full transition-all" style={{ width: `${pct}%` }} />
</div> </div>
</div> </div>
)} )}
@@ -111,7 +111,7 @@ export default function CommissionsPage() {
) : commissions.map((c: any) => ( ) : commissions.map((c: any) => (
<tr key={c.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]"> <tr key={c.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
<td className="px-3 py-2 text-white text-xs">{c.rmp_sales_staff?.full_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{c.rmp_sales_staff?.full_name ?? '—'}</td>
<td className="px-3 py-2 text-[#3b82f6] text-xs font-mono">{c.rmp_invoices?.invoice_number ?? '—'}</td> <td className="px-3 py-2 text-[#ffb800] text-xs font-mono">{c.rmp_invoices?.invoice_number ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{fmt(c.sale_amount_cents ?? 0)}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{fmt(c.sale_amount_cents ?? 0)}</td>
<td className="px-3 py-2 text-[#888] text-xs">{((c.commission_rate ?? 0) * 100).toFixed(1)}%</td> <td className="px-3 py-2 text-[#888] text-xs">{((c.commission_rate ?? 0) * 100).toFixed(1)}%</td>
<td className="px-3 py-2 text-green-400 text-xs">{fmt(c.commission_amount_cents ?? 0)}</td> <td className="px-3 py-2 text-green-400 text-xs">{fmt(c.commission_amount_cents ?? 0)}</td>

View File

@@ -43,7 +43,7 @@ export default function DocumentsPage() {
const years = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i); const years = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -54,14 +54,14 @@ export default function DocumentsPage() {
<h1 className="text-xl font-bold">Document Vault</h1> <h1 className="text-xl font-bold">Document Vault</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ Upload Document</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Upload Document</button>
</div> </div>
{/* Year tabs */} {/* Year tabs */}
<div className="flex gap-1 flex-wrap"> <div className="flex gap-1 flex-wrap">
<button onClick={() => setFilterYear('')} className={`px-3 py-1 rounded text-xs ${!filterYear ? 'bg-[#3b82f6] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>All Years</button> <button onClick={() => setFilterYear('')} className={`px-3 py-1 rounded text-xs ${!filterYear ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>All Years</button>
{years.map(y => ( {years.map(y => (
<button key={y} onClick={() => setFilterYear(String(y))} className={`px-3 py-1 rounded text-xs ${filterYear === String(y) ? 'bg-[#3b82f6] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>{y}</button> <button key={y} onClick={() => setFilterYear(String(y))} className={`px-3 py-1 rounded text-xs ${filterYear === String(y) ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>{y}</button>
))} ))}
</div> </div>
@@ -84,31 +84,31 @@ export default function DocumentsPage() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Title *</label> <label className="text-xs text-[#888] block mb-1">Title *</label>
<input required value={form.title} onChange={e => setForm(p => ({ ...p, title: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input required value={form.title} onChange={e => setForm(p => ({ ...p, title: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
{form.document_type === 'sales_agreement' && ( {form.document_type === 'sales_agreement' && (
<> <>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Person *</label> <label className="text-xs text-[#888] block mb-1">Person *</label>
<input required value={form.person} onChange={e => setForm(p => ({ ...p, person: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input required value={form.person} onChange={e => setForm(p => ({ ...p, person: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Year *</label> <label className="text-xs text-[#888] block mb-1">Year *</label>
<input type="number" required value={form.year} onChange={e => setForm(p => ({ ...p, year: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input type="number" required value={form.year} onChange={e => setForm(p => ({ ...p, year: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</> </>
)} )}
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">File URL</label> <label className="text-xs text-[#888] block mb-1">File URL</label>
<input value={form.file_url} onChange={e => setForm(p => ({ ...p, file_url: e.target.value }))} placeholder="https://…" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.file_url} onChange={e => setForm(p => ({ ...p, file_url: e.target.value }))} placeholder="https://…" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Save Document'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Save Document'}</button>
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -136,7 +136,7 @@ export default function DocumentsPage() {
<td className="px-4 py-2 text-[#888] text-xs">{d.year ?? '—'}</td> <td className="px-4 py-2 text-[#888] text-xs">{d.year ?? '—'}</td>
<td className="px-4 py-2 text-[#555] text-xs">{d.uploaded_at ? new Date(d.uploaded_at).toLocaleDateString() : '—'}</td> <td className="px-4 py-2 text-[#555] text-xs">{d.uploaded_at ? new Date(d.uploaded_at).toLocaleDateString() : '—'}</td>
<td className="px-4 py-2 flex gap-2"> <td className="px-4 py-2 flex gap-2">
{d.file_url && <a href={d.file_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#3b82f6] hover:underline">Download</a>} {d.file_url && <a href={d.file_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#ffb800] hover:underline">Download</a>}
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -84,7 +84,7 @@ export default function ExpensesPage() {
setForm({ vendor: '', amount: '', category: 'other', description: '', expense_date: new Date().toISOString().split('T')[0], receipt_source: 'manual' }); setForm({ vendor: '', amount: '', category: 'other', description: '', expense_date: new Date().toISOString().split('T')[0], receipt_source: 'manual' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -95,7 +95,7 @@ export default function ExpensesPage() {
<h1 className="text-xl font-bold">Expenses</h1> <h1 className="text-xl font-bold">Expenses</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ Log Expense</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Log Expense</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -132,16 +132,16 @@ export default function ExpensesPage() {
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Vendor</label> <label className="text-xs text-[#888] block mb-1">Vendor</label>
<input type="text" value={form.vendor} onChange={e => handleChange('vendor', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input type="text" value={form.vendor} onChange={e => handleChange('vendor', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Amount ($) <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Amount ($) <span className="text-red-400">*</span></label>
<input type="number" step="0.01" value={form.amount} onChange={e => handleChange('amount', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors.amount ? 'border-red-500/60' : 'border-[#252525]'}`} /> <input type="number" step="0.01" value={form.amount} onChange={e => handleChange('amount', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.amount ? 'border-red-500/60' : 'border-[#252525]'}`} />
{errors.amount && <p className="mt-1 text-xs text-red-400">{errors.amount}</p>} {errors.amount && <p className="mt-1 text-xs text-red-400">{errors.amount}</p>}
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Date <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Date <span className="text-red-400">*</span></label>
<input type="date" value={form.expense_date} onChange={e => handleChange('expense_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors.expense_date ? 'border-red-500/60' : 'border-[#252525]'}`} /> <input type="date" value={form.expense_date} onChange={e => handleChange('expense_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.expense_date ? 'border-red-500/60' : 'border-[#252525]'}`} />
{errors.expense_date && <p className="mt-1 text-xs text-red-400">{errors.expense_date}</p>} {errors.expense_date && <p className="mt-1 text-xs text-red-400">{errors.expense_date}</p>}
</div> </div>
<div> <div>
@@ -152,11 +152,11 @@ export default function ExpensesPage() {
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Description</label> <label className="text-xs text-[#888] block mb-1">Description</label>
<input value={form.description} onChange={e => handleChange('description', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.description} onChange={e => handleChange('description', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Log Expense'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Log Expense'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>

View File

@@ -83,7 +83,7 @@ function InvoicesContent() {
setPayForm({ method: 'check', paid_date: new Date().toISOString().split('T')[0], wire_reference: '' }); setPayForm({ method: 'check', paid_date: new Date().toISOString().split('T')[0], wire_reference: '' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -94,7 +94,7 @@ function InvoicesContent() {
<h1 className="text-xl font-bold">Invoices</h1> <h1 className="text-xl font-bold">Invoices</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ New Invoice (via Order)</Link> <Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ New Invoice (via Order)</Link>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -108,7 +108,7 @@ function InvoicesContent() {
{/* Filters */} {/* Filters */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
{['','pending','paid','overdue','cancelled'].map(s => ( {['','pending','paid','overdue','cancelled'].map(s => (
<button key={s} onClick={() => setFilterStatus(s)} className={`px-3 py-1 rounded text-xs capitalize ${filterStatus === s ? 'bg-[#3b82f6] text-white' : 'bg-[#111] border border-[#252525] text-[#888] hover:text-white'}`}>{s || 'All'}</button> <button key={s} onClick={() => setFilterStatus(s)} className={`px-3 py-1 rounded text-xs capitalize ${filterStatus === s ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888] hover:text-white'}`}>{s || 'All'}</button>
))} ))}
</div> </div>
@@ -168,7 +168,7 @@ function InvoicesContent() {
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No invoices found</td></tr> <tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No invoices found</td></tr>
) : invoices.map((inv: any) => ( ) : invoices.map((inv: any) => (
<tr key={inv.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]"> <tr key={inv.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
<td className="px-3 py-2 text-[#3b82f6] font-mono text-xs">{inv.invoice_number}</td> <td className="px-3 py-2 text-[#ffb800] font-mono text-xs">{inv.invoice_number}</td>
<td className="px-3 py-2 text-white text-xs">{inv.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{inv.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs capitalize">{inv.site}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{inv.site}</td>
<td className="px-3 py-2 text-[#888] text-xs font-mono">{inv.rmp_orders?.internal_order_number ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs font-mono">{inv.rmp_orders?.internal_order_number ?? '—'}</td>
@@ -195,7 +195,7 @@ function InvoicesContent() {
export default function InvoicesPage() { export default function InvoicesPage() {
return ( return (
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>}> <Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>}>
<InvoicesContent /> <InvoicesContent />
</Suspense> </Suspense>
); );

View File

@@ -104,7 +104,7 @@ function OrdersContent() {
setForm({ client_id: '', site: '', product_id: '', ad_unit: '', description: '', start_date: '', end_date: '', total_amount_cents: '', currency: 'USD', invoicing_schedule: 'single', next_invoice_date: '', staff_id: '', po_number: '', notes: '' }); setForm({ client_id: '', site: '', product_id: '', ad_unit: '', description: '', start_date: '', end_date: '', total_amount_cents: '', currency: 'USD', invoicing_schedule: 'single', next_invoice_date: '', staff_id: '', po_number: '', notes: '' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -115,7 +115,7 @@ function OrdersContent() {
<h1 className="text-xl font-bold">Orders</h1> <h1 className="text-xl font-bold">Orders</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ New Order</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ New Order</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -136,7 +136,7 @@ function OrdersContent() {
<option value="">All Statuses</option> <option value="">All Statuses</option>
{STATUSES.map(s => <option key={s} value={s}>{s}</option>)} {STATUSES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
<input value={filterClient} onChange={e => setFilterClient(e.target.value)} placeholder="Filter by client…" className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#3b82f6]" /> <input value={filterClient} onChange={e => setFilterClient(e.target.value)} placeholder="Filter by client…" className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#ffb800]" />
</div> </div>
{/* New Order Form */} {/* New Order Form */}
@@ -154,7 +154,7 @@ function OrdersContent() {
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Client <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Client <span className="text-red-400">*</span></label>
<select value={form.client_id} onChange={e => handleChange('client_id', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors.client_id ? 'border-red-500/60' : 'border-[#252525]'}`}> <select value={form.client_id} onChange={e => handleChange('client_id', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.client_id ? 'border-red-500/60' : 'border-[#252525]'}`}>
<option value="">Select client</option> <option value="">Select client</option>
{clients.map((c: any) => <option key={c.id} value={c.id}>{c.company_name}</option>)} {clients.map((c: any) => <option key={c.id} value={c.id}>{c.company_name}</option>)}
</select> </select>
@@ -162,7 +162,7 @@ function OrdersContent() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Site <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Site <span className="text-red-400">*</span></label>
<select value={form.site} onChange={e => handleChange('site', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors.site ? 'border-red-500/60' : 'border-[#252525]'}`}> <select value={form.site} onChange={e => handleChange('site', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.site ? 'border-red-500/60' : 'border-[#252525]'}`}>
<option value="">Select site</option> <option value="">Select site</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
@@ -170,57 +170,57 @@ function OrdersContent() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Product</label> <label className="text-xs text-[#888] block mb-1">Product</label>
<select value={form.product_id} onChange={e => handleChange('product_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]"> <select value={form.product_id} onChange={e => handleChange('product_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]">
<option value="">Select product</option> <option value="">Select product</option>
{products.map((p: any) => <option key={p.id} value={p.id}>{p.name}</option>)} {products.map((p: any) => <option key={p.id} value={p.id}>{p.name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Ad Unit</label> <label className="text-xs text-[#888] block mb-1">Ad Unit</label>
<input value={form.ad_unit} onChange={e => handleChange('ad_unit', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.ad_unit} onChange={e => handleChange('ad_unit', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Start Date</label> <label className="text-xs text-[#888] block mb-1">Start Date</label>
<input type="date" value={form.start_date} onChange={e => handleChange('start_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input type="date" value={form.start_date} onChange={e => handleChange('start_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">End Date</label> <label className="text-xs text-[#888] block mb-1">End Date</label>
<input type="date" value={form.end_date} onChange={e => handleChange('end_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors.end_date ? 'border-red-500/60' : 'border-[#252525]'}`} /> <input type="date" value={form.end_date} onChange={e => handleChange('end_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.end_date ? 'border-red-500/60' : 'border-[#252525]'}`} />
{errors.end_date && <p className="mt-1 text-xs text-red-400">{errors.end_date}</p>} {errors.end_date && <p className="mt-1 text-xs text-red-400">{errors.end_date}</p>}
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Total Amount ($)</label> <label className="text-xs text-[#888] block mb-1">Total Amount ($)</label>
<input type="number" step="0.01" value={form.total_amount_cents} onChange={e => handleChange('total_amount_cents', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors.total_amount_cents ? 'border-red-500/60' : 'border-[#252525]'}`} /> <input type="number" step="0.01" value={form.total_amount_cents} onChange={e => handleChange('total_amount_cents', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors.total_amount_cents ? 'border-red-500/60' : 'border-[#252525]'}`} />
{errors.total_amount_cents && <p className="mt-1 text-xs text-red-400">{errors.total_amount_cents}</p>} {errors.total_amount_cents && <p className="mt-1 text-xs text-red-400">{errors.total_amount_cents}</p>}
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Invoicing Schedule</label> <label className="text-xs text-[#888] block mb-1">Invoicing Schedule</label>
<select value={form.invoicing_schedule} onChange={e => handleChange('invoicing_schedule', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]"> <select value={form.invoicing_schedule} onChange={e => handleChange('invoicing_schedule', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]">
{SCHEDULES.map(s => <option key={s} value={s}>{s}</option>)} {SCHEDULES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Next Invoice Date</label> <label className="text-xs text-[#888] block mb-1">Next Invoice Date</label>
<input type="date" value={form.next_invoice_date} onChange={e => handleChange('next_invoice_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input type="date" value={form.next_invoice_date} onChange={e => handleChange('next_invoice_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Salesperson</label> <label className="text-xs text-[#888] block mb-1">Salesperson</label>
<select value={form.staff_id} onChange={e => handleChange('staff_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]"> <select value={form.staff_id} onChange={e => handleChange('staff_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]">
<option value="">None</option> <option value="">None</option>
{staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)} {staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">PO Number</label> <label className="text-xs text-[#888] block mb-1">PO Number</label>
<input value={form.po_number} onChange={e => handleChange('po_number', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.po_number} onChange={e => handleChange('po_number', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div className="col-span-2 md:col-span-3"> <div className="col-span-2 md:col-span-3">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Order'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Order'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -245,7 +245,7 @@ function OrdersContent() {
<tr key={o.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]"> <tr key={o.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
<td className="px-3 py-2 text-white text-xs">{o.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{o.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{o.po_number ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs">{o.po_number ?? '—'}</td>
<td className="px-3 py-2 text-[#3b82f6] text-xs font-mono">{o.internal_order_number}</td> <td className="px-3 py-2 text-[#ffb800] text-xs font-mono">{o.internal_order_number}</td>
<td className="px-3 py-2 text-[#888] text-xs capitalize">{o.site}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{o.site}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{o.rmp_products?.name ?? o.ad_unit ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{o.rmp_products?.name ?? o.ad_unit ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{o.ad_unit ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs">{o.ad_unit ?? '—'}</td>
@@ -255,7 +255,7 @@ function OrdersContent() {
<span className={`px-2 py-0.5 rounded-full text-xs ${o.status === 'active' ? 'bg-green-400/10 text-green-400' : o.status === 'cancelled' ? 'bg-red-400/10 text-red-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{o.status}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${o.status === 'active' ? 'bg-green-400/10 text-green-400' : o.status === 'cancelled' ? 'bg-red-400/10 text-red-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{o.status}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<Link href={`/admin/accounting/orders/${o.id}`} className="text-xs text-[#3b82f6] hover:underline">View</Link> <Link href={`/admin/accounting/orders/${o.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}
@@ -269,7 +269,7 @@ function OrdersContent() {
export default function OrdersPage() { export default function OrdersPage() {
return ( return (
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>}> <Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>}>
<OrdersContent /> <OrdersContent />
</Suspense> </Suspense>
); );

View File

@@ -44,7 +44,7 @@ export default function AccountingDashboard() {
if (loading || loadingData) return ( if (loading || loadingData) 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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
if (!user) return null; if (!user) return null;
@@ -59,11 +59,11 @@ export default function AccountingDashboard() {
<p className="text-[#888] text-sm mt-1">Relevant Media Properties, LLC</p> <p className="text-[#888] text-sm mt-1">Relevant Media Properties, LLC</p>
</div> </div>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<Link href="/admin/accounting/clients" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ New Client</Link> <Link href="/admin/accounting/clients" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ New Client</Link>
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ New Order</Link> <Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ New Order</Link>
<Link href="/admin/accounting/invoices" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ New Invoice</Link> <Link href="/admin/accounting/invoices" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ New Invoice</Link>
<Link href="/admin/accounting/expenses" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">+ Log Expense</Link> <Link href="/admin/accounting/expenses" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">+ Log Expense</Link>
<Link href="/admin/accounting/reconciliation" className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors">Upload Bank CSV</Link> <Link href="/admin/accounting/reconciliation" className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">Upload Bank CSV</Link>
</div> </div>
</div> </div>
@@ -146,7 +146,7 @@ export default function AccountingDashboard() {
{ label: 'Reports', href: '/admin/accounting/reports' }, { label: 'Reports', href: '/admin/accounting/reports' },
{ label: 'Documents', href: '/admin/accounting/documents' }, { label: 'Documents', href: '/admin/accounting/documents' },
].map(l => ( ].map(l => (
<Link key={l.href} href={l.href} className="bg-[#111] border border-[#252525] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#3b82f6] transition-colors"> <Link key={l.href} href={l.href} className="bg-[#111] border border-[#252525] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#ffb800] transition-colors">
{l.label} {l.label}
</Link> </Link>
))} ))}

View File

@@ -48,7 +48,7 @@ export default function ReconciliationPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -59,7 +59,7 @@ export default function ReconciliationPage() {
<h1 className="text-xl font-bold">Bank Reconciliation</h1> <h1 className="text-xl font-bold">Bank Reconciliation</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<label className={`px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 cursor-pointer ${uploading ? 'opacity-50' : ''}`}> <label className={`px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 cursor-pointer ${uploading ? 'opacity-50' : ''}`}>
{uploading ? 'Uploading…' : 'Upload Bank CSV'} {uploading ? 'Uploading…' : 'Upload Bank CSV'}
<input type="file" accept=".csv" className="hidden" onChange={handleUpload} disabled={uploading} /> <input type="file" accept=".csv" className="hidden" onChange={handleUpload} disabled={uploading} />
</label> </label>

View File

@@ -28,7 +28,7 @@ export default function ReportsPage() {
useEffect(() => { if (user) loadReport(); }, [user]); useEffect(() => { if (user) loadReport(); }, [user]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -43,12 +43,12 @@ export default function ReportsPage() {
<input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white focus:outline-none" /> <input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white focus:outline-none" />
<span className="text-[#555] text-xs">to</span> <span className="text-[#555] text-xs">to</span>
<input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white focus:outline-none" /> <input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white focus:outline-none" />
<button onClick={loadReport} disabled={loadingData} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Run Report</button> <button onClick={loadReport} disabled={loadingData} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Run Report</button>
</div> </div>
</div> </div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div> <div className="flex items-center justify-center py-20"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>
) : report ? ( ) : report ? (
<div className="space-y-4"> <div className="space-y-4">
{/* P&L Summary */} {/* P&L Summary */}

View File

@@ -97,7 +97,7 @@ export default function StaffPage() {
if (errors[errKey]) setErrors(p => { const n = { ...p }; delete n[errKey]; return n; }); if (errors[errKey]) setErrors(p => { const n = { ...p }; delete n[errKey]; return n; });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -108,7 +108,7 @@ export default function StaffPage() {
<h1 className="text-xl font-bold">Sales Staff</h1> <h1 className="text-xl font-bold">Sales Staff</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ Add Salesperson</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Add Salesperson</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -140,7 +140,7 @@ export default function StaffPage() {
type={f.key === 'email' ? 'email' : 'text'} type={f.key === 'email' ? 'email' : 'text'}
value={(form as any)[f.key]} value={(form as any)[f.key]}
onChange={e => handleChange(f.key, e.target.value)} onChange={e => handleChange(f.key, e.target.value)}
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#3b82f6] ${errors[f.key] ? 'border-red-500/60' : 'border-[#252525]'}`} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#ffb800] ${errors[f.key] ? 'border-red-500/60' : 'border-[#252525]'}`}
/> />
{errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>} {errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>}
</div> </div>
@@ -149,7 +149,7 @@ export default function StaffPage() {
<div> <div>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<label className="text-xs text-[#888]">Commission Tiers ({new Date().getFullYear()})</label> <label className="text-xs text-[#888]">Commission Tiers ({new Date().getFullYear()})</label>
<button type="button" onClick={() => setTiers(t => [...t, { tier_order: t.length + 1, threshold_cents: '', rate: '' }])} className="text-xs text-[#3b82f6] hover:underline">+ Add Tier</button> <button type="button" onClick={() => setTiers(t => [...t, { tier_order: t.length + 1, threshold_cents: '', rate: '' }])} className="text-xs text-[#ffb800] hover:underline">+ Add Tier</button>
</div> </div>
{tiers.map((tier, i) => ( {tiers.map((tier, i) => (
<div key={i} className="flex gap-2 mb-2 items-start"> <div key={i} className="flex gap-2 mb-2 items-start">
@@ -167,7 +167,7 @@ export default function StaffPage() {
))} ))}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -197,7 +197,7 @@ export default function StaffPage() {
<td className="px-4 py-2 text-green-400 text-xs">{fmt(s.commission_ytd ?? 0)}</td> <td className="px-4 py-2 text-green-400 text-xs">{fmt(s.commission_ytd ?? 0)}</td>
<td className="px-4 py-2 text-yellow-400 text-xs">{fmt(s.commission_outstanding ?? 0)}</td> <td className="px-4 py-2 text-yellow-400 text-xs">{fmt(s.commission_outstanding ?? 0)}</td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<Link href={`/admin/accounting/staff/${s.id}`} className="text-xs text-[#3b82f6] hover:underline">View</Link> <Link href={`/admin/accounting/staff/${s.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -73,7 +73,7 @@ export default function AdOpsIOsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
const selectedProduct = products.find((p: any) => p.id === form.product_id); const selectedProduct = products.find((p: any) => p.id === form.product_id);
@@ -88,7 +88,7 @@ export default function AdOpsIOsPage() {
<h1 className="text-xl font-bold">Insertion Orders</h1> <h1 className="text-xl font-bold">Insertion Orders</h1>
<Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link> <Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ New IO Extension</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ New IO Extension</button>
</div> </div>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
@@ -108,7 +108,7 @@ export default function AdOpsIOsPage() {
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Existing IO ID *</label> <label className="text-xs text-[#888] block mb-1">Existing IO ID *</label>
<input required value={form.existing_io_id} onChange={e => setForm(p => ({ ...p, existing_io_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input required value={form.existing_io_id} onChange={e => setForm(p => ({ ...p, existing_io_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Client</label> <label className="text-xs text-[#888] block mb-1">Client</label>
@@ -140,11 +140,11 @@ export default function AdOpsIOsPage() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Banner Image URL</label> <label className="text-xs text-[#888] block mb-1">Banner Image URL</label>
<input value={form.banner_image_url} onChange={e => setForm(p => ({ ...p, banner_image_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.banner_image_url} onChange={e => setForm(p => ({ ...p, banner_image_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Click-Through URL</label> <label className="text-xs text-[#888] block mb-1">Click-Through URL</label>
<input value={form.click_through_url} onChange={e => setForm(p => ({ ...p, click_through_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.click_through_url} onChange={e => setForm(p => ({ ...p, click_through_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div className="flex items-center gap-3 pt-4"> <div className="flex items-center gap-3 pt-4">
<label className="flex items-center gap-2 text-xs text-[#888] cursor-pointer"> <label className="flex items-center gap-2 text-xs text-[#888] cursor-pointer">
@@ -159,14 +159,14 @@ export default function AdOpsIOsPage() {
{form.is_comp && ( {form.is_comp && (
<div> <div>
<label className="text-xs text-[#888] block mb-1">Comp Reason</label> <label className="text-xs text-[#888] block mb-1">Comp Reason</label>
<input value={form.comp_reason} onChange={e => setForm(p => ({ ...p, comp_reason: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.comp_reason} onChange={e => setForm(p => ({ ...p, comp_reason: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
)} )}
{isEmailProduct && ( {isEmailProduct && (
<> <>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Email HTML URL</label> <label className="text-xs text-[#888] block mb-1">Email HTML URL</label>
<input value={form.email_html_url} onChange={e => setForm(p => ({ ...p, email_html_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.email_html_url} onChange={e => setForm(p => ({ ...p, email_html_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Scheduled Send Date</label> <label className="text-xs text-[#888] block mb-1">Scheduled Send Date</label>
@@ -174,23 +174,23 @@ export default function AdOpsIOsPage() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">List Segment</label> <label className="text-xs text-[#888] block mb-1">List Segment</label>
<input value={form.email_list_segment} onChange={e => setForm(p => ({ ...p, email_list_segment: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.email_list_segment} onChange={e => setForm(p => ({ ...p, email_list_segment: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</> </>
)} )}
{isAdvertorial && ( {isAdvertorial && (
<div> <div>
<label className="text-xs text-[#888] block mb-1">Page URL (permanent)</label> <label className="text-xs text-[#888] block mb-1">Page URL (permanent)</label>
<input value={form.page_url} onChange={e => setForm(p => ({ ...p, page_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.page_url} onChange={e => setForm(p => ({ ...p, page_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
)} )}
<div className="col-span-2 md:col-span-3"> <div className="col-span-2 md:col-span-3">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create IO Extension'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create IO Extension'}</button>
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -212,7 +212,7 @@ export default function AdOpsIOsPage() {
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No IO extensions found</td></tr> <tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No IO extensions found</td></tr>
) : ios.map((io: any) => ( ) : ios.map((io: any) => (
<tr key={io.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]"> <tr key={io.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
<td className="px-3 py-2 text-[#3b82f6] font-mono text-xs">{io.existing_io_id}</td> <td className="px-3 py-2 text-[#ffb800] font-mono text-xs">{io.existing_io_id}</td>
<td className="px-3 py-2 text-white text-xs">{io.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{io.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{io.rmp_products?.name ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{io.rmp_products?.name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{io.start_date}</td> <td className="px-3 py-2 text-[#888] text-xs">{io.start_date}</td>
@@ -227,7 +227,7 @@ export default function AdOpsIOsPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${io.is_paid ? 'bg-green-400/10 text-green-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{io.is_paid ? 'Paid' : 'Unpaid'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${io.is_paid ? 'bg-green-400/10 text-green-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{io.is_paid ? 'Paid' : 'Unpaid'}</span>
</td> </td>
<td className="px-3 py-2 flex gap-2"> <td className="px-3 py-2 flex gap-2">
<Link href={`/admin/adops/ios/${io.id}`} className="text-xs text-[#3b82f6] hover:underline">View</Link> <Link href={`/admin/adops/ios/${io.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
{io.flight_status === 'expired' && ( {io.flight_status === 'expired' && (
<button onClick={() => { const r = prompt('Reactivation reason?'); if (r) updateStatus(io.id, 'active', r); }} className="text-xs text-green-400 hover:underline">Reactivate</button> <button onClick={() => { const r = prompt('Reactivation reason?'); if (r) updateStatus(io.id, 'active', r); }} className="text-xs text-green-400 hover:underline">Reactivate</button>
)} )}

View File

@@ -22,7 +22,7 @@ export default function AdOpsNotificationsPage() {
useEffect(() => { if (user) load(); }, [user, load]); useEffect(() => { if (user) load(); }, [user, load]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -50,7 +50,7 @@ export default function AdOpsNotificationsPage() {
) : notifications.map((n: any) => ( ) : notifications.map((n: any) => (
<tr key={n.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]"> <tr key={n.id} className="border-b border-[#1a1a1a] hover:bg-[#161616]">
<td className="px-3 py-2 text-[#888] text-xs">{n.notification_type?.replace(/_/g, ' ')}</td> <td className="px-3 py-2 text-[#888] text-xs">{n.notification_type?.replace(/_/g, ' ')}</td>
<td className="px-3 py-2 text-[#3b82f6] font-mono text-xs">{n.io_extension_id?.slice(0, 8) ?? '—'}</td> <td className="px-3 py-2 text-[#ffb800] font-mono text-xs">{n.io_extension_id?.slice(0, 8) ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{n.recipient_email ?? n.recipient_phone ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{n.recipient_email ?? n.recipient_phone ?? '—'}</td>
<td className="px-3 py-2 text-[#555] text-xs">{n.sent_via}</td> <td className="px-3 py-2 text-[#555] text-xs">{n.sent_via}</td>
<td className="px-3 py-2 text-[#555] text-xs">{n.sent_at ? new Date(n.sent_at).toLocaleString() : '—'}</td> <td className="px-3 py-2 text-[#555] text-xs">{n.sent_at ? new Date(n.sent_at).toLocaleString() : '—'}</td>

View File

@@ -43,7 +43,7 @@ export default function AdOpsDashboardPage() {
useEffect(() => { if (user) load(); }, [user, load]); useEffect(() => { if (user) load(); }, [user, load]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -87,7 +87,7 @@ export default function AdOpsDashboardPage() {
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#252525] rounded-lg overflow-x-auto">
<div className="px-4 py-3 border-b border-[#252525] flex items-center justify-between"> <div className="px-4 py-3 border-b border-[#252525] flex items-center justify-between">
<h2 className="text-sm font-semibold">Active Flights</h2> <h2 className="text-sm font-semibold">Active Flights</h2>
<Link href="/admin/adops/ios" className="text-xs text-[#3b82f6] hover:underline">View All IOs </Link> <Link href="/admin/adops/ios" className="text-xs text-[#ffb800] hover:underline">View All IOs </Link>
</div> </div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
@@ -117,7 +117,7 @@ export default function AdOpsDashboardPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${f.is_paid ? 'bg-green-400/10 text-green-400' : f.is_comp ? 'bg-blue-400/10 text-blue-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{f.is_comp ? 'Comp' : f.is_paid ? 'Paid' : 'Unpaid'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${f.is_paid ? 'bg-green-400/10 text-green-400' : f.is_comp ? 'bg-blue-400/10 text-blue-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{f.is_comp ? 'Comp' : f.is_paid ? 'Paid' : 'Unpaid'}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<Link href={`/admin/adops/ios/${f.id}`} className="text-xs text-[#3b82f6] hover:underline">View</Link> <Link href={`/admin/adops/ios/${f.id}`} className="text-xs text-[#ffb800] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}
@@ -151,7 +151,7 @@ export default function AdOpsDashboardPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${e.email_sent_at ? 'bg-green-400/10 text-green-400' : 'bg-blue-400/10 text-blue-400'}`}>{e.email_sent_at ? 'Sent' : 'Pending'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${e.email_sent_at ? 'bg-green-400/10 text-green-400' : 'bg-blue-400/10 text-blue-400'}`}>{e.email_sent_at ? 'Sent' : 'Pending'}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
{e.email_html_url && <a href={e.email_html_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#3b82f6] hover:underline">Preview</a>} {e.email_html_url && <a href={e.email_html_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#ffb800] hover:underline">Preview</a>}
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -49,7 +49,7 @@ export default function AdOpsProductsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -60,7 +60,7 @@ export default function AdOpsProductsPage() {
<h1 className="text-xl font-bold">Product Catalog</h1> <h1 className="text-xl font-bold">Product Catalog</h1>
<Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link> <Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ Add Product</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Add Product</button>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
@@ -90,23 +90,23 @@ export default function AdOpsProductsPage() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Name *</label> <label className="text-xs text-[#888] block mb-1">Name *</label>
<input required value={form.name} onChange={e => setForm(p => ({ ...p, name: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input required value={form.name} onChange={e => setForm(p => ({ ...p, name: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Dimensions</label> <label className="text-xs text-[#888] block mb-1">Dimensions</label>
<input value={form.dimensions} onChange={e => setForm(p => ({ ...p, dimensions: e.target.value }))} placeholder="e.g. 300x250" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.dimensions} onChange={e => setForm(p => ({ ...p, dimensions: e.target.value }))} placeholder="e.g. 300x250" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Duration (days)</label> <label className="text-xs text-[#888] block mb-1">Duration (days)</label>
<input type="number" value={form.duration_days} onChange={e => setForm(p => ({ ...p, duration_days: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input type="number" value={form.duration_days} onChange={e => setForm(p => ({ ...p, duration_days: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div className="col-span-2 md:col-span-3"> <div className="col-span-2 md:col-span-3">
<label className="text-xs text-[#888] block mb-1">Description</label> <label className="text-xs text-[#888] block mb-1">Description</label>
<input value={form.description} onChange={e => setForm(p => ({ ...p, description: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={form.description} onChange={e => setForm(p => ({ ...p, description: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Product'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Product'}</button>
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -137,7 +137,7 @@ export default function AdOpsProductsPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${p.active ? 'bg-green-400/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{p.active ? 'Active' : 'Inactive'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${p.active ? 'bg-green-400/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{p.active ? 'Active' : 'Inactive'}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<button onClick={() => toggleActive(p.id, p.active)} className="text-xs text-[#3b82f6] hover:underline">{p.active ? 'Deactivate' : 'Activate'}</button> <button onClick={() => toggleActive(p.id, p.active)} className="text-xs text-[#ffb800] hover:underline">{p.active ? 'Deactivate' : 'Activate'}</button>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -64,7 +64,7 @@ interface MonthlyTrend {
// ─── Mock / derived data helpers ────────────────────────────────────────────── // ─── Mock / derived data helpers ──────────────────────────────────────────────
const CATEGORY_COLORS = [ const CATEGORY_COLORS = [
'#3b82f6', '#f59e0b', '#10b981', '#8b5cf6', '#ffb800', '#f59e0b', '#10b981', '#8b5cf6',
'#ef4444', '#06b6d4', '#f97316', '#84cc16', '#ef4444', '#06b6d4', '#f97316', '#84cc16',
]; ];
@@ -78,7 +78,7 @@ interface StatCardProps {
icon: React.ReactNode; icon: React.ReactNode;
} }
function StatCard({ label, value, sub, accent = '#3b82f6', icon }: StatCardProps) { function StatCard({ label, value, sub, accent = '#ffb800', icon }: StatCardProps) {
return ( return (
<div className="bg-[#111] border border-[#252525] rounded-lg p-5 flex items-start gap-4"> <div className="bg-[#111] border border-[#252525] rounded-lg p-5 flex items-start gap-4">
<div <div
@@ -252,7 +252,7 @@ export default function AdminAnalyticsPage() {
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="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading analytics</p> <p className="text-[#555] text-sm font-body">Loading analytics</p>
</div> </div>
</div> </div>
@@ -275,20 +275,20 @@ export default function AdminAnalyticsPage() {
</p> </p>
</div> </div>
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<Link href="/admin/articles" className="text-[#555] hover:text-[#3b82f6] text-xs font-body uppercase tracking-wider transition-colors"> <Link href="/admin/articles" className="text-[#555] hover:text-[#ffb800] text-xs font-body uppercase tracking-wider transition-colors">
Articles Articles
</Link> </Link>
<span className="text-[#333]">·</span> <span className="text-[#333]">·</span>
<Link href="/admin/users" className="text-[#555] hover:text-[#3b82f6] text-xs font-body uppercase tracking-wider transition-colors"> <Link href="/admin/users" className="text-[#555] hover:text-[#ffb800] text-xs font-body uppercase tracking-wider transition-colors">
Users Users
</Link> </Link>
<span className="text-[#333]">·</span> <span className="text-[#333]">·</span>
<Link href="/admin/import" className="text-[#555] hover:text-[#3b82f6] text-xs font-body uppercase tracking-wider transition-colors"> <Link href="/admin/import" className="text-[#555] hover:text-[#ffb800] text-xs font-body uppercase tracking-wider transition-colors">
WP Import WP Import
</Link> </Link>
<button <button
onClick={fetchAnalytics} onClick={fetchAnalytics}
className="ml-2 px-3 py-1.5 bg-[#1a1a1a] border border-[#333] hover:border-[#3b82f6] text-[#888] hover:text-[#3b82f6] text-xs font-body rounded transition-colors" className="ml-2 px-3 py-1.5 bg-[#1a1a1a] border border-[#333] hover:border-[#ffb800] text-[#888] hover:text-[#ffb800] text-xs font-body rounded transition-colors"
> >
Refresh Refresh
</button> </button>
@@ -312,7 +312,7 @@ export default function AdminAnalyticsPage() {
label="Total Articles" label="Total Articles"
value={metrics?.totalArticles ?? 0} value={metrics?.totalArticles ?? 0}
icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>} icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>}
accent="#3b82f6" accent="#ffb800"
/> />
<StatCard <StatCard
label="Published" label="Published"
@@ -361,7 +361,7 @@ export default function AdminAnalyticsPage() {
value={`${publishRate}%`} value={`${publishRate}%`}
sub="Published / total" sub="Published / total"
icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>} icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>}
accent="#3b82f6" accent="#ffb800"
/> />
</div> </div>
</section> </section>

View File

@@ -384,7 +384,7 @@ export default function AdminArticlesPage() {
if (loading || (!user && !loading)) { if (loading || (!user && !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-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -409,7 +409,7 @@ export default function AdminArticlesPage() {
<div className="bg-[#111111] border-b border-[#252525]"> <div className="bg-[#111111] border-b border-[#252525]">
<div className="max-w-7xl mx-auto px-4 py-5"> <div className="max-w-7xl mx-auto px-4 py-5">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<Link href="/home-page" className="text-[#555] hover:text-[#3b82f6] transition-colors text-sm">Home</Link> <Link href="/home-page" className="text-[#555] hover:text-[#ffb800] transition-colors text-sm">Home</Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
<span className="text-[#888] text-sm">Admin</span> <span className="text-[#888] text-sm">Admin</span>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
@@ -423,13 +423,13 @@ export default function AdminArticlesPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => setShowNewArticle(true)} onClick={() => setShowNewArticle(true)}
className="flex items-center gap-1.5 text-xs font-bold text-white bg-[#3b82f6] hover:bg-[#2563eb] rounded px-3 py-2 transition-colors"> className="flex items-center gap-1.5 text-xs font-bold text-white bg-[#ffb800] hover:bg-[#d99700] rounded px-3 py-2 transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
New Article New Article
</button> </button>
<Link <Link
href="/admin/import" href="/admin/import"
className="flex items-center gap-1.5 text-xs text-[#666] hover:text-[#3b82f6] border border-[#252525] hover:border-[#3b82f6] rounded px-3 py-2 transition-colors"> className="flex items-center gap-1.5 text-xs text-[#666] hover:text-[#ffb800] border border-[#252525] hover:border-[#ffb800] rounded px-3 py-2 transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>
WP Import WP Import
</Link> </Link>
@@ -512,13 +512,13 @@ export default function AdminArticlesPage() {
placeholder="Search articles..." placeholder="Search articles..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#252525] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="w-full bg-[#111] border border-[#252525] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
/> />
</div> </div>
<select <select
value={sourceFilter} value={sourceFilter}
onChange={(e) => setSourceFilter(e.target.value as SourceFilter)} onChange={(e) => setSourceFilter(e.target.value as SourceFilter)}
className="bg-[#111] border border-[#252525] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6]"> className="bg-[#111] border border-[#252525] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800]">
<option value="all">All Sources</option> <option value="all">All Sources</option>
<option value="imported">Imported (WP)</option> <option value="imported">Imported (WP)</option>
<option value="native">Native</option> <option value="native">Native</option>
@@ -527,13 +527,13 @@ export default function AdminArticlesPage() {
{/* Bulk Actions Bar */} {/* Bulk Actions Bar */}
{selectedIds.size > 0 && ( {selectedIds.size > 0 && (
<div className="flex items-center gap-3 bg-[#0d1a2e] border border-[#1e3a5f] rounded-lg px-4 py-3"> <div className="flex items-center gap-3 bg-[#0d1a2e] border border-[#4a3500] rounded-lg px-4 py-3">
<span className="text-[#3b82f6] text-sm font-bold">{selectedIds.size} selected</span> <span className="text-[#ffb800] text-sm font-bold">{selectedIds.size} selected</span>
<div className="flex-1" /> <div className="flex-1" />
<select <select
value={bulkAction} value={bulkAction}
onChange={(e) => setBulkAction(e.target.value as BulkAction | '')} onChange={(e) => setBulkAction(e.target.value as BulkAction | '')}
className="bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-1.5 focus:outline-none focus:border-[#3b82f6]"> className="bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-1.5 focus:outline-none focus:border-[#ffb800]">
<option value="">Bulk Action</option> <option value="">Bulk Action</option>
<option value="publish">Publish</option> <option value="publish">Publish</option>
<option value="pending">Submit for Review</option> <option value="pending">Submit for Review</option>
@@ -544,7 +544,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={handleBulkAction} onClick={handleBulkAction}
disabled={!bulkAction || bulkProcessing} disabled={!bulkAction || bulkProcessing}
className="flex items-center gap-1.5 bg-[#3b82f6] hover:bg-[#2563eb] disabled:bg-[#1e3a5f] disabled:cursor-not-allowed text-white text-sm font-bold px-4 py-1.5 rounded transition-colors"> className="flex items-center gap-1.5 bg-[#ffb800] hover:bg-[#d99700] disabled:bg-[#4a3500] disabled:cursor-not-allowed text-white text-sm font-bold px-4 py-1.5 rounded transition-colors">
{bulkProcessing ? ( {bulkProcessing ? (
<div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : null} ) : null}
@@ -587,9 +587,9 @@ export default function AdminArticlesPage() {
<div className="flex items-center gap-3 mt-1"> <div className="flex items-center gap-3 mt-1">
<span className="text-[11px] text-[#555]">{article.author_name || 'Unknown author'}</span> <span className="text-[11px] text-[#555]">{article.author_name || 'Unknown author'}</span>
{article.category && ( {article.category && (
<span className="text-[10px] font-bold uppercase tracking-wider text-[#3b82f6] bg-[#0d1a2e] px-1.5 py-0.5 rounded">{article.category}</span> <span className="text-[10px] font-bold uppercase tracking-wider text-[#ffb800] bg-[#0d1a2e] px-1.5 py-0.5 rounded">{article.category}</span>
)} )}
<span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#3b82f6] bg-[#3b82f6]/10'}`}> <span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#ffb800] bg-[#ffb800]/10'}`}>
{article.source === 'imported' ? 'WP' : 'Native'} {article.source === 'imported' ? 'WP' : 'Native'}
</span> </span>
</div> </div>
@@ -612,7 +612,7 @@ export default function AdminArticlesPage() {
</button> </button>
<button <button
onClick={() => openEdit(article)} onClick={() => openEdit(article)}
className="p-1.5 text-[#555] hover:text-[#3b82f6] transition-colors rounded hover:bg-[#1a1a1a]" className="p-1.5 text-[#555] hover:text-[#ffb800] transition-colors rounded hover:bg-[#1a1a1a]"
title="Edit"> title="Edit">
<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="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /> <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
@@ -634,7 +634,7 @@ export default function AdminArticlesPage() {
type="checkbox" type="checkbox"
checked={articles.length > 0 && selectedIds.size === articles.length} checked={articles.length > 0 && selectedIds.size === articles.length}
onChange={toggleSelectAll} onChange={toggleSelectAll}
className="w-4 h-4 rounded border-[#333] bg-[#1a1a1a] accent-[#3b82f6] cursor-pointer" className="w-4 h-4 rounded border-[#333] bg-[#1a1a1a] accent-[#ffb800] cursor-pointer"
/> />
<span className="text-[10px] text-[#555] uppercase tracking-wider font-bold flex-1">Article</span> <span className="text-[10px] text-[#555] uppercase tracking-wider font-bold flex-1">Article</span>
<span className="text-[10px] text-[#555] uppercase tracking-wider font-bold w-24 hidden md:block">Category</span> <span className="text-[10px] text-[#555] uppercase tracking-wider font-bold w-24 hidden md:block">Category</span>
@@ -646,7 +646,7 @@ export default function AdminArticlesPage() {
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : articles.length === 0 ? ( ) : articles.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
@@ -668,7 +668,7 @@ export default function AdminArticlesPage() {
type="checkbox" type="checkbox"
checked={selectedIds.has(article.id)} checked={selectedIds.has(article.id)}
onChange={() => toggleSelect(article.id)} onChange={() => toggleSelect(article.id)}
className="w-4 h-4 rounded border-[#333] bg-[#1a1a1a] accent-[#3b82f6] cursor-pointer flex-shrink-0" className="w-4 h-4 rounded border-[#333] bg-[#1a1a1a] accent-[#ffb800] cursor-pointer flex-shrink-0"
/> />
{/* Thumbnail + Title */} {/* Thumbnail + Title */}
@@ -699,7 +699,7 @@ export default function AdminArticlesPage() {
{/* Category */} {/* Category */}
<div className="w-24 hidden md:block"> <div className="w-24 hidden md:block">
{article.category ? ( {article.category ? (
<span className="text-[10px] font-bold uppercase tracking-wider text-[#3b82f6] bg-[#0d1a2e] px-1.5 py-0.5 rounded"> <span className="text-[10px] font-bold uppercase tracking-wider text-[#ffb800] bg-[#0d1a2e] px-1.5 py-0.5 rounded">
{article.category} {article.category}
</span> </span>
) : ( ) : (
@@ -710,7 +710,7 @@ export default function AdminArticlesPage() {
{/* Source */} {/* Source */}
<div className="w-20 hidden lg:block"> <div className="w-20 hidden lg:block">
<span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${ <span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${
article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#3b82f6] bg-[#3b82f6]/10' article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#ffb800] bg-[#ffb800]/10'
}`}> }`}>
{article.source === 'imported' ? 'WP' : 'Native'} {article.source === 'imported' ? 'WP' : 'Native'}
</span> </span>
@@ -741,7 +741,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => openEdit(article)} onClick={() => openEdit(article)}
title="Edit" title="Edit"
className="p-1.5 text-[#555] hover:text-[#3b82f6] transition-colors rounded hover:bg-[#1a1a1a]"> className="p-1.5 text-[#555] hover:text-[#ffb800] transition-colors rounded hover:bg-[#1a1a1a]">
<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="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /> <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" /> <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
@@ -839,7 +839,7 @@ export default function AdminArticlesPage() {
<div className="flex items-center justify-between px-6 py-4 border-b border-[#252525] sticky top-0 bg-[#111] z-10 rounded-t-xl"> <div className="flex items-center justify-between px-6 py-4 border-b border-[#252525] sticky top-0 bg-[#111] z-10 rounded-t-xl">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<h2 className="text-base font-bold text-white font-heading">New Article</h2> <h2 className="text-base font-bold text-white font-heading">New Article</h2>
<span className="text-[10px] font-bold uppercase tracking-wider text-[#3b82f6] bg-[#3b82f6]/10 px-1.5 py-0.5 rounded">Native</span> <span className="text-[10px] font-bold uppercase tracking-wider text-[#ffb800] bg-[#ffb800]/10 px-1.5 py-0.5 rounded">Native</span>
</div> </div>
<button <button
onClick={() => setShowNewArticle(false)} onClick={() => setShowNewArticle(false)}
@@ -859,7 +859,7 @@ export default function AdminArticlesPage() {
value={newTitle} value={newTitle}
onChange={(e) => setNewTitle(e.target.value)} onChange={(e) => setNewTitle(e.target.value)}
placeholder="Enter article title..." placeholder="Enter article title..."
className="w-full bg-[#1a1a1a] border border-[#333] text-white text-lg font-bold rounded px-4 py-3 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="w-full bg-[#1a1a1a] border border-[#333] text-white text-lg font-bold rounded px-4 py-3 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
/> />
</div> </div>
@@ -871,7 +871,7 @@ export default function AdminArticlesPage() {
onChange={(e) => setNewExcerpt(e.target.value)} onChange={(e) => setNewExcerpt(e.target.value)}
placeholder="Brief summary shown in article listings..." placeholder="Brief summary shown in article listings..."
rows={2} rows={2}
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2.5 focus:outline-none focus:border-[#3b82f6] placeholder-[#444] resize-none" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2.5 focus:outline-none focus:border-[#ffb800] placeholder-[#444] resize-none"
/> />
</div> </div>
@@ -894,7 +894,7 @@ export default function AdminArticlesPage() {
value={newAuthor} value={newAuthor}
onChange={(e) => setNewAuthor(e.target.value)} onChange={(e) => setNewAuthor(e.target.value)}
placeholder="Author name" placeholder="Author name"
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -904,7 +904,7 @@ export default function AdminArticlesPage() {
value={newCategory} value={newCategory}
onChange={(e) => setNewCategory(e.target.value)} onChange={(e) => setNewCategory(e.target.value)}
placeholder="e.g. Technology" placeholder="e.g. Technology"
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -912,7 +912,7 @@ export default function AdminArticlesPage() {
<select <select
value={newStatus} value={newStatus}
onChange={(e) => setNewStatus(e.target.value as 'draft' | 'pending' | 'published')} onChange={(e) => setNewStatus(e.target.value as 'draft' | 'pending' | 'published')}
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6]"> className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800]">
<option value="draft">Draft</option> <option value="draft">Draft</option>
<option value="pending">Submit for Review</option> <option value="pending">Submit for Review</option>
<option value="published">Publish Now</option> <option value="published">Publish Now</option>
@@ -929,7 +929,7 @@ export default function AdminArticlesPage() {
value={newFeaturedImage} value={newFeaturedImage}
onChange={(e) => setNewFeaturedImage(e.target.value)} onChange={(e) => setNewFeaturedImage(e.target.value)}
placeholder="https://..." placeholder="https://..."
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -939,7 +939,7 @@ export default function AdminArticlesPage() {
value={newFeaturedImageAlt} value={newFeaturedImageAlt}
onChange={(e) => setNewFeaturedImageAlt(e.target.value)} onChange={(e) => setNewFeaturedImageAlt(e.target.value)}
placeholder="Describe the image..." placeholder="Describe the image..."
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
/> />
</div> </div>
</div> </div>
@@ -966,7 +966,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={handleNewArticleSave} onClick={handleNewArticleSave}
disabled={newSaving || !newTitle.trim()} disabled={newSaving || !newTitle.trim()}
className="flex items-center gap-2 bg-[#3b82f6] hover:bg-[#2563eb] disabled:bg-[#1e3a5f] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors"> className="flex items-center gap-2 bg-[#ffb800] hover:bg-[#d99700] disabled:bg-[#4a3500] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors">
{newSaving ? ( {newSaving ? (
<div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : ( ) : (
@@ -1002,7 +1002,7 @@ export default function AdminArticlesPage() {
type="text" type="text"
value={editTitle} value={editTitle}
onChange={(e) => setEditTitle(e.target.value)} onChange={(e) => setEditTitle(e.target.value)}
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
@@ -1012,7 +1012,7 @@ export default function AdminArticlesPage() {
type="text" type="text"
value={editAuthor} value={editAuthor}
onChange={(e) => setEditAuthor(e.target.value)} onChange={(e) => setEditAuthor(e.target.value)}
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<div> <div>
@@ -1021,7 +1021,7 @@ export default function AdminArticlesPage() {
type="text" type="text"
value={editCategory} value={editCategory}
onChange={(e) => setEditCategory(e.target.value)} onChange={(e) => setEditCategory(e.target.value)}
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
</div> </div>
@@ -1030,7 +1030,7 @@ export default function AdminArticlesPage() {
<select <select
value={editStatus} value={editStatus}
onChange={(e) => setEditStatus(e.target.value)} onChange={(e) => setEditStatus(e.target.value)}
className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6]"> className="w-full bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800]">
<option value="draft">Draft</option> <option value="draft">Draft</option>
<option value="pending">Pending Review</option> <option value="pending">Pending Review</option>
<option value="published">Published</option> <option value="published">Published</option>
@@ -1040,19 +1040,19 @@ export default function AdminArticlesPage() {
{/* Scheduling — only shown for draft status */} {/* Scheduling — only shown for draft status */}
{editStatus === 'draft' && ( {editStatus === 'draft' && (
<div className="bg-[#0d1a2e] border border-[#1e3a5f] rounded-lg p-4"> <div className="bg-[#0d1a2e] border border-[#4a3500] rounded-lg p-4">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#3b82f6]"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#ffb800]">
<circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /> <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
</svg> </svg>
<label className="text-xs text-[#3b82f6] font-bold uppercase tracking-wider">Schedule Publish</label> <label className="text-xs text-[#ffb800] font-bold uppercase tracking-wider">Schedule Publish</label>
</div> </div>
<input <input
type="datetime-local" type="datetime-local"
value={editScheduledAt} value={editScheduledAt}
onChange={(e) => setEditScheduledAt(e.target.value)} onChange={(e) => setEditScheduledAt(e.target.value)}
min={new Date().toISOString().slice(0, 16)} min={new Date().toISOString().slice(0, 16)}
className="w-full bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] [color-scheme:dark]" className="w-full bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800] [color-scheme:dark]"
/> />
{editScheduledAt && ( {editScheduledAt && (
<div className="flex items-center justify-between mt-2"> <div className="flex items-center justify-between mt-2">
@@ -1074,7 +1074,7 @@ export default function AdminArticlesPage() {
<div className="flex items-center gap-2 pt-1"> <div className="flex items-center gap-2 pt-1">
<span className="text-xs text-[#555]">Source:</span> <span className="text-xs text-[#555]">Source:</span>
<span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${ <span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${
editingArticle.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#3b82f6] bg-[#3b82f6]/10' editingArticle.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#ffb800] bg-[#ffb800]/10'
}`}> }`}>
{editingArticle.source === 'imported' ? 'WordPress Import' : 'Native'} {editingArticle.source === 'imported' ? 'WordPress Import' : 'Native'}
</span> </span>
@@ -1090,7 +1090,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={handleEditSave} onClick={handleEditSave}
disabled={editSaving || !editTitle.trim()} disabled={editSaving || !editTitle.trim()}
className="flex items-center gap-2 bg-[#3b82f6] hover:bg-[#2563eb] disabled:bg-[#1e3a5f] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors"> className="flex items-center gap-2 bg-[#ffb800] hover:bg-[#d99700] disabled:bg-[#4a3500] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors">
{editSaving ? ( {editSaving ? (
<div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : null} ) : null}

View File

@@ -340,7 +340,7 @@ export default function AuditLogPage() {
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-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -382,7 +382,7 @@ export default function AuditLogPage() {
<StatCard <StatCard
label="Total Events" label="Total Events"
value={logs.length} value={logs.length}
accent="bg-[#3b82f6]/10 text-[#3b82f6]" accent="bg-[#ffb800]/10 text-[#ffb800]"
icon={ icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
@@ -433,7 +433,7 @@ export default function AuditLogPage() {
placeholder="Search by user, item, or action…" placeholder="Search by user, item, or action…"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#252525] rounded-lg pl-9 pr-4 py-2.5 text-sm text-white placeholder-[#444] focus:outline-none focus:border-[#3b82f6] font-body" className="w-full bg-[#111] border border-[#252525] rounded-lg pl-9 pr-4 py-2.5 text-sm text-white placeholder-[#444] focus:outline-none focus:border-[#ffb800] font-body"
/> />
</div> </div>
@@ -445,7 +445,7 @@ export default function AuditLogPage() {
onClick={() => setActionFilter(f)} onClick={() => setActionFilter(f)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-all ${ className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-all ${
actionFilter === f actionFilter === f
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#555] hover:text-[#888]' : 'text-[#555] hover:text-[#888]'
}`} }`}
> >
@@ -480,7 +480,7 @@ export default function AuditLogPage() {
{loadingData && ( {loadingData && (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading audit logs</p> <p className="text-[#555] text-sm font-body">Loading audit logs</p>
</div> </div>
</div> </div>
@@ -496,7 +496,7 @@ export default function AuditLogPage() {
</div> </div>
<p className="text-[#555] text-sm font-body">No audit log entries found</p> <p className="text-[#555] text-sm font-body">No audit log entries found</p>
{searchQuery && ( {searchQuery && (
<button onClick={() => setSearchQuery('')} className="text-[#3b82f6] text-xs font-body hover:underline"> <button onClick={() => setSearchQuery('')} className="text-[#ffb800] text-xs font-body hover:underline">
Clear search Clear search
</button> </button>
)} )}
@@ -569,7 +569,7 @@ export default function AuditLogPage() {
<div className="flex items-center"> <div className="flex items-center">
<button <button
onClick={(e) => { e.stopPropagation(); setSelectedLog(log); }} onClick={(e) => { e.stopPropagation(); setSelectedLog(log); }}
className="text-[#444] hover:text-[#3b82f6] transition-colors p-1" className="text-[#444] hover:text-[#ffb800] transition-colors p-1"
aria-label="View details" aria-label="View details"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">

View File

@@ -162,7 +162,7 @@ export default function BannedTermsPage() {
if (loading || loadingData) { if (loading || loadingData) {
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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -200,7 +200,7 @@ export default function BannedTermsPage() {
Scan Published Posts Scan Published Posts
</button> </button>
<button onClick={() => { setForm(EMPTY_FORM); setEditingId(null); setShowAddForm(true); }} <button onClick={() => { setForm(EMPTY_FORM); setEditingId(null); setShowAddForm(true); }}
className="flex items-center gap-2 px-4 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors"> className="flex items-center gap-2 px-4 py-2 bg-[#ffb800] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg>
Add Term Add Term
</button> </button>
@@ -240,12 +240,12 @@ export default function BannedTermsPage() {
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-xs font-body text-[#888] mb-1.5">Term / Company Name *</label> <label className="block text-xs font-body text-[#888] mb-1.5">Term / Company Name *</label>
<input type="text" value={form.term} onChange={e => setForm(f => ({ ...f, term: e.target.value }))} placeholder="e.g. Sony, Acme Corp" <input type="text" value={form.term} onChange={e => setForm(f => ({ ...f, term: e.target.value }))} placeholder="e.g. Sony, Acme Corp"
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]" required /> 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-[#ffb800]" required />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Ban Level</label> <label className="block text-xs font-body text-[#888] mb-1.5">Ban Level</label>
<select value={form.ban_level} onChange={e => setForm(f => ({ ...f, ban_level: e.target.value as 'soft' | 'hard' }))} <select value={form.ban_level} onChange={e => setForm(f => ({ ...f, ban_level: e.target.value as 'soft' | 'hard' }))}
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-[#ffb800]">
<option value="soft">Soft Ban Flag for review (contributor doesn't know)</option> <option value="soft">Soft Ban Flag for review (contributor doesn't know)</option>
<option value="hard">Hard Ban — Block submission entirely</option> <option value="hard">Hard Ban — Block submission entirely</option>
</select> </select>
@@ -253,7 +253,7 @@ export default function BannedTermsPage() {
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label> <label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label>
<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-[#ffb800]">
<option value="both">Both Sites</option> <option value="both">Both Sites</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
@@ -262,11 +262,11 @@ export default function BannedTermsPage() {
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-xs font-body text-[#888] mb-1.5">Internal Notes (not shown to contributors)</label> <label className="block text-xs font-body text-[#888] mb-1.5">Internal Notes (not shown to contributors)</label>
<textarea value={form.notes} onChange={e => setForm(f => ({ ...f, notes: e.target.value }))} rows={2} placeholder="Reason for ban, context..." <textarea value={form.notes} onChange={e => setForm(f => ({ ...f, notes: e.target.value }))} rows={2} placeholder="Reason for ban, context..."
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] resize-none" /> 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-[#ffb800] resize-none" />
</div> </div>
<div className="md:col-span-2 flex gap-3"> <div className="md:col-span-2 flex gap-3">
<button type="submit" disabled={saving} <button type="submit" disabled={saving}
className="px-5 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50"> className="px-5 py-2 bg-[#ffb800] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50">
{saving ? 'Saving...' : editingId ? 'Update Term' : 'Add Term'} {saving ? 'Saving...' : editingId ? 'Update Term' : 'Add Term'}
</button> </button>
<button type="button" onClick={() => { setShowAddForm(false); setEditingId(null); setForm(EMPTY_FORM); }} <button type="button" onClick={() => { setShowAddForm(false); setEditingId(null); setForm(EMPTY_FORM); }}
@@ -281,22 +281,22 @@ export default function BannedTermsPage() {
{/* Filters */} {/* Filters */}
<div className="mb-4 flex flex-wrap gap-3"> <div className="mb-4 flex flex-wrap gap-3">
<input type="text" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} placeholder="Search terms..." <input type="text" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} placeholder="Search terms..."
className="bg-[#111] border border-[#252525] rounded-lg px-3 py-2 text-white text-sm font-body focus:outline-none focus:border-[#3b82f6] w-48" /> className="bg-[#111] border border-[#252525] rounded-lg px-3 py-2 text-white text-sm font-body focus:outline-none focus:border-[#ffb800] w-48" />
<select value={filterBanLevel} onChange={e => setFilterBanLevel(e.target.value)} <select value={filterBanLevel} onChange={e => setFilterBanLevel(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-[#ffb800]">
<option value="">All Ban Levels</option> <option value="">All Ban Levels</option>
<option value="soft">Soft Ban</option> <option value="soft">Soft Ban</option>
<option value="hard">Hard Ban</option> <option value="hard">Hard Ban</option>
</select> </select>
<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-[#ffb800]">
<option value="">All Sites</option> <option value="">All Sites</option>
<option value="avbeat">AV 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>
<select value={filterActive} onChange={e => setFilterActive(e.target.value)} <select value={filterActive} onChange={e => setFilterActive(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-[#ffb800]">
<option value="">All Status</option> <option value="">All Status</option>
<option value="true">Active</option> <option value="true">Active</option>
<option value="false">Inactive</option> <option value="false">Inactive</option>
@@ -348,7 +348,7 @@ export default function BannedTermsPage() {
<td className="px-4 py-3 text-xs text-[#555] font-body max-w-xs truncate">{term.notes || ''}</td> <td className="px-4 py-3 text-xs text-[#555] font-body max-w-xs truncate">{term.notes || ''}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button onClick={() => handleEdit(term)} className="text-xs text-[#3b82f6] hover:text-blue-300 font-body transition-colors">Edit</button> <button onClick={() => handleEdit(term)} className="text-xs text-[#ffb800] hover:text-blue-300 font-body transition-colors">Edit</button>
<button onClick={() => handleDeactivate(term.id, term.is_active)} disabled={actionLoading === term.id} <button onClick={() => handleDeactivate(term.id, term.is_active)} disabled={actionLoading === term.id}
className="text-xs text-[#888] hover:text-white font-body transition-colors disabled:opacity-50"> className="text-xs text-[#888] hover:text-white font-body transition-colors disabled:opacity-50">
{term.is_active ? 'Deactivate' : 'Reactivate'} {term.is_active ? 'Deactivate' : 'Reactivate'}

View File

@@ -54,7 +54,7 @@ function StatBlock({ label, b }: { label: string; b: StatBucket }) {
<span className="font-semibold">{fmtNum(b.clicks)}</span> <span className="font-semibold">{fmtNum(b.clicks)}</span>
<span className="text-[#6b7280]"> clk</span> <span className="text-[#6b7280]"> clk</span>
<span className="mx-1 text-[#374151]">·</span> <span className="mx-1 text-[#374151]">·</span>
<span className="text-[#60a5fa]">{pct(b.clicks, b.impressions)}</span> <span className="text-[#ffd25a]">{pct(b.clicks, b.impressions)}</span>
</div> </div>
</div> </div>
); );
@@ -140,7 +140,7 @@ export default function BannerRows({
<input <input
value={current.name} value={current.name}
onChange={(e) => field(b.id, "name", e.target.value)} onChange={(e) => field(b.id, "name", e.target.value)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#3b82f6] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#ffb800] focus:outline-none"
/> />
</label> </label>
@@ -150,7 +150,7 @@ export default function BannerRows({
value={current.click_url || ""} value={current.click_url || ""}
onChange={(e) => field(b.id, "click_url", e.target.value)} onChange={(e) => field(b.id, "click_url", e.target.value)}
placeholder="https://…" placeholder="https://…"
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#3b82f6] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#ffb800] focus:outline-none"
/> />
</label> </label>
@@ -160,7 +160,7 @@ export default function BannerRows({
type="date" type="date"
value={fmtDate(current.start_date)} value={fmtDate(current.start_date)}
onChange={(e) => field(b.id, "start_date", e.target.value ? new Date(e.target.value).toISOString() : null)} onChange={(e) => field(b.id, "start_date", e.target.value ? new Date(e.target.value).toISOString() : null)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#3b82f6] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#ffb800] focus:outline-none"
/> />
</label> </label>
@@ -170,7 +170,7 @@ export default function BannerRows({
type="date" type="date"
value={fmtDate(current.end_date)} value={fmtDate(current.end_date)}
onChange={(e) => field(b.id, "end_date", e.target.value ? new Date(e.target.value).toISOString() : null)} onChange={(e) => field(b.id, "end_date", e.target.value ? new Date(e.target.value).toISOString() : null)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#3b82f6] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#ffb800] focus:outline-none"
/> />
</label> </label>
@@ -179,7 +179,7 @@ export default function BannerRows({
<select <select
value={current.status} value={current.status}
onChange={(e) => field(b.id, "status", e.target.value)} onChange={(e) => field(b.id, "status", e.target.value)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#3b82f6] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#ffb800] focus:outline-none"
> >
<option value="active">active</option> <option value="active">active</option>
<option value="scheduled">scheduled</option> <option value="scheduled">scheduled</option>
@@ -210,7 +210,7 @@ export default function BannerRows({
<button <button
onClick={() => save(b.id)} onClick={() => save(b.id)}
disabled={!dirty || pending} disabled={!dirty || pending}
className="rounded bg-[#1d4ed8] px-3 py-1.5 text-sm font-medium text-white hover:bg-[#2563eb] disabled:opacity-40 disabled:cursor-not-allowed" className="rounded bg-[#1d4ed8] px-3 py-1.5 text-sm font-medium text-white hover:bg-[#d99700] disabled:opacity-40 disabled:cursor-not-allowed"
> >
Save changes Save changes
</button> </button>
@@ -219,7 +219,7 @@ export default function BannerRows({
</div> </div>
<Link <Link
href={`/admin/banners/${b.id}/analytics`} href={`/admin/banners/${b.id}/analytics`}
className="text-sm text-[#60a5fa] hover:underline" className="text-sm text-[#ffd25a] hover:underline"
> >
View detailed analytics View detailed analytics
</Link> </Link>

View File

@@ -68,7 +68,7 @@ export default function AnalyticsCharts({
</div> </div>
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <section className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<ChartCard title="Impressions per day" data={imp} stroke="#60a5fa" /> <ChartCard title="Impressions per day" data={imp} stroke="#ffd25a" />
<ChartCard title="Clicks per day" data={clk} stroke="#34d399" /> <ChartCard title="Clicks per day" data={clk} stroke="#34d399" />
</section> </section>
@@ -121,7 +121,7 @@ function TopListCard({ title, rows }: { title: string; rows: TopRow[] }) {
{rows.map((r) => ( {rows.map((r) => (
<tr key={r.label} className="border-b border-[#1f2937] last:border-0"> <tr key={r.label} className="border-b border-[#1f2937] last:border-0">
<td className="py-2 pr-3 text-[#e5e7eb] truncate max-w-[400px]" title={r.label}>{r.label}</td> <td className="py-2 pr-3 text-[#e5e7eb] truncate max-w-[400px]" title={r.label}>{r.label}</td>
<td className="py-2 text-right text-[#60a5fa]">{r.count.toLocaleString()}</td> <td className="py-2 text-right text-[#ffd25a]">{r.count.toLocaleString()}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@@ -142,7 +142,7 @@ export default async function BannerAnalyticsPage({
return ( return (
<main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]"> <main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]">
<Link href="/admin/banners" className="text-sm text-[#60a5fa] hover:underline"> <Link href="/admin/banners" className="text-sm text-[#ffd25a] hover:underline">
Back to banners Back to banners
</Link> </Link>
@@ -159,7 +159,7 @@ export default async function BannerAnalyticsPage({
<p className="text-sm text-[#9ca3af]"> <p className="text-sm text-[#9ca3af]">
{b.size} · {b.status} ·{" "} {b.size} · {b.status} ·{" "}
{b.click_url && ( {b.click_url && (
<a className="text-[#60a5fa] hover:underline" href={b.click_url} target="_blank" rel="noopener noreferrer"> <a className="text-[#ffd25a] hover:underline" href={b.click_url} target="_blank" rel="noopener noreferrer">
{b.click_url.slice(0, 80)}{b.click_url.length > 80 ? "…" : ""} {b.click_url.slice(0, 80)}{b.click_url.length > 80 ? "…" : ""}
</a> </a>
)} )}

View File

@@ -113,7 +113,7 @@ export default async function AdminBannersPage() {
after the next refresh. after the next refresh.
</p> </p>
</div> </div>
<Link href="/admin" className="text-sm text-[#60a5fa] hover:underline"> <Link href="/admin" className="text-sm text-[#ffd25a] hover:underline">
Admin home Admin home
</Link> </Link>
</header> </header>

View File

@@ -171,7 +171,7 @@ export default function CompanyCoveragePage() {
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="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading company coverage</p> <p className="text-[#555] text-sm font-body">Loading company coverage</p>
</div> </div>
</div> </div>
@@ -218,7 +218,7 @@ export default function CompanyCoveragePage() {
<button <button
onClick={handleScan} onClick={handleScan}
disabled={scanning} disabled={scanning}
className="flex items-center gap-2 px-4 py-2 bg-[#3b82f6] hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-body font-semibold rounded-lg transition-colors" className="flex items-center gap-2 px-4 py-2 bg-[#ffb800] hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-body font-semibold rounded-lg transition-colors"
> >
{scanning ? ( {scanning ? (
<> <>
@@ -262,7 +262,7 @@ export default function CompanyCoveragePage() {
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-4 py-1.5 rounded-md text-sm font-body font-semibold capitalize transition-colors ${ className={`px-4 py-1.5 rounded-md text-sm font-body font-semibold capitalize transition-colors ${
activeTab === tab activeTab === tab
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -282,7 +282,7 @@ export default function CompanyCoveragePage() {
{activeTab === 'queued' ? 'No stories queued — run a scan to detect companies' : `No ${activeTab} stories`} {activeTab === 'queued' ? 'No stories queued — run a scan to detect companies' : `No ${activeTab} stories`}
</p> </p>
{activeTab === 'queued' && ( {activeTab === 'queued' && (
<button onClick={handleScan} disabled={scanning} className="mt-3 text-xs text-[#3b82f6] hover:text-blue-300 font-body underline"> <button onClick={handleScan} disabled={scanning} className="mt-3 text-xs text-[#ffb800] hover:text-blue-300 font-body underline">
Run scan now Run scan now
</button> </button>
)} )}
@@ -374,7 +374,7 @@ export default function CompanyCoveragePage() {
<div className="flex items-start justify-between gap-3 mb-3"> <div className="flex items-start justify-between gap-3 mb-3">
<div> <div>
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<svg className="w-4 h-4 text-[#3b82f6]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#ffb800]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg> </svg>
<h3 className="text-sm font-display font-bold text-white">Auto-Pilot</h3> <h3 className="text-sm font-display font-bold text-white">Auto-Pilot</h3>
@@ -422,7 +422,7 @@ export default function CompanyCoveragePage() {
</div> </div>
<p className="text-xs text-[#555] font-body mb-2">New story suggestions are emailed to:</p> <p className="text-xs text-[#555] font-body mb-2">New story suggestions are emailed to:</p>
<div className="bg-[#0d0d0d] border border-[#1a1a1a] rounded-md px-3 py-2"> <div className="bg-[#0d0d0d] border border-[#1a1a1a] rounded-md px-3 py-2">
<p className="text-xs text-[#3b82f6] font-body font-mono break-all">{settings.reminder_email}</p> <p className="text-xs text-[#ffb800] font-body font-mono break-all">{settings.reminder_email}</p>
</div> </div>
<p className="text-xs text-[#444] font-body mt-2">Reminders are sent when Auto-Pilot is off and new companies are detected.</p> <p className="text-xs text-[#444] font-body mt-2">Reminders are sent when Auto-Pilot is off and new companies are detected.</p>
</div> </div>

View File

@@ -245,7 +245,7 @@ export default function CompanyTrackerPage() {
if (loading || loadingData) { if (loading || loadingData) {
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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -291,13 +291,13 @@ export default function CompanyTrackerPage() {
await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'update_settings', autopilot_enabled: newVal, reminder_email: settings.reminder_email }) }); await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'update_settings', autopilot_enabled: newVal, reminder_email: settings.reminder_email }) });
showToast(newVal ? '🚀 Auto-Pilot enabled' : '⏸ Auto-Pilot disabled'); showToast(newVal ? '🚀 Auto-Pilot enabled' : '⏸ Auto-Pilot disabled');
}} }}
className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#3b82f6]' : 'bg-[#333]'}`} className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#ffb800]' : 'bg-[#333]'}`}
> >
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
<button onClick={handleScan} disabled={scanning} <button onClick={handleScan} disabled={scanning}
className="flex items-center gap-2 px-4 py-2 bg-[#3b82f6] hover:bg-blue-500 disabled:opacity-50 text-white text-sm font-body font-semibold rounded-lg transition-colors"> className="flex items-center gap-2 px-4 py-2 bg-[#ffb800] hover:bg-blue-500 disabled:opacity-50 text-white text-sm font-body font-semibold rounded-lg transition-colors">
{scanning ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>} {scanning ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>}
{scanning ? 'Scanning…' : 'Scan Articles Now'} {scanning ? 'Scanning…' : 'Scan Articles Now'}
</button> </button>
@@ -337,7 +337,7 @@ export default function CompanyTrackerPage() {
<div className="flex flex-wrap gap-3 mb-4"> <div className="flex flex-wrap gap-3 mb-4">
{(['generated','pending','approved','published','rejected'] as QueueStatus[]).map(s => ( {(['generated','pending','approved','published','rejected'] as QueueStatus[]).map(s => (
<button key={s} onClick={() => setQueueStatus(s)} <button key={s} onClick={() => setQueueStatus(s)}
className={`px-3 py-1.5 rounded-lg text-xs font-body font-medium transition-colors capitalize ${queueStatus === s ? 'bg-[#3b82f6] text-white' : 'bg-[#111] border border-[#252525] text-[#555] hover:text-white'}`}> className={`px-3 py-1.5 rounded-lg text-xs font-body font-medium transition-colors capitalize ${queueStatus === s ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#555] hover:text-white'}`}>
{s === 'generated' ? 'Pending Review' : s} {s === 'generated' ? 'Pending Review' : s}
</button> </button>
))} ))}
@@ -430,7 +430,7 @@ export default function CompanyTrackerPage() {
<div> <div>
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<p className="text-sm text-[#555] font-body">{companies.length} companies tracked</p> <p className="text-sm text-[#555] font-body">{companies.length} companies tracked</p>
<button onClick={() => setShowAddCompany(true)} className="flex items-center gap-2 px-4 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors"> <button onClick={() => setShowAddCompany(true)} className="flex items-center gap-2 px-4 py-2 bg-[#ffb800] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg>
Add Company Add Company
</button> </button>
@@ -443,17 +443,17 @@ export default function CompanyTrackerPage() {
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Company Name *</label> <label className="block text-xs font-body text-[#888] mb-1.5">Company Name *</label>
<input type="text" value={addCompanyForm.company_name} onChange={e => setAddCompanyForm(f => ({ ...f, company_name: e.target.value }))} required <input type="text" value={addCompanyForm.company_name} onChange={e => setAddCompanyForm(f => ({ ...f, company_name: e.target.value }))} required
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-[#ffb800]" />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Website URL</label> <label className="block text-xs font-body text-[#888] mb-1.5">Website URL</label>
<input type="text" value={addCompanyForm.company_website} onChange={e => setAddCompanyForm(f => ({ ...f, company_website: e.target.value }))} placeholder="example.com" <input type="text" value={addCompanyForm.company_website} onChange={e => setAddCompanyForm(f => ({ ...f, company_website: e.target.value }))} placeholder="example.com"
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-[#ffb800]" />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label> <label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label>
<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-[#ffb800]">
<option value="both">Both Sites</option> <option value="both">Both Sites</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
@@ -462,14 +462,14 @@ export default function CompanyTrackerPage() {
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Crawl Priority</label> <label className="block text-xs font-body text-[#888] mb-1.5">Crawl Priority</label>
<select value={addCompanyForm.crawl_priority} onChange={e => setAddCompanyForm(f => ({ ...f, crawl_priority: e.target.value }))} <select value={addCompanyForm.crawl_priority} onChange={e => setAddCompanyForm(f => ({ ...f, crawl_priority: 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-[#ffb800]">
<option value="standard">Standard (weekly)</option> <option value="standard">Standard (weekly)</option>
<option value="high">High (every 3 days)</option> <option value="high">High (every 3 days)</option>
<option value="critical">Critical (daily)</option> <option value="critical">Critical (daily)</option>
</select> </select>
</div> </div>
<div className="md:col-span-2 flex gap-3"> <div className="md:col-span-2 flex gap-3">
<button type="submit" className="px-5 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">Add Company</button> <button type="submit" className="px-5 py-2 bg-[#ffb800] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">Add Company</button>
<button type="button" onClick={() => setShowAddCompany(false)} className="px-4 py-2 bg-[#1a1a1a] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors">Cancel</button> <button type="button" onClick={() => setShowAddCompany(false)} className="px-4 py-2 bg-[#1a1a1a] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors">Cancel</button>
</div> </div>
</form> </form>
@@ -492,12 +492,12 @@ export default function CompanyTrackerPage() {
) : companies.map(company => ( ) : companies.map(company => (
<tr key={company.id} className="hover:bg-[#0d0d0d] transition-colors"> <tr key={company.id} className="hover:bg-[#0d0d0d] transition-colors">
<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-[#ffb800] 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 === 'avbeat' ? '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-[#ffb800]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${company.auto_story_enabled ? 'translate-x-4' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${company.auto_story_enabled ? 'translate-x-4' : 'translate-x-0'}`} />
</button> </button>
</td> </td>
@@ -506,7 +506,7 @@ export default function CompanyTrackerPage() {
<td className="px-4 py-3"> <td className="px-4 py-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button onClick={async () => { await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'force_crawl', company_id: company.id }) }); showToast('Crawl scheduled'); }} <button onClick={async () => { await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'force_crawl', company_id: company.id }) }); showToast('Crawl scheduled'); }}
className="text-xs text-[#3b82f6] hover:text-blue-300 font-body transition-colors">Force Crawl</button> className="text-xs text-[#ffb800] hover:text-blue-300 font-body transition-colors">Force Crawl</button>
<button onClick={() => handleDeleteCompany(company.id)} disabled={actionLoading === company.id} <button onClick={() => handleDeleteCompany(company.id)} disabled={actionLoading === company.id}
className="text-xs text-red-500 hover:text-red-400 font-body transition-colors disabled:opacity-50">Remove</button> className="text-xs text-red-500 hover:text-red-400 font-body transition-colors disabled:opacity-50">Remove</button>
</div> </div>
@@ -567,14 +567,14 @@ export default function CompanyTrackerPage() {
<div className="text-xs text-[#555] font-body mt-0.5">When enabled, high-confidence stories publish automatically without review</div> <div className="text-xs text-[#555] font-body mt-0.5">When enabled, high-confidence stories publish automatically without review</div>
</div> </div>
<button onClick={() => setSettings(s => ({ ...s, autopilot_enabled: !s.autopilot_enabled }))} <button onClick={() => setSettings(s => ({ ...s, autopilot_enabled: !s.autopilot_enabled }))}
className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#3b82f6]' : 'bg-[#333]'}`}> className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#ffb800]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Reminder Email</label> <label className="block text-xs font-body text-[#888] mb-1.5">Reminder Email</label>
<input type="email" value={settings.reminder_email} onChange={e => setSettings(s => ({ ...s, reminder_email: e.target.value }))} <input type="email" value={settings.reminder_email} onChange={e => setSettings(s => ({ ...s, reminder_email: 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-[#ffb800]" />
</div> </div>
</div> </div>
</div> </div>
@@ -588,7 +588,7 @@ export default function CompanyTrackerPage() {
<div className="text-xs text-[#555] font-body mt-0.5">Master switch for all automated story generation</div> <div className="text-xs text-[#555] font-body mt-0.5">Master switch for all automated story generation</div>
</div> </div>
<button onClick={() => setTrackerSettings(s => ({ ...s, auto_story_global_enabled: !s.auto_story_global_enabled }))} <button onClick={() => setTrackerSettings(s => ({ ...s, auto_story_global_enabled: !s.auto_story_global_enabled }))}
className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_story_global_enabled ? 'bg-[#3b82f6]' : 'bg-[#333]'}`}> className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_story_global_enabled ? 'bg-[#ffb800]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_story_global_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_story_global_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
@@ -598,25 +598,25 @@ export default function CompanyTrackerPage() {
<div className="text-xs text-[#555] font-body mt-0.5">Publish stories with confidence {Math.round(trackerSettings.auto_publish_confidence_threshold * 100)}% automatically</div> <div className="text-xs text-[#555] font-body mt-0.5">Publish stories with confidence {Math.round(trackerSettings.auto_publish_confidence_threshold * 100)}% automatically</div>
</div> </div>
<button onClick={() => setTrackerSettings(s => ({ ...s, auto_publish_enabled: !s.auto_publish_enabled }))} <button onClick={() => setTrackerSettings(s => ({ ...s, auto_publish_enabled: !s.auto_publish_enabled }))}
className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_publish_enabled ? 'bg-[#3b82f6]' : 'bg-[#333]'}`}> className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_publish_enabled ? 'bg-[#ffb800]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_publish_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_publish_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Mention Threshold (min mentions to qualify for auto-stories)</label> <label className="block text-xs font-body text-[#888] mb-1.5">Mention Threshold (min mentions to qualify for auto-stories)</label>
<input type="number" min={1} max={20} value={trackerSettings.mention_threshold} onChange={e => setTrackerSettings(s => ({ ...s, mention_threshold: parseInt(e.target.value) }))} <input type="number" min={1} max={20} value={trackerSettings.mention_threshold} onChange={e => setTrackerSettings(s => ({ ...s, mention_threshold: parseInt(e.target.value) }))}
className="w-32 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-32 bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Max Stories Per Company Per Week</label> <label className="block text-xs font-body text-[#888] mb-1.5">Max Stories Per Company Per Week</label>
<input type="number" min={1} max={20} value={trackerSettings.max_stories_per_company_per_week} onChange={e => setTrackerSettings(s => ({ ...s, max_stories_per_company_per_week: parseInt(e.target.value) }))} <input type="number" min={1} max={20} value={trackerSettings.max_stories_per_company_per_week} onChange={e => setTrackerSettings(s => ({ ...s, max_stories_per_company_per_week: parseInt(e.target.value) }))}
className="w-32 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-32 bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
</div> </div>
<button onClick={handleSaveSettings} disabled={savingSettings} <button onClick={handleSaveSettings} disabled={savingSettings}
className="px-6 py-2.5 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50"> className="px-6 py-2.5 bg-[#ffb800] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50">
{savingSettings ? 'Saving...' : 'Save Settings'} {savingSettings ? 'Saving...' : 'Save Settings'}
</button> </button>
</div> </div>

View File

@@ -181,7 +181,7 @@ function GeneratePanel({ onGenerated }: { onGenerated: () => void }) {
<select <select
value={site} value={site}
onChange={(e) => setSite(e.target.value as RMPSite)} onChange={(e) => setSite(e.target.value as RMPSite)}
className="w-full bg-[#1a1a1a] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#1a1a1a] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#ffb800]"
> >
{Object.entries(SITE_LABELS).map(([key, val]) => ( {Object.entries(SITE_LABELS).map(([key, val]) => (
<option key={key} value={key}>{val.label}</option> <option key={key} value={key}>{val.label}</option>
@@ -195,14 +195,14 @@ function GeneratePanel({ onGenerated }: { onGenerated: () => void }) {
value={topic} value={topic}
onChange={(e) => setTopic(e.target.value)} onChange={(e) => setTopic(e.target.value)}
placeholder="e.g. ATSC 3.0 deployment update" placeholder="e.g. ATSC 3.0 deployment update"
className="w-full bg-[#1a1a1a] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body placeholder-[#444] focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#1a1a1a] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body placeholder-[#444] focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={handleGenerate} onClick={handleGenerate}
disabled={loading} disabled={loading}
className="flex-1 bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white text-sm font-body font-semibold py-2 px-4 rounded-lg transition-colors" className="flex-1 bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 text-white text-sm font-body font-semibold py-2 px-4 rounded-lg transition-colors"
> >
{loading ? 'Generating…' : 'Generate Article'} {loading ? 'Generating…' : 'Generate Article'}
</button> </button>
@@ -279,7 +279,7 @@ export default function ContentStatusPage() {
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="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading content status</p> <p className="text-[#555] text-sm font-body">Loading content status</p>
</div> </div>
</div> </div>

View File

@@ -90,7 +90,7 @@ function StatCard({
label, label,
value, value,
sub, sub,
accent = '#3b82f6', accent = '#ffb800',
icon, icon,
trend, trend,
}: { }: {
@@ -252,7 +252,7 @@ export default function EmailAnalyticsDashboard() {
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="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading analytics</p> <p className="text-[#555] text-sm font-body">Loading analytics</p>
</div> </div>
</div> </div>
@@ -280,7 +280,7 @@ export default function EmailAnalyticsDashboard() {
onClick={() => setTimeRange(r)} onClick={() => setTimeRange(r)}
className={`px-3 py-1.5 text-xs font-semibold font-body rounded-lg transition-colors ${ className={`px-3 py-1.5 text-xs font-semibold font-body rounded-lg transition-colors ${
timeRange === r timeRange === r
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#555] hover:text-white border border-[#252525] hover:border-[#444]' : 'text-[#555] hover:text-white border border-[#252525] hover:border-[#444]'
}`} }`}
> >
@@ -317,7 +317,7 @@ export default function EmailAnalyticsDashboard() {
label="Active Subscribers" label="Active Subscribers"
value={fmt(stats.activeSubscribers)} value={fmt(stats.activeSubscribers)}
sub={`${fmt(stats.unsubscribed)} unsubscribed`} sub={`${fmt(stats.unsubscribed)} unsubscribed`}
accent="#3b82f6" accent="#ffb800"
trend={{ value: 12, label: 'vs last month' }} trend={{ value: 12, label: 'vs last month' }}
icon={ icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
@@ -378,7 +378,7 @@ export default function EmailAnalyticsDashboard() {
<YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => fmt(v)} /> <YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => fmt(v)} />
<Tooltip content={<ChartTooltip />} /> <Tooltip content={<ChartTooltip />} />
<Legend wrapperStyle={{ fontSize: 11, color: '#888', paddingTop: 12 }} /> <Legend wrapperStyle={{ fontSize: 11, color: '#888', paddingTop: 12 }} />
<Bar dataKey="sent" name="Sent" fill="#3b82f620" stroke="#3b82f6" strokeWidth={1} radius={[3, 3, 0, 0]} /> <Bar dataKey="sent" name="Sent" fill="#3b82f620" stroke="#ffb800" strokeWidth={1} radius={[3, 3, 0, 0]} />
<Bar dataKey="opens" name="Opens" fill="#10b98120" stroke="#10b981" strokeWidth={1} radius={[3, 3, 0, 0]} /> <Bar dataKey="opens" name="Opens" fill="#10b98120" stroke="#10b981" strokeWidth={1} radius={[3, 3, 0, 0]} />
<Bar dataKey="clicks" name="Clicks" fill="#f59e0b20" stroke="#f59e0b" strokeWidth={1} radius={[3, 3, 0, 0]} /> <Bar dataKey="clicks" name="Clicks" fill="#f59e0b20" stroke="#f59e0b" strokeWidth={1} radius={[3, 3, 0, 0]} />
</ComposedChart> </ComposedChart>
@@ -397,8 +397,8 @@ export default function EmailAnalyticsDashboard() {
<AreaChart data={filteredRates} margin={{ top: 4, right: 8, left: -10, bottom: 0 }}> <AreaChart data={filteredRates} margin={{ top: 4, right: 8, left: -10, bottom: 0 }}>
<defs> <defs>
<linearGradient id="openGrad" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="openGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.2} /> <stop offset="5%" stopColor="#ffb800" stopOpacity={0.2} />
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} /> <stop offset="95%" stopColor="#ffb800" stopOpacity={0} />
</linearGradient> </linearGradient>
<linearGradient id="clickGrad" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="clickGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#10b981" stopOpacity={0.2} /> <stop offset="5%" stopColor="#10b981" stopOpacity={0.2} />
@@ -418,10 +418,10 @@ export default function EmailAnalyticsDashboard() {
<Area <Area
type="monotone" type="monotone"
dataKey="Open Rate" dataKey="Open Rate"
stroke="#3b82f6" stroke="#ffb800"
strokeWidth={2} strokeWidth={2}
fill="url(#openGrad)" fill="url(#openGrad)"
dot={{ r: 3, fill: '#3b82f6' }} dot={{ r: 3, fill: '#ffb800' }}
activeDot={{ r: 5 }} activeDot={{ r: 5 }}
/> />
<Area <Area
@@ -463,7 +463,7 @@ export default function EmailAnalyticsDashboard() {
fill="#8b5cf618" fill="#8b5cf618"
dot={false} dot={false}
/> />
<Bar dataKey="net" name="Net New" fill="#3b82f6" radius={[3, 3, 0, 0]} /> <Bar dataKey="net" name="Net New" fill="#ffb800" radius={[3, 3, 0, 0]} />
</ComposedChart> </ComposedChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
@@ -503,7 +503,7 @@ export default function EmailAnalyticsDashboard() {
label: 'Open Rate', label: 'Open Rate',
yours: stats.avgOpenRate, yours: stats.avgOpenRate,
benchmark: 21.5, benchmark: 21.5,
color: '#3b82f6', color: '#ffb800',
unit: '%', unit: '%',
}, },
{ {

View File

@@ -75,7 +75,7 @@ function DomainsContent() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -86,7 +86,7 @@ function DomainsContent() {
<h1 className="text-xl font-bold">Sending Domains</h1> <h1 className="text-xl font-bold">Sending Domains</h1>
<Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link> <Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link>
</div> </div>
<button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ Add Domain</button> <button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Add Domain</button>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
@@ -109,7 +109,7 @@ function DomainsContent() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Domain *</label> <label className="text-xs text-[#888] block mb-1">Domain *</label>
<input required value={addForm.domain} onChange={e => setAddForm(p => ({ ...p, domain: e.target.value }))} placeholder="mail.example.com" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input required value={addForm.domain} onChange={e => setAddForm(p => ({ ...p, domain: e.target.value }))} placeholder="mail.example.com" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Type</label> <label className="text-xs text-[#888] block mb-1">Type</label>
@@ -119,7 +119,7 @@ function DomainsContent() {
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Domain'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Domain'}</button>
<button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -197,7 +197,7 @@ function DomainsContent() {
{/* Actions */} {/* Actions */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<button onClick={() => runVerify(d.id)} disabled={verifying === d.id} className="px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] disabled:opacity-50"> <button onClick={() => runVerify(d.id)} disabled={verifying === d.id} className="px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] disabled:opacity-50">
{verifying === d.id ? 'Verifying…' : 'Run Full Verification'} {verifying === d.id ? 'Verifying…' : 'Run Full Verification'}
</button> </button>
<button onClick={() => toggleActive(d.id, d.is_active)} disabled={!d.dns_fully_verified && !d.bypass_verification && !d.is_active} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white disabled:opacity-30"> <button onClick={() => toggleActive(d.id, d.is_active)} disabled={!d.dns_fully_verified && !d.bypass_verification && !d.is_active} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white disabled:opacity-30">
@@ -219,7 +219,7 @@ function DomainsContent() {
export default function EmailDomainsPage() { export default function EmailDomainsPage() {
return ( return (
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>}> <Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>}>
<DomainsContent /> <DomainsContent />
</Suspense> </Suspense>
); );

View File

@@ -42,7 +42,7 @@ export default function EmailDashboard() {
domains: domains.filter((d: any) => d.site === site), domains: domains.filter((d: any) => d.site === site),
})); }));
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -77,7 +77,7 @@ export default function EmailDashboard() {
<div key={site} className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-3"> <div key={site} className="bg-[#111] border border-[#252525] rounded-lg p-4 space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-sm font-semibold capitalize">{site}</h3> <h3 className="text-sm font-semibold capitalize">{site}</h3>
<Link href={`/admin/email/domains?site=${site}`} className="text-xs text-[#3b82f6] hover:underline">Manage </Link> <Link href={`/admin/email/domains?site=${site}`} className="text-xs text-[#ffb800] hover:underline">Manage </Link>
</div> </div>
<div className="text-xs text-[#888]"> <div className="text-xs text-[#888]">
<span className="text-white">{primary?.domain ?? 'No primary'}</span> <span className="text-white">{primary?.domain ?? 'No primary'}</span>
@@ -113,7 +113,7 @@ export default function EmailDashboard() {
{ label: 'Suppressions', href: '/admin/email/suppressions' }, { label: 'Suppressions', href: '/admin/email/suppressions' },
{ label: 'Send Log', href: '/admin/email/send-log' }, { label: 'Send Log', href: '/admin/email/send-log' },
].map(l => ( ].map(l => (
<Link key={l.href} href={l.href} className="bg-[#111] border border-[#252525] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#3b82f6] transition-colors"> <Link key={l.href} href={l.href} className="bg-[#111] border border-[#252525] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#ffb800] transition-colors">
{l.label} {l.label}
</Link> </Link>
))} ))}

View File

@@ -29,7 +29,7 @@ export default function SendLogPage() {
useEffect(() => { if (user) load(); }, [user, load]); useEffect(() => { if (user) load(); }, [user, load]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (

View File

@@ -72,7 +72,7 @@ export default function SuppressionsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -84,14 +84,14 @@ export default function SuppressionsPage() {
<Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link> <Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500">+ Add Suppression</button> <button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500">+ Add Suppression</button>
</div> </div>
</div> </div>
{/* Site selector */} {/* Site selector */}
<div className="flex gap-1 flex-wrap"> <div className="flex gap-1 flex-wrap">
{SITES.map(s => ( {SITES.map(s => (
<button key={s} onClick={() => setSite(s)} className={`px-3 py-1 rounded text-xs capitalize ${site === s ? 'bg-[#3b82f6] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>{s}</button> <button key={s} onClick={() => setSite(s)} className={`px-3 py-1 rounded text-xs capitalize ${site === s ? 'bg-[#ffb800] text-white' : 'bg-[#111] border border-[#252525] text-[#888]'}`}>{s}</button>
))} ))}
</div> </div>
@@ -119,7 +119,7 @@ export default function SuppressionsPage() {
<option value="">All Types</option> <option value="">All Types</option>
{SUPP_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)} {SUPP_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
</select> </select>
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search email…" className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#3b82f6]" /> <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search email…" className="px-2 py-1 bg-[#111] border border-[#252525] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#ffb800]" />
</div> </div>
{showAddForm && ( {showAddForm && (
@@ -128,7 +128,7 @@ export default function SuppressionsPage() {
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Email *</label> <label className="text-xs text-[#888] block mb-1">Email *</label>
<input required type="email" value={addForm.email} onChange={e => setAddForm(p => ({ ...p, email: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input required type="email" value={addForm.email} onChange={e => setAddForm(p => ({ ...p, email: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Type</label> <label className="text-xs text-[#888] block mb-1">Type</label>
@@ -138,11 +138,11 @@ export default function SuppressionsPage() {
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<input value={addForm.notes} onChange={e => setAddForm(p => ({ ...p, notes: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input value={addForm.notes} onChange={e => setAddForm(p => ({ ...p, notes: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add'}</button>
<button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
@@ -186,13 +186,13 @@ export default function SuppressionsPage() {
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#252525] flex items-center justify-between"> <div className="px-4 py-3 border-b border-[#252525] flex items-center justify-between">
<h2 className="text-sm font-semibold">Suppression Keywords</h2> <h2 className="text-sm font-semibold">Suppression Keywords</h2>
<button onClick={() => setShowKeywordForm(true)} className="text-xs text-[#3b82f6] hover:underline">+ Add Keyword</button> <button onClick={() => setShowKeywordForm(true)} className="text-xs text-[#ffb800] hover:underline">+ Add Keyword</button>
</div> </div>
{showKeywordForm && ( {showKeywordForm && (
<form onSubmit={handleAddKeyword} className="px-4 py-3 border-b border-[#1a1a1a] flex gap-2 items-end"> <form onSubmit={handleAddKeyword} className="px-4 py-3 border-b border-[#1a1a1a] flex gap-2 items-end">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Keyword *</label> <label className="text-xs text-[#888] block mb-1">Keyword *</label>
<input required value={keywordForm.keyword} onChange={e => setKeywordForm(p => ({ ...p, keyword: e.target.value }))} className="px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#3b82f6]" /> <input required value={keywordForm.keyword} onChange={e => setKeywordForm(p => ({ ...p, keyword: e.target.value }))} className="px-2 py-1.5 bg-[#0a0a0a] border border-[#252525] rounded text-xs text-white focus:outline-none focus:border-[#ffb800]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Type</label> <label className="text-xs text-[#888] block mb-1">Type</label>
@@ -201,7 +201,7 @@ export default function SuppressionsPage() {
<option value="hostile">hostile</option> <option value="hostile">hostile</option>
</select> </select>
</div> </div>
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#3b82f6] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Add</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#ffb800] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Add</button>
<button type="button" onClick={() => setShowKeywordForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888]">Cancel</button> <button type="button" onClick={() => setShowKeywordForm(false)} className="px-3 py-1.5 bg-[#1a1a1a] border border-[#252525] rounded text-xs text-[#888]">Cancel</button>
</form> </form>
)} )}
@@ -216,7 +216,7 @@ export default function SuppressionsPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${k.active ? 'bg-green-400/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{k.active ? 'Active' : 'Inactive'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${k.active ? 'bg-green-400/10 text-green-400' : 'bg-[#1a1a1a] text-[#555]'}`}>{k.active ? 'Active' : 'Inactive'}</span>
</td> </td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<button onClick={() => toggleKeyword(k.id, k.active)} className="text-xs text-[#3b82f6] hover:underline">{k.active ? 'Deactivate' : 'Activate'}</button> <button onClick={() => toggleKeyword(k.id, k.active)} className="text-xs text-[#ffb800] hover:underline">{k.active ? 'Deactivate' : 'Activate'}</button>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -167,7 +167,7 @@ const EMPTY_STATS: ForumStats = {
}, },
}; };
const CATEGORY_COLORS = ['#3b82f6', '#f59e0b', '#10b981', '#8b5cf6', '#ef4444', '#06b6d4', '#f97316', '#84cc16']; const CATEGORY_COLORS = ['#ffb800', '#f59e0b', '#10b981', '#8b5cf6', '#ef4444', '#06b6d4', '#f97316', '#84cc16'];
export default function ForumDashboardPage() { export default function ForumDashboardPage() {
const { user, loading } = useAuth(); const { user, loading } = useAuth();
@@ -204,7 +204,7 @@ export default function ForumDashboardPage() {
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="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading forum dashboard</p> <p className="text-[#555] text-sm font-body">Loading forum dashboard</p>
</div> </div>
</div> </div>
@@ -245,7 +245,7 @@ export default function ForumDashboardPage() {
</button> </button>
<Link <Link
href="/admin/forum-moderation" href="/admin/forum-moderation"
className="flex items-center gap-2 px-3 py-2 bg-[#3b82f6] rounded-lg text-xs text-white hover:bg-blue-500 transition-colors font-body font-semibold" className="flex items-center gap-2 px-3 py-2 bg-[#ffb800] rounded-lg text-xs text-white hover:bg-blue-500 transition-colors font-body font-semibold"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <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="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
@@ -407,7 +407,7 @@ export default function ForumDashboardPage() {
iconType="circle" iconType="circle"
iconSize={8} iconSize={8}
/> />
<Line type="monotone" dataKey="threads" stroke="#3b82f6" strokeWidth={2} dot={{ fill: '#3b82f6', r: 3 }} name="New Threads" /> <Line type="monotone" dataKey="threads" stroke="#ffb800" strokeWidth={2} dot={{ fill: '#ffb800', r: 3 }} name="New Threads" />
<Line type="monotone" dataKey="replies" stroke="#10b981" strokeWidth={2} dot={{ fill: '#10b981', r: 3 }} name="New Replies" /> <Line type="monotone" dataKey="replies" stroke="#10b981" strokeWidth={2} dot={{ fill: '#10b981', r: 3 }} name="New Replies" />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -425,7 +425,7 @@ export default function ForumDashboardPage() {
<h2 className="text-sm font-display font-bold text-white">Top Categories</h2> <h2 className="text-sm font-display font-bold text-white">Top Categories</h2>
<p className="text-xs text-[#555] font-body mt-0.5">By thread count</p> <p className="text-xs text-[#555] font-body mt-0.5">By thread count</p>
</div> </div>
<Link href="/forum" className="text-xs text-[#3b82f6] hover:text-blue-300 font-body transition-colors">View forum </Link> <Link href="/forum" className="text-xs text-[#ffb800] hover:text-blue-300 font-body transition-colors">View forum </Link>
</div> </div>
{topCategories.length === 0 ? ( {topCategories.length === 0 ? (
<div className="px-5 py-10 text-center"> <div className="px-5 py-10 text-center">
@@ -475,7 +475,7 @@ export default function ForumDashboardPage() {
<div className="w-6 h-6 rounded-full bg-[#1a1a1a] flex items-center justify-center flex-shrink-0 text-xs font-bold font-body text-[#555]"> <div className="w-6 h-6 rounded-full bg-[#1a1a1a] flex items-center justify-center flex-shrink-0 text-xs font-bold font-body text-[#555]">
{idx + 1} {idx + 1}
</div> </div>
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-[#3b82f6] to-[#8b5cf6] flex items-center justify-center flex-shrink-0"> <div className="w-8 h-8 rounded-full bg-gradient-to-br from-[#ffb800] to-[#8b5cf6] flex items-center justify-center flex-shrink-0">
<span className="text-xs font-bold text-white font-body"> <span className="text-xs font-bold text-white font-body">
{u.author_name.charAt(0).toUpperCase()} {u.author_name.charAt(0).toUpperCase()}
</span> </span>
@@ -523,7 +523,7 @@ export default function ForumDashboardPage() {
iconType="circle" iconType="circle"
iconSize={8} iconSize={8}
/> />
<Bar dataKey="thread_count" fill="#3b82f6" radius={[3, 3, 0, 0]} name="Threads" /> <Bar dataKey="thread_count" fill="#ffb800" radius={[3, 3, 0, 0]} name="Threads" />
<Bar dataKey="post_count" fill="#10b981" radius={[3, 3, 0, 0]} name="Posts" /> <Bar dataKey="post_count" fill="#10b981" radius={[3, 3, 0, 0]} name="Posts" />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -539,7 +539,7 @@ export default function ForumDashboardPage() {
<h2 className="text-sm font-display font-bold text-white">Moderation Stats</h2> <h2 className="text-sm font-display font-bold text-white">Moderation Stats</h2>
<p className="text-xs text-[#555] font-body mt-0.5">Current moderation state across all forum content</p> <p className="text-xs text-[#555] font-body mt-0.5">Current moderation state across all forum content</p>
</div> </div>
<Link href="/admin/forum-moderation" className="text-xs text-[#3b82f6] hover:text-blue-300 font-body transition-colors">Open moderation </Link> <Link href="/admin/forum-moderation" className="text-xs text-[#ffb800] hover:text-blue-300 font-body transition-colors">Open moderation </Link>
</div> </div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 divide-x divide-y sm:divide-y-0 divide-[#1a1a1a]"> <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 divide-x divide-y sm:divide-y-0 divide-[#1a1a1a]">
{[ {[

View File

@@ -126,7 +126,7 @@ function ThreadRow({ thread, onAction, loadingId }: ThreadRowProps) {
<div className="flex flex-wrap items-center gap-2 mb-1"> <div className="flex flex-wrap items-center gap-2 mb-1">
<Link <Link
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
className="text-white font-body font-semibold text-sm hover:text-[#3b82f6] transition-colors truncate max-w-xs" className="text-white font-body font-semibold text-sm hover:text-[#ffb800] transition-colors truncate max-w-xs"
target="_blank" target="_blank"
> >
{thread.title} {thread.title}
@@ -243,7 +243,7 @@ function ReplyRow({ reply, onAction, loadingId }: ReplyRowProps) {
{reply.forum_threads && ( {reply.forum_threads && (
<Link <Link
href={`/forum/thread/${reply.forum_threads.id}`} href={`/forum/thread/${reply.forum_threads.id}`}
className="text-[#3b82f6] font-body text-xs hover:underline truncate max-w-xs" className="text-[#ffb800] font-body text-xs hover:underline truncate max-w-xs"
target="_blank" target="_blank"
> >
{reply.forum_threads.title} {reply.forum_threads.title}
@@ -398,7 +398,7 @@ export default function ForumModerationPage() {
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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -435,10 +435,10 @@ export default function ForumModerationPage() {
)} )}
{/* Page Header */} {/* Page Header */}
<div className="bg-[#1a2535] border-b border-[#2a3a50]"> <div className="bg-[#1a1208] border-b border-[#2a3a50]">
<div className="max-w-7xl mx-auto px-4 py-6"> <div className="max-w-7xl mx-auto px-4 py-6">
<nav className="text-sm font-body text-[#666] mb-2"> <nav className="text-sm font-body text-[#666] mb-2">
<Link href="/admin" className="hover:text-[#3b82f6] transition-colors">Admin</Link> <Link href="/admin" className="hover:text-[#ffb800] transition-colors">Admin</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<span className="text-[#aab4c4]">Forum Moderation</span> <span className="text-[#aab4c4]">Forum Moderation</span>
</nav> </nav>
@@ -470,7 +470,7 @@ export default function ForumModerationPage() {
onClick={() => { setActiveTab(tab); setPage(1); }} onClick={() => { setActiveTab(tab); setPage(1); }}
className={`px-5 py-2 text-sm font-body font-semibold rounded-md transition-colors capitalize ${ className={`px-5 py-2 text-sm font-body font-semibold rounded-md transition-colors capitalize ${
activeTab === tab activeTab === tab
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -488,7 +488,7 @@ export default function ForumModerationPage() {
onClick={() => { setThreadFilter(f.value); setPage(1); }} onClick={() => { setThreadFilter(f.value); setPage(1); }}
className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${ className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${
threadFilter === f.value threadFilter === f.value
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'bg-[#1a1a1a] text-[#888] hover:bg-[#252525] hover:text-white border border-[#252525]' : 'bg-[#1a1a1a] text-[#888] hover:bg-[#252525] hover:text-white border border-[#252525]'
}`} }`}
> >
@@ -501,7 +501,7 @@ export default function ForumModerationPage() {
onClick={() => { setReplyFilter(f.value); setPage(1); }} onClick={() => { setReplyFilter(f.value); setPage(1); }}
className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${ className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${
replyFilter === f.value replyFilter === f.value
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'bg-[#1a1a1a] text-[#888] hover:bg-[#252525] hover:text-white border border-[#252525]' : 'bg-[#1a1a1a] text-[#888] hover:bg-[#252525] hover:text-white border border-[#252525]'
}`} }`}
> >
@@ -515,7 +515,7 @@ export default function ForumModerationPage() {
{/* Content */} {/* Content */}
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<div className="w-7 h-7 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-7 h-7 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : activeTab === 'threads' ? ( ) : activeTab === 'threads' ? (
<> <>

View File

@@ -137,12 +137,12 @@ export default function AdminForumSeedPage() {
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#111111]"> <main className="min-h-screen bg-[#111111]">
<div className="bg-[#1a2535] border-b border-[#2a3a50]"> <div className="bg-[#1a1208] border-b border-[#2a3a50]">
<div className="max-w-container mx-auto px-4 py-6"> <div className="max-w-container mx-auto px-4 py-6">
<nav className="text-sm font-body text-[#666] mb-2"> <nav className="text-sm font-body text-[#666] mb-2">
<Link href="/admin" className="hover:text-[#3b82f6] transition-colors">Admin</Link> <Link href="/admin" className="hover:text-[#ffb800] transition-colors">Admin</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<Link href="/forum" className="hover:text-[#3b82f6] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#ffb800] transition-colors">Forum</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<span className="text-[#aab4c4]">Seed Tool</span> <span className="text-[#aab4c4]">Seed Tool</span>
</nav> </nav>
@@ -161,13 +161,13 @@ export default function AdminForumSeedPage() {
<div className="flex border-b border-[#252525]"> <div className="flex border-b border-[#252525]">
<button <button
onClick={() => setActiveTab('import')} onClick={() => setActiveTab('import')}
className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'import' ? 'text-white border-b-2 border-[#3b82f6]' : 'text-[#666] hover:text-[#aaa]'}`} className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'import' ? 'text-white border-b-2 border-[#ffb800]' : 'text-[#666] hover:text-[#aaa]'}`}
> >
Import Editor Import Editor
</button> </button>
<button <button
onClick={() => setActiveTab('json')} onClick={() => setActiveTab('json')}
className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'json' ? 'text-white border-b-2 border-[#3b82f6]' : 'text-[#666] hover:text-[#aaa]'}`} className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'json' ? 'text-white border-b-2 border-[#ffb800]' : 'text-[#666] hover:text-[#aaa]'}`}
> >
JSON Schema JSON Schema
</button> </button>
@@ -182,14 +182,14 @@ export default function AdminForumSeedPage() {
value={seedData} value={seedData}
onChange={e => setSeedData(e.target.value)} onChange={e => setSeedData(e.target.value)}
rows={24} rows={24}
className="w-full bg-[#0d0d0d] border border-[#333] rounded-lg px-3 py-3 text-[#d0d0d0] font-mono text-xs focus:outline-none focus:border-[#3b82f6] resize-none" className="w-full bg-[#0d0d0d] border border-[#333] rounded-lg px-3 py-3 text-[#d0d0d0] font-mono text-xs focus:outline-none focus:border-[#ffb800] resize-none"
spellCheck={false} spellCheck={false}
/> />
<div className="flex gap-3 mt-3"> <div className="flex gap-3 mt-3">
<button <button
onClick={handleImport} onClick={handleImport}
disabled={importing} disabled={importing}
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
{importing ? 'Importing...' : 'Import Threads'} {importing ? 'Importing...' : 'Import Threads'}
</button> </button>
@@ -226,7 +226,7 @@ export default function AdminForumSeedPage() {
<h4 className="text-white font-body font-semibold text-sm mb-2">Available Category Slugs</h4> <h4 className="text-white font-body font-semibold text-sm mb-2">Available Category Slugs</h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{['live-production','ip-cloud','audio','cameras','storage-mam','streaming','ai-automation','post-production','nab-ibc','career-jobs','gear-reviews','general'].map(slug => ( {['live-production','ip-cloud','audio','cameras','storage-mam','streaming','ai-automation','post-production','nab-ibc','career-jobs','gear-reviews','general'].map(slug => (
<code key={slug} className="bg-[#0d0d0d] border border-[#333] text-[#3b82f6] font-mono text-xs px-2 py-1 rounded">{slug}</code> <code key={slug} className="bg-[#0d0d0d] border border-[#333] text-[#ffb800] font-mono text-xs px-2 py-1 rounded">{slug}</code>
))} ))}
</div> </div>
</div> </div>
@@ -268,17 +268,17 @@ export default function AdminForumSeedPage() {
<div className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-4"> <div className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-4">
<h3 className="text-white font-heading font-semibold mb-3">Quick Links</h3> <h3 className="text-white font-heading font-semibold mb-3">Quick Links</h3>
<div className="space-y-2"> <div className="space-y-2">
<Link href="/forum" className="flex items-center gap-2 text-[#3b82f6] hover:underline font-body text-sm"> <Link href="/forum" className="flex items-center gap-2 text-[#ffb800] hover:underline font-body text-sm">
<span></span> View Forum <span></span> View Forum
</Link> </Link>
<Link href="/admin" className="flex items-center gap-2 text-[#3b82f6] hover:underline font-body text-sm"> <Link href="/admin" className="flex items-center gap-2 text-[#ffb800] hover:underline font-body text-sm">
<span></span> Admin Dashboard <span></span> Admin Dashboard
</Link> </Link>
</div> </div>
</div> </div>
{/* Tips */} {/* Tips */}
<div className="bg-[#1a2535] border border-[#2a3a50] rounded-lg p-4"> <div className="bg-[#1a1208] border border-[#2a3a50] rounded-lg p-4">
<h3 className="text-white font-heading font-semibold mb-2 text-sm">Tips</h3> <h3 className="text-white font-heading font-semibold mb-2 text-sm">Tips</h3>
<ul className="text-[#aab4c4] font-body text-xs space-y-1.5"> <ul className="text-[#aab4c4] font-body text-xs space-y-1.5">
<li> Threads are imported as real posts they appear immediately in the forum</li> <li> Threads are imported as real posts they appear immediately in the forum</li>

View File

@@ -134,7 +134,7 @@ export default function WPImportPage() {
if (loading || (!user && !loading)) { if (loading || (!user && !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-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -145,7 +145,7 @@ export default function WPImportPage() {
<div className="bg-[#111111] border-b border-[#252525]"> <div className="bg-[#111111] border-b border-[#252525]">
<div className="max-w-6xl mx-auto px-4 py-5"> <div className="max-w-6xl mx-auto px-4 py-5">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<Link href="/home-page" className="text-[#555] hover:text-[#3b82f6] transition-colors text-sm"> <Link href="/home-page" className="text-[#555] hover:text-[#ffb800] transition-colors text-sm">
Home Home
</Link> </Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
@@ -158,14 +158,14 @@ 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.avbeat.com" target="_blank" rel="noopener noreferrer" className="text-[#3b82f6] hover:underline"> <a href="https://www.avbeat.com" target="_blank" rel="noopener noreferrer" className="text-[#ffb800] hover:underline">
avbeat.com avbeat.com
</a>{' '} </a>{' '}
via WordPress REST API via WordPress REST API
</p> </p>
</div> </div>
<div className="flex items-center gap-2 text-xs text-[#555] bg-[#1a1a1a] border border-[#252525] rounded px-3 py-2"> <div className="flex items-center gap-2 text-xs text-[#555] bg-[#1a1a1a] border border-[#252525] rounded px-3 py-2">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#3b82f6]"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#ffb800]">
<circle cx="12" cy="12" r="10" /><path d="M12 8v4l3 3" /> <circle cx="12" cy="12" r="10" /><path d="M12 8v4l3 3" />
</svg> </svg>
{posts.length} posts imported {posts.length} posts imported
@@ -178,7 +178,7 @@ export default function WPImportPage() {
{/* Import Controls */} {/* Import Controls */}
<div className="bg-[#111111] border border-[#252525] rounded-lg p-5"> <div className="bg-[#111111] border border-[#252525] rounded-lg p-5">
<h2 className="text-base font-bold text-white font-heading mb-4 flex items-center gap-2"> <h2 className="text-base font-bold text-white font-heading mb-4 flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#3b82f6]"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#ffb800]">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" />
</svg> </svg>
Run Import Run Import
@@ -191,7 +191,7 @@ export default function WPImportPage() {
value={maxPages} value={maxPages}
onChange={(e) => setMaxPages(Number(e.target.value))} onChange={(e) => setMaxPages(Number(e.target.value))}
disabled={isImporting} disabled={isImporting}
className="bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] disabled:opacity-50"> className="bg-[#1a1a1a] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800] disabled:opacity-50">
<option value={1}>1 page (~20 posts)</option> <option value={1}>1 page (~20 posts)</option>
<option value={3}>3 pages (~60 posts)</option> <option value={3}>3 pages (~60 posts)</option>
<option value={5}>5 pages (~100 posts)</option> <option value={5}>5 pages (~100 posts)</option>
@@ -204,7 +204,7 @@ export default function WPImportPage() {
<button <button
onClick={handleImport} onClick={handleImport}
disabled={isImporting} disabled={isImporting}
className="flex items-center gap-2 bg-[#3b82f6] hover:bg-[#2563eb] disabled:bg-[#1e3a5f] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors"> className="flex items-center gap-2 bg-[#ffb800] hover:bg-[#d99700] disabled:bg-[#4a3500] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors">
{isImporting ? ( {isImporting ? (
<> <>
<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,16 +223,16 @@ export default function WPImportPage() {
{/* Import Progress / Result */} {/* Import Progress / Result */}
{isImporting && ( {isImporting && (
<div className="mt-4 bg-[#0d1a2e] border border-[#1e3a5f] rounded p-4"> <div className="mt-4 bg-[#0d1a2e] border border-[#4a3500] rounded p-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<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-[#ffb800] 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-[#ffb800] text-sm font-bold">Import in progress</p>
<p className="text-[#666] text-xs mt-0.5">Fetching posts from avbeat.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-[#1a1208] rounded-full overflow-hidden">
<div className="h-full bg-[#3b82f6] rounded-full animate-pulse w-2/3" /> <div className="h-full bg-[#ffb800] rounded-full animate-pulse w-2/3" />
</div> </div>
</div> </div>
)} )}
@@ -261,7 +261,7 @@ export default function WPImportPage() {
{[ {[
{ label: 'Fetched', value: importResult.totalFetched, color: 'text-[#cccccc]' }, { label: 'Fetched', value: importResult.totalFetched, color: 'text-[#cccccc]' },
{ label: 'Imported', value: importResult.totalImported, color: 'text-green-400' }, { label: 'Imported', value: importResult.totalImported, color: 'text-green-400' },
{ label: 'Pages', value: importResult.pagesProcessed, color: 'text-[#3b82f6]' }, { label: 'Pages', value: importResult.pagesProcessed, color: 'text-[#ffb800]' },
{ label: 'Errors', value: importResult.totalErrors, color: importResult.totalErrors ? 'text-red-400' : 'text-[#555]' }, { label: 'Errors', value: importResult.totalErrors, color: importResult.totalErrors ? 'text-red-400' : 'text-[#555]' },
].map((stat) => ( ].map((stat) => (
<div key={stat.label} className="bg-[#111] rounded p-2.5 text-center"> <div key={stat.label} className="bg-[#111] rounded p-2.5 text-center">
@@ -284,7 +284,7 @@ export default function WPImportPage() {
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-5 py-2.5 text-sm font-bold uppercase tracking-wider border-b-2 transition-colors ${ className={`px-5 py-2.5 text-sm font-bold uppercase tracking-wider border-b-2 transition-colors ${
activeTab === tab activeTab === tab
? 'border-[#3b82f6] text-[#3b82f6]' ? 'border-[#ffb800] text-[#ffb800]'
: 'border-transparent text-[#666] hover:text-[#999]' : 'border-transparent text-[#666] hover:text-[#999]'
}`}> }`}>
{tab === 'posts' ? `Imported Posts (${posts.length})` : `Import History (${logs.length})`} {tab === 'posts' ? `Imported Posts (${posts.length})` : `Import History (${logs.length})`}
@@ -306,14 +306,14 @@ export default function WPImportPage() {
placeholder="Search posts..." placeholder="Search posts..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#252525] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="w-full bg-[#111] border border-[#252525] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
/> />
</div> </div>
</div> </div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filteredPosts.length === 0 ? ( ) : filteredPosts.length === 0 ? (
<div className="text-center py-16 bg-[#111] border border-[#252525] rounded-lg"> <div className="text-center py-16 bg-[#111] border border-[#252525] rounded-lg">
@@ -354,7 +354,7 @@ export default function WPImportPage() {
<h3 className="text-sm font-bold text-[#cccccc] leading-snug line-clamp-1">{post.title}</h3> <h3 className="text-sm font-bold text-[#cccccc] leading-snug line-clamp-1">{post.title}</h3>
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 mt-1"> <div className="flex flex-wrap items-center gap-x-3 gap-y-1 mt-1">
{post.category && ( {post.category && (
<span className="text-[10px] font-bold uppercase tracking-wider text-[#3b82f6] bg-[#0d1a2e] px-1.5 py-0.5 rounded"> <span className="text-[10px] font-bold uppercase tracking-wider text-[#ffb800] bg-[#0d1a2e] px-1.5 py-0.5 rounded">
{post.category} {post.category}
</span> </span>
)} )}
@@ -368,7 +368,7 @@ export default function WPImportPage() {
href={`https://www.avbeat.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-[#ffb800] transition-colors p-1"
title="View on avbeat.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" />
@@ -402,7 +402,7 @@ export default function WPImportPage() {
<div> <div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : logs.length === 0 ? ( ) : logs.length === 0 ? (
<div className="text-center py-16 bg-[#111] border border-[#252525] rounded-lg"> <div className="text-center py-16 bg-[#111] border border-[#252525] rounded-lg">

View File

@@ -67,7 +67,7 @@ function fmtDate(dateStr: string): string {
// ─── Sub-components ─────────────────────────────────────────────────────────── // ─── Sub-components ───────────────────────────────────────────────────────────
function StatCard({ label, value, sub, accent = '#3b82f6', icon }: { function StatCard({ label, value, sub, accent = '#ffb800', icon }: {
label: string; value: string; sub: string; accent?: string; icon: React.ReactNode; label: string; value: string; sub: string; accent?: string; icon: React.ReactNode;
}) { }) {
return ( return (
@@ -243,7 +243,7 @@ export default function NewsletterAnalyticsPage() {
if (loading || loadingData) { if (loading || loadingData) {
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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -308,7 +308,7 @@ export default function NewsletterAnalyticsPage() {
label="Avg Open Rate" label="Avg Open Rate"
value={pct(avgOpenRate)} value={pct(avgOpenRate)}
sub={`across ${digestData.length} digests`} sub={`across ${digestData.length} digests`}
accent="#3b82f6" accent="#ffb800"
icon={ icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
@@ -362,7 +362,7 @@ export default function NewsletterAnalyticsPage() {
<YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => `${v}%`} /> <YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => `${v}%`} />
<Tooltip content={<ChartTooltip />} /> <Tooltip content={<ChartTooltip />} />
<Legend wrapperStyle={{ fontSize: 11, color: '#888' }} /> <Legend wrapperStyle={{ fontSize: 11, color: '#888' }} />
<Line type="monotone" dataKey="Open Rate" stroke="#3b82f6" strokeWidth={2} dot={{ r: 3, fill: '#3b82f6' }} activeDot={{ r: 5 }} /> <Line type="monotone" dataKey="Open Rate" stroke="#ffb800" strokeWidth={2} dot={{ r: 3, fill: '#ffb800' }} activeDot={{ r: 5 }} />
<Line type="monotone" dataKey="Click Rate" stroke="#10b981" strokeWidth={2} dot={{ r: 3, fill: '#10b981' }} activeDot={{ r: 5 }} /> <Line type="monotone" dataKey="Click Rate" stroke="#10b981" strokeWidth={2} dot={{ r: 3, fill: '#10b981' }} activeDot={{ r: 5 }} />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -380,7 +380,7 @@ export default function NewsletterAnalyticsPage() {
<YAxis tick={{ fill: '#555', fontSize: 11 }} tickLine={false} axisLine={false} /> <YAxis tick={{ fill: '#555', fontSize: 11 }} tickLine={false} axisLine={false} />
<Tooltip content={<ChartTooltip />} /> <Tooltip content={<ChartTooltip />} />
<Legend wrapperStyle={{ fontSize: 11, color: '#888' }} /> <Legend wrapperStyle={{ fontSize: 11, color: '#888' }} />
<Bar dataKey="subscribers" name="Total Subscribers" fill="#3b82f6" radius={[3, 3, 0, 0]} /> <Bar dataKey="subscribers" name="Total Subscribers" fill="#ffb800" radius={[3, 3, 0, 0]} />
<Bar dataKey="unsubscribes" name="Unsubscribes" fill="#ef4444" radius={[3, 3, 0, 0]} /> <Bar dataKey="unsubscribes" name="Unsubscribes" fill="#ef4444" radius={[3, 3, 0, 0]} />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>

View File

@@ -252,7 +252,7 @@ export default function CampaignEditorPage() {
article_blocks: payload.article_blocks, article_blocks: payload.article_blocks,
layout: 'featured', layout: 'featured',
style: 'dark', style: 'dark',
accent_color: '#3b82f6', accent_color: '#ffb800',
is_preset: false, is_preset: false,
}), }),
}); });

View File

@@ -356,7 +356,7 @@ export default function AdminNewsletterPage() {
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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -397,7 +397,7 @@ export default function AdminNewsletterPage() {
</Link> </Link>
<Link <Link
href="/admin/newsletter/campaign-editor" 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-[#3b82f6] hover:bg-[#2563eb] 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-[#ffb800] hover:bg-[#d99700] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <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" /> <path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
</svg> </svg>
@@ -430,12 +430,12 @@ export default function AdminNewsletterPage() {
<div className="flex gap-1 mb-6 bg-[#111] border border-[#252525] rounded-lg p-1 w-fit"> <div className="flex gap-1 mb-6 bg-[#111] border border-[#252525] rounded-lg p-1 w-fit">
<button <button
onClick={() => setActiveTab('subscribers')} onClick={() => setActiveTab('subscribers')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'subscribers' ? 'bg-[#3b82f6] 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-[#ffb800] text-white' : 'text-[#888] hover:text-white'}`}>
Subscribers Subscribers
</button> </button>
<button <button
onClick={() => setActiveTab('send-digest')} onClick={() => setActiveTab('send-digest')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'send-digest' ? 'bg-[#3b82f6] 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-[#ffb800] text-white' : 'text-[#888] hover:text-white'}`}>
Send Digest Send Digest
</button> </button>
</div> </div>
@@ -457,7 +457,7 @@ export default function AdminNewsletterPage() {
<button <button
key={s} key={s}
onClick={() => setStatusFilter(s)} onClick={() => setStatusFilter(s)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-colors ${statusFilter === s ? 'bg-[#3b82f6] 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-[#ffb800] text-white' : 'text-[#888] hover:text-white'}`}
> >
{s} {s}
</button> </button>
@@ -478,9 +478,9 @@ export default function AdminNewsletterPage() {
{/* Bulk Actions Bar */} {/* Bulk Actions Bar */}
{someSelected && ( {someSelected && (
<div className="flex items-center justify-between bg-[#1a2535] border border-[#1e3a5f] rounded-lg px-4 py-3 mb-4"> <div className="flex items-center justify-between bg-[#1a1208] border border-[#4a3500] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-sm font-body font-semibold text-[#3b82f6]"> <span className="text-sm font-body font-semibold text-[#ffb800]">
{selectedIds.size} selected {selectedIds.size} selected
</span> </span>
<button <button
@@ -512,7 +512,7 @@ export default function AdminNewsletterPage() {
Delete Delete
</button> </button>
{bulkLoading && ( {bulkLoading && (
<div className="w-4 h-4 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
)} )}
</div> </div>
</div> </div>
@@ -522,7 +522,7 @@ export default function AdminNewsletterPage() {
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : subscribers.length === 0 ? ( ) : subscribers.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
@@ -540,7 +540,7 @@ export default function AdminNewsletterPage() {
type="checkbox" type="checkbox"
checked={allSelected} checked={allSelected}
onChange={toggleSelectAll} onChange={toggleSelectAll}
className="w-4 h-4 rounded border-[#444] bg-[#1a1a1a] accent-[#3b82f6] cursor-pointer" className="w-4 h-4 rounded border-[#444] bg-[#1a1a1a] accent-[#ffb800] cursor-pointer"
/> />
</th> </th>
<th className="text-left px-4 py-3 text-xs font-body font-bold text-[#555] uppercase tracking-wider">Email</th> <th className="text-left px-4 py-3 text-xs font-body font-bold text-[#555] uppercase tracking-wider">Email</th>
@@ -561,14 +561,14 @@ export default function AdminNewsletterPage() {
type="checkbox" type="checkbox"
checked={selectedIds.has(sub.id)} checked={selectedIds.has(sub.id)}
onChange={() => toggleSelect(sub.id)} onChange={() => toggleSelect(sub.id)}
className="w-4 h-4 rounded border-[#444] bg-[#1a1a1a] accent-[#3b82f6] cursor-pointer" className="w-4 h-4 rounded border-[#444] bg-[#1a1a1a] accent-[#ffb800] cursor-pointer"
/> />
</td> </td>
<td className="px-4 py-3 text-sm font-body text-[#e0e0e0]">{sub.email}</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"> <td className="px-4 py-3 hidden md:table-cell">
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{sub.topics?.slice(0, 3).map((t) => ( {sub.topics?.slice(0, 3).map((t) => (
<span key={t} className="text-xs bg-[#1a2535] text-[#3b82f6] px-2 py-0.5 rounded-full font-body">{t}</span> <span key={t} className="text-xs bg-[#1a1208] text-[#ffb800] px-2 py-0.5 rounded-full font-body">{t}</span>
))} ))}
{sub.topics?.length > 3 && ( {sub.topics?.length > 3 && (
<span className="text-xs text-[#555] font-body">+{sub.topics.length - 3}</span> <span className="text-xs text-[#555] font-body">+{sub.topics.length - 3}</span>
@@ -616,21 +616,21 @@ export default function AdminNewsletterPage() {
{activeTab === 'send-digest' && ( {activeTab === 'send-digest' && (
<div className="max-w-2xl"> <div className="max-w-2xl">
{/* SMTP warning if not configured */} {/* SMTP warning if not configured */}
<div className="bg-[#1a2535] border border-[#1e3a5f] rounded-lg p-4 mb-6 flex gap-3"> <div className="bg-[#1a1208] border border-[#4a3500] rounded-lg p-4 mb-6 flex gap-3">
<svg className="w-5 h-5 text-[#3b82f6] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-[#ffb800] 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" /> <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> </svg>
<div> <div>
<p className="text-[#e0e0e0] text-sm font-body font-semibold">SMTP Required</p> <p className="text-[#e0e0e0] text-sm font-body font-semibold">SMTP Required</p>
<p className="text-[#777] text-xs font-body mt-0.5"> <p className="text-[#777] text-xs font-body mt-0.5">
Set <code className="text-[#3b82f6]">SMTP_HOST</code>, <code className="text-[#3b82f6]">SMTP_USER</code>, <code className="text-[#3b82f6]">SMTP_PASS</code>, and <code className="text-[#3b82f6]">SMTP_FROM_EMAIL</code> in your environment variables to enable sending. Set <code className="text-[#ffb800]">SMTP_HOST</code>, <code className="text-[#ffb800]">SMTP_USER</code>, <code className="text-[#ffb800]">SMTP_PASS</code>, and <code className="text-[#ffb800]">SMTP_FROM_EMAIL</code> in your environment variables to enable sending.
</p> </p>
</div> </div>
</div> </div>
{/* Applied template badge */} {/* Applied template badge */}
{appliedTemplate && ( {appliedTemplate && (
<div className="flex items-center justify-between bg-[#1a2535] border border-[#1e3a5f] rounded-lg px-4 py-3 mb-4"> <div className="flex items-center justify-between bg-[#1a1208] border border-[#4a3500] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center gap-2"> <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' }}> <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 }}> <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> <p className="text-xs font-body text-[#555]">Fill in the fields below or start from a template</p>
<button <button
onClick={() => { fetchTemplates(); setShowTemplateDrawer(true); }} onClick={() => { fetchTemplates(); setShowTemplateDrawer(true); }}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#3b82f6] border border-[#1e3a5f] rounded-lg hover:bg-[#1a2535] transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#ffb800] border border-[#4a3500] rounded-lg hover:bg-[#1a1208] transition-colors"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <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" /> <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,7 +725,7 @@ export default function AdminNewsletterPage() {
<button <button
type="button" type="button"
onClick={addArticle} onClick={addArticle}
className="text-xs text-[#3b82f6] hover:text-blue-400 font-body font-semibold transition-colors" className="text-xs text-[#ffb800] hover:text-blue-400 font-body font-semibold transition-colors"
> >
+ Add Article + Add Article
</button> </button>
@@ -807,7 +807,7 @@ export default function AdminNewsletterPage() {
<div className="flex items-center justify-between px-6 py-4 border-b border-[#252525] flex-shrink-0"> <div className="flex items-center justify-between px-6 py-4 border-b border-[#252525] flex-shrink-0">
<h2 className="font-display font-bold text-white text-base">Choose a Template</h2> <h2 className="font-display font-bold text-white text-base">Choose a Template</h2>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin/newsletter/templates" className="text-xs text-[#3b82f6] font-body hover:text-blue-400 transition-colors"> <Link href="/admin/newsletter/templates" className="text-xs text-[#ffb800] font-body hover:text-blue-400 transition-colors">
Manage templates Manage templates
</Link> </Link>
<button onClick={() => setShowTemplateDrawer(false)} className="text-[#555] hover:text-white transition-colors"> <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"> <div className="overflow-y-auto flex-1 p-4">
{loadingTemplates ? ( {loadingTemplates ? (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : templates.length === 0 ? ( ) : templates.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<p className="text-[#555] font-body text-sm">No templates found.</p> <p className="text-[#555] font-body text-sm">No templates found.</p>
<Link href="/admin/newsletter/templates" className="text-[#3b82f6] text-sm font-body hover:text-blue-400 mt-2 inline-block"> <Link href="/admin/newsletter/templates" className="text-[#ffb800] text-sm font-body hover:text-blue-400 mt-2 inline-block">
Create your first template Create your first template
</Link> </Link>
</div> </div>
@@ -835,7 +835,7 @@ export default function AdminNewsletterPage() {
<button <button
key={tpl.id} key={tpl.id}
onClick={() => applyTemplate(tpl)} onClick={() => applyTemplate(tpl)}
className="flex items-start gap-3 p-4 bg-[#0d0d0d] border border-[#252525] rounded-xl text-left hover:border-[#3b82f6] hover:bg-[#1a2535] transition-colors group" className="flex items-start gap-3 p-4 bg-[#0d0d0d] border border-[#252525] rounded-xl text-left hover:border-[#ffb800] hover:bg-[#1a1208] 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' }}> <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 }}> <span style={{ color: tpl.accent_color }}>

View File

@@ -42,7 +42,7 @@ const STYLE_OPTIONS = [
]; ];
const ACCENT_COLORS = [ const ACCENT_COLORS = [
'#3b82f6', '#ef4444', '#10b981', '#8b5cf6', '#f59e0b', '#ec4899', '#06b6d4', '#f97316', '#ffb800', '#ef4444', '#10b981', '#8b5cf6', '#f59e0b', '#ec4899', '#06b6d4', '#f97316',
]; ];
const LAYOUT_ICONS: Record<string, React.ReactNode> = { const LAYOUT_ICONS: Record<string, React.ReactNode> = {
@@ -83,7 +83,7 @@ const emptyForm = (): Omit<NewsletterTemplate, 'id' | 'is_preset' | 'created_at'
subject_prefix: '', subject_prefix: '',
header_text: '', header_text: '',
footer_text: '', footer_text: '',
accent_color: '#3b82f6', accent_color: '#ffb800',
article_blocks: [{ title: '', excerpt: '', url: '', category: '' }], article_blocks: [{ title: '', excerpt: '', url: '', category: '' }],
}); });
@@ -226,7 +226,7 @@ export default function NewsletterTemplatesPage() {
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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -272,11 +272,11 @@ export default function NewsletterTemplatesPage() {
<div className="mb-8"> <div className="mb-8">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<h2 className="font-display font-bold text-white text-base">Preset Templates</h2> <h2 className="font-display font-bold text-white text-base">Preset Templates</h2>
<span className="text-xs bg-[#1a2535] text-[#3b82f6] px-2 py-0.5 rounded-full font-body">{presets.length}</span> <span className="text-xs bg-[#1a1208] text-[#ffb800] px-2 py-0.5 rounded-full font-body">{presets.length}</span>
</div> </div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@@ -298,7 +298,7 @@ export default function NewsletterTemplatesPage() {
<div> <div>
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<h2 className="font-display font-bold text-white text-base">Custom Templates</h2> <h2 className="font-display font-bold text-white text-base">Custom Templates</h2>
<span className="text-xs bg-[#1a2535] text-[#3b82f6] px-2 py-0.5 rounded-full font-body">{custom.length}</span> <span className="text-xs bg-[#1a1208] text-[#ffb800] px-2 py-0.5 rounded-full font-body">{custom.length}</span>
</div> </div>
{!loadingData && custom.length === 0 ? ( {!loadingData && custom.length === 0 ? (
<div className="bg-[#111] border border-[#252525] rounded-lg p-10 text-center"> <div className="bg-[#111] border border-[#252525] rounded-lg p-10 text-center">
@@ -306,7 +306,7 @@ export default function NewsletterTemplatesPage() {
<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" /> <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> </svg>
<p className="text-[#555] font-body text-sm mb-3">No custom templates yet</p> <p className="text-[#555] font-body text-sm mb-3">No custom templates yet</p>
<button onClick={openNew} className="text-[#3b82f6] text-sm font-body font-semibold hover:text-blue-400 transition-colors"> <button onClick={openNew} className="text-[#ffb800] text-sm font-body font-semibold hover:text-blue-400 transition-colors">
Create your first template Create your first template
</button> </button>
</div> </div>
@@ -376,8 +376,8 @@ export default function NewsletterTemplatesPage() {
key={opt.value} key={opt.value}
type="button" type="button"
onClick={() => setForm((f) => ({ ...f, layout: opt.value as NewsletterTemplate['layout'] }))} onClick={() => setForm((f) => ({ ...f, layout: opt.value as NewsletterTemplate['layout'] }))}
className={`flex items-start gap-3 p-3 rounded-lg border text-left transition-colors ${form.layout === opt.value ? 'border-[#3b82f6] bg-[#1a2535]' : 'border-[#252525] bg-[#0d0d0d] hover:border-[#333]'}`}> className={`flex items-start gap-3 p-3 rounded-lg border text-left transition-colors ${form.layout === opt.value ? 'border-[#ffb800] bg-[#1a1208]' : 'border-[#252525] bg-[#0d0d0d] hover:border-[#333]'}`}>
<span className={`mt-0.5 ${form.layout === opt.value ? 'text-[#3b82f6]' : 'text-[#555]'}`}> <span className={`mt-0.5 ${form.layout === opt.value ? 'text-[#ffb800]' : 'text-[#555]'}`}>
{LAYOUT_ICONS[opt.value]} {LAYOUT_ICONS[opt.value]}
</span> </span>
<div> <div>
@@ -398,7 +398,7 @@ export default function NewsletterTemplatesPage() {
key={opt.value} key={opt.value}
type="button" type="button"
onClick={() => setForm((f) => ({ ...f, style: opt.value as NewsletterTemplate['style'] }))} onClick={() => setForm((f) => ({ ...f, style: opt.value as NewsletterTemplate['style'] }))}
className={`flex-1 py-2.5 px-3 rounded-lg border text-sm font-body font-semibold transition-colors ${form.style === opt.value ? 'border-[#3b82f6] bg-[#1a2535] text-white' : 'border-[#252525] bg-[#0d0d0d] text-[#888] hover:border-[#333]'}`}> className={`flex-1 py-2.5 px-3 rounded-lg border text-sm font-body font-semibold transition-colors ${form.style === opt.value ? 'border-[#ffb800] bg-[#1a1208] text-white' : 'border-[#252525] bg-[#0d0d0d] text-[#888] hover:border-[#333]'}`}>
{opt.label} {opt.label}
</button> </button>
))} ))}
@@ -469,7 +469,7 @@ export default function NewsletterTemplatesPage() {
<div> <div>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<label className="text-xs font-body font-bold text-[#888] uppercase tracking-wider">Article Block Slots</label> <label className="text-xs font-body font-bold text-[#888] uppercase tracking-wider">Article Block Slots</label>
<button type="button" onClick={addBlock} className="text-xs text-[#3b82f6] hover:text-blue-400 font-body font-semibold transition-colors"> <button type="button" onClick={addBlock} className="text-xs text-[#ffb800] hover:text-blue-400 font-body font-semibold transition-colors">
+ Add Slot + Add Slot
</button> </button>
</div> </div>
@@ -486,7 +486,7 @@ export default function NewsletterTemplatesPage() {
type="checkbox" type="checkbox"
checked={!!block.featured} checked={!!block.featured}
onChange={(e) => updateBlock(i, 'featured', e.target.checked)} onChange={(e) => updateBlock(i, 'featured', e.target.checked)}
className="w-3 h-3 accent-[#3b82f6]" className="w-3 h-3 accent-[#ffb800]"
/> />
<span className="text-xs font-body text-[#888]">Featured</span> <span className="text-xs font-body text-[#888]">Featured</span>
</label> </label>
@@ -597,7 +597,7 @@ export default function NewsletterTemplatesPage() {
{previewTemplate.article_blocks.map((b, i) => ( {previewTemplate.article_blocks.map((b, i) => (
<div key={i} className="flex items-center gap-2 bg-[#0d0d0d] rounded px-3 py-2"> <div key={i} className="flex items-center gap-2 bg-[#0d0d0d] rounded px-3 py-2">
<span className="text-xs text-[#555] font-body w-12 flex-shrink-0">Slot {i + 1}</span> <span className="text-xs text-[#555] font-body w-12 flex-shrink-0">Slot {i + 1}</span>
{b.featured && <span className="text-xs text-[#3b82f6] font-body bg-[#1a2535] px-1.5 py-0.5 rounded">Featured</span>} {b.featured && <span className="text-xs text-[#ffb800] font-body bg-[#1a1208] px-1.5 py-0.5 rounded">Featured</span>}
{b.category && <span className="text-xs text-[#888] font-body">{b.category}</span>} {b.category && <span className="text-xs text-[#888] font-body">{b.category}</span>}
{b.title && <span className="text-xs text-[#e0e0e0] font-body truncate">{b.title}</span>} {b.title && <span className="text-xs text-[#e0e0e0] font-body truncate">{b.title}</span>}
</div> </div>
@@ -668,12 +668,12 @@ function TemplateCard({ template, onEdit, onDelete, onPreview, deletingId }: Tem
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => onPreview(template)} onClick={() => onPreview(template)}
className="flex-1 py-1.5 text-xs font-body font-semibold text-[#3b82f6] border border-[#1e3a5f] rounded-lg hover:bg-[#1a2535] transition-colors"> className="flex-1 py-1.5 text-xs font-body font-semibold text-[#ffb800] border border-[#4a3500] rounded-lg hover:bg-[#1a1208] transition-colors">
Preview Preview
</button> </button>
<Link <Link
href={`/admin/newsletter?template=${template.id}`} href={`/admin/newsletter?template=${template.id}`}
className="flex-1 py-1.5 text-xs font-body font-semibold text-center text-white bg-[#1a2535] border border-[#1e3a5f] rounded-lg hover:bg-[#1e3a5f] transition-colors"> className="flex-1 py-1.5 text-xs font-body font-semibold text-center text-white bg-[#1a1208] border border-[#4a3500] rounded-lg hover:bg-[#4a3500] transition-colors">
Use Use
</Link> </Link>
{!template.is_preset && ( {!template.is_preset && (

View File

@@ -244,7 +244,7 @@ function NotificationRow({ notification: n, onMarkRead, onDismiss }: Notificatio
{n.actionUrl && ( {n.actionUrl && (
<Link <Link
href={n.actionUrl} href={n.actionUrl}
className="text-xs font-body font-bold text-[#3b82f6] hover:text-blue-300 transition-colors" className="text-xs font-body font-bold text-[#ffb800] hover:text-blue-300 transition-colors"
> >
{n.actionLabel || 'View →'} {n.actionLabel || 'View →'}
</Link> </Link>
@@ -474,7 +474,7 @@ export default function NotificationsPage() {
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="text-center"> <div className="text-center">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin mx-auto mb-3" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin mx-auto mb-3" />
<p className="text-[#555] text-sm font-body">Loading notifications</p> <p className="text-[#555] text-sm font-body">Loading notifications</p>
</div> </div>
</div> </div>
@@ -512,7 +512,7 @@ export default function NotificationsPage() {
{stats.unread > 0 && ( {stats.unread > 0 && (
<button <button
onClick={handleMarkAllRead} onClick={handleMarkAllRead}
className="px-3 py-1.5 text-xs font-body font-bold text-[#3b82f6] border border-[#3b82f6]/30 rounded hover:bg-[#3b82f6]/10 transition-colors" className="px-3 py-1.5 text-xs font-body font-bold text-[#ffb800] border border-[#ffb800]/30 rounded hover:bg-[#ffb800]/10 transition-colors"
> >
Mark all read Mark all read
</button> </button>
@@ -558,7 +558,7 @@ export default function NotificationsPage() {
label="Unread" label="Unread"
value={stats.unread} value={stats.unread}
icon={<BellIcon className="w-4 h-4" />} icon={<BellIcon className="w-4 h-4" />}
color="text-[#3b82f6] bg-[#3b82f6]/5 border-[#3b82f6]/30" color="text-[#ffb800] bg-[#ffb800]/5 border-[#ffb800]/30"
active={filter === 'unread'} active={filter === 'unread'}
onClick={() => setFilter(filter === 'unread' ? 'all' : 'unread')} onClick={() => setFilter(filter === 'unread' ? 'all' : 'unread')}
/> />
@@ -580,7 +580,7 @@ export default function NotificationsPage() {
onClick={() => setFilter(f)} onClick={() => setFilter(f)}
className={`px-3 py-1.5 text-xs font-body font-bold rounded transition-colors ${ className={`px-3 py-1.5 text-xs font-body font-bold rounded transition-colors ${
filter === f filter === f
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#555] hover:text-[#888] hover:bg-[#111]' : 'text-[#555] hover:text-[#888] hover:bg-[#111]'
}`} }`}
> >

View File

@@ -125,15 +125,15 @@ interface QuickActionProps {
function QuickAction({ label, description, href, icon, accent }: QuickActionProps) { function QuickAction({ label, description, href, icon, accent }: QuickActionProps) {
return ( return (
<Link href={href} className="flex items-center gap-3 p-3 bg-[#111] border border-[#252525] rounded-lg hover:border-[#3b82f6] hover:bg-[#0d1a2d] transition-all group"> <Link href={href} className="flex items-center gap-3 p-3 bg-[#111] border border-[#252525] rounded-lg hover:border-[#ffb800] hover:bg-[#0d1a2d] transition-all group">
<div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${accent} group-hover:scale-105 transition-transform`}> <div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${accent} group-hover:scale-105 transition-transform`}>
{icon} {icon}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<p className="text-sm font-body font-semibold text-white group-hover:text-[#3b82f6] transition-colors">{label}</p> <p className="text-sm font-body font-semibold text-white group-hover:text-[#ffb800] transition-colors">{label}</p>
<p className="text-xs text-[#555] font-body truncate">{description}</p> <p className="text-xs text-[#555] font-body truncate">{description}</p>
</div> </div>
<svg className="w-4 h-4 text-[#444] group-hover:text-[#3b82f6] ml-auto flex-shrink-0 transition-colors" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#444] group-hover:text-[#ffb800] ml-auto flex-shrink-0 transition-colors" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg> </svg>
</Link> </Link>
@@ -239,7 +239,7 @@ export default function AdminDashboardPage() {
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="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading dashboard</p> <p className="text-[#555] text-sm font-body">Loading dashboard</p>
</div> </div>
</div> </div>
@@ -375,7 +375,7 @@ export default function AdminDashboardPage() {
<div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#252525] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#1a1a1a] flex items-center justify-between"> <div className="px-5 py-4 border-b border-[#1a1a1a] flex items-center justify-between">
<h2 className="text-sm font-display font-bold text-white">Recent Activity</h2> <h2 className="text-sm font-display font-bold text-white">Recent Activity</h2>
<Link href="/admin/articles" className="text-xs text-[#3b82f6] hover:text-blue-300 font-body transition-colors">View all </Link> <Link href="/admin/articles" className="text-xs text-[#ffb800] hover:text-blue-300 font-body transition-colors">View all </Link>
</div> </div>
<div className="divide-y divide-[#1a1a1a]"> <div className="divide-y divide-[#1a1a1a]">
{recentActivity.length === 0 ? ( {recentActivity.length === 0 ? (

View File

@@ -41,7 +41,7 @@ export default function ReviewActions({ id, currentStatus }: Props) {
value={note} value={note}
onChange={(e) => setNote(e.target.value)} onChange={(e) => setNote(e.target.value)}
placeholder="Reviewer note (optional)" placeholder="Reviewer note (optional)"
className="w-full rounded border border-[#1f2937] bg-[#070a10] p-2 text-sm text-[#e5e7eb] focus:border-[#3b82f6] focus:outline-none" className="w-full rounded border border-[#1f2937] bg-[#070a10] p-2 text-sm text-[#e5e7eb] focus:border-[#ffb800] focus:outline-none"
rows={2} rows={2}
/> />
<div className="mt-3 flex flex-wrap gap-3 text-sm"> <div className="mt-3 flex flex-wrap gap-3 text-sm">

View File

@@ -41,12 +41,12 @@ export default async function ReviewDetailPage({ params }: { params: Promise<{ i
return ( return (
<main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]"> <main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]">
<Link href="/admin/review-queue" className="text-sm text-[#60a5fa] hover:underline"> <Link href="/admin/review-queue" className="text-sm text-[#ffd25a] hover:underline">
Back to queue Back to queue
</Link> </Link>
<header className="mt-4 mb-6 border-b border-[#222] pb-4"> <header className="mt-4 mb-6 border-b border-[#222] pb-4">
<div className="flex items-center gap-3 text-xs uppercase tracking-wider text-[#60a5fa]"> <div className="flex items-center gap-3 text-xs uppercase tracking-wider text-[#ffd25a]">
<span>{persona?.name || "—"}</span> <span>{persona?.name || "—"}</span>
<span className="text-[#4b5563]">·</span> <span className="text-[#4b5563]">·</span>
<span>{art.category}</span> <span>{art.category}</span>

View File

@@ -70,7 +70,7 @@ export default async function ReviewQueuePage() {
return ( return (
<li <li
key={r.id} key={r.id}
className="rounded border border-[#1f2937] bg-[#0b0f17] p-5 hover:border-[#3b82f6] transition-colors" className="rounded border border-[#1f2937] bg-[#0b0f17] p-5 hover:border-[#ffb800] transition-colors"
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
{persona?.avatar_url ? ( {persona?.avatar_url ? (
@@ -85,7 +85,7 @@ export default async function ReviewQueuePage() {
<div className="h-14 w-14 rounded-full bg-[#1f2937]" /> <div className="h-14 w-14 rounded-full bg-[#1f2937]" />
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-xs uppercase tracking-wider text-[#60a5fa]"> <div className="text-xs uppercase tracking-wider text-[#ffd25a]">
{persona?.name || "—"} · {r.category} {persona?.name || "—"} · {r.category}
</div> </div>
<h2 className="text-xl font-semibold mt-1">{r.title}</h2> <h2 className="text-xl font-semibold mt-1">{r.title}</h2>
@@ -100,7 +100,7 @@ export default async function ReviewQueuePage() {
<div className="mt-4 flex flex-wrap items-center gap-3 text-xs"> <div className="mt-4 flex flex-wrap items-center gap-3 text-xs">
<Link <Link
href={`/admin/review-queue/${r.id}`} href={`/admin/review-queue/${r.id}`}
className="rounded bg-[#1d4ed8] px-3 py-1.5 font-medium text-white hover:bg-[#2563eb]" className="rounded bg-[#1d4ed8] px-3 py-1.5 font-medium text-white hover:bg-[#d99700]"
> >
Review Review
</Link> </Link>

View File

@@ -12,7 +12,7 @@ export default function RmpHubPage() {
useEffect(() => { if (!loading && !user) router?.push('/login'); }, [user, loading, router]); useEffect(() => { if (!loading && !user) router?.push('/login'); }, [user, loading, router]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -83,14 +83,14 @@ export default function RmpHubPage() {
</Link> </Link>
))} ))}
</div> </div>
<Link href={section?.href} className="block w-full text-center px-3 py-1.5 bg-[#1a2535] border border-[#2a3a50] rounded text-xs text-[#3b82f6] hover:bg-[#0d1a2d] transition-colors"> <Link href={section?.href} className="block w-full text-center px-3 py-1.5 bg-[#1a1208] border border-[#2a3a50] rounded text-xs text-[#ffb800] hover:bg-[#0d1a2d] transition-colors">
Open {section?.title} Open {section?.title}
</Link> </Link>
</div> </div>
))} ))}
</div> </div>
<div className="bg-[#0d1117] border border-[#1a2535] rounded-lg p-4"> <div className="bg-[#0d1117] border border-[#1a1208] rounded-lg p-4">
<p className="text-xs text-[#555] mb-3">Quick Navigation</p> <p className="text-xs text-[#555] mb-3">Quick Navigation</p>
<RmpAdminNav /> <RmpAdminNav />
</div> </div>

View File

@@ -324,7 +324,7 @@ export default function AdminRolesPage() {
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="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading roles panel</p> <p className="text-[#555] text-sm font-body">Loading roles panel</p>
</div> </div>
</div> </div>
@@ -376,7 +376,7 @@ export default function AdminRolesPage() {
key={role} key={role}
onClick={() => { setSelectedRole(role === selectedRole ? 'all' : role); setActiveTab('assign'); }} onClick={() => { setSelectedRole(role === selectedRole ? 'all' : role); setActiveTab('assign'); }}
className={`bg-[#111] border rounded-lg p-4 text-left hover:border-[#333] transition-all ${ className={`bg-[#111] border rounded-lg p-4 text-left hover:border-[#333] transition-all ${
selectedRole === role ? 'border-[#3b82f6] bg-[#0d1a2d]' : 'border-[#252525]' selectedRole === role ? 'border-[#ffb800] bg-[#0d1a2d]' : 'border-[#252525]'
}`} }`}
> >
<div className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-body font-semibold border mb-2 ${color}`}> <div className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-body font-semibold border mb-2 ${color}`}>
@@ -402,7 +402,7 @@ export default function AdminRolesPage() {
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-body font-medium transition-all ${ className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-body font-medium transition-all ${
activeTab === tab.id activeTab === tab.id
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -428,13 +428,13 @@ export default function AdminRolesPage() {
placeholder="Search users…" placeholder="Search users…"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#0d0d0d] border border-[#252525] rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder-[#555] font-body focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#252525] rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder-[#555] font-body focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<select <select
value={selectedRole} value={selectedRole}
onChange={(e) => setSelectedRole(e.target.value)} onChange={(e) => setSelectedRole(e.target.value)}
className="bg-[#0d0d0d] border border-[#252525] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#3b82f6]" className="bg-[#0d0d0d] border border-[#252525] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#ffb800]"
> >
<option value="all">All Roles</option> <option value="all">All Roles</option>
{ROLES.map((r) => ( {ROLES.map((r) => (
@@ -500,14 +500,14 @@ export default function AdminRolesPage() {
defaultValue={u.role} defaultValue={u.role}
onChange={(e) => handleAssignRole(u.id, e.target.value)} onChange={(e) => handleAssignRole(u.id, e.target.value)}
disabled={processingUserId === u.id} disabled={processingUserId === u.id}
className="bg-[#0d0d0d] border border-[#252525] rounded-lg px-2 py-1.5 text-xs text-white font-body focus:outline-none focus:border-[#3b82f6] disabled:opacity-50" className="bg-[#0d0d0d] border border-[#252525] rounded-lg px-2 py-1.5 text-xs text-white font-body focus:outline-none focus:border-[#ffb800] disabled:opacity-50"
> >
{ROLES.map((r) => ( {ROLES.map((r) => (
<option key={r} value={r} className="capitalize">{r.charAt(0).toUpperCase() + r.slice(1)}</option> <option key={r} value={r} className="capitalize">{r.charAt(0).toUpperCase() + r.slice(1)}</option>
))} ))}
</select> </select>
{processingUserId === u.id && ( {processingUserId === u.id && (
<div className="w-4 h-4 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
)} )}
</div> </div>
</td> </td>
@@ -557,7 +557,7 @@ export default function AdminRolesPage() {
<button <button
onClick={handleSavePermissions} onClick={handleSavePermissions}
disabled={savingPermissions} disabled={savingPermissions}
className="px-3 py-1.5 text-xs font-body font-semibold bg-[#3b82f6] hover:bg-blue-500 text-white rounded-lg transition-colors disabled:opacity-50 flex items-center gap-1.5" className="px-3 py-1.5 text-xs font-body font-semibold bg-[#ffb800] hover:bg-blue-500 text-white rounded-lg transition-colors disabled:opacity-50 flex items-center gap-1.5"
> >
{savingPermissions && <div className="w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" />} {savingPermissions && <div className="w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" />}
Save Changes Save Changes
@@ -593,7 +593,7 @@ export default function AdminRolesPage() {
{isEditing ? ( {isEditing ? (
<button <button
onClick={() => setEditedPermissions((prev) => ({ ...prev, [key]: !val }))} onClick={() => setEditedPermissions((prev) => ({ ...prev, [key]: !val }))}
className={`relative w-9 h-5 rounded-full transition-colors ${val ? 'bg-[#3b82f6]' : 'bg-[#333]'}`} className={`relative w-9 h-5 rounded-full transition-colors ${val ? 'bg-[#ffb800]' : 'bg-[#333]'}`}
> >
<span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${val ? 'translate-x-4' : 'translate-x-0.5'}`} /> <span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${val ? 'translate-x-4' : 'translate-x-0.5'}`} />
</button> </button>
@@ -644,7 +644,7 @@ export default function AdminRolesPage() {
onClick={() => setActivityRoleFilter(r)} onClick={() => setActivityRoleFilter(r)}
className={`px-2.5 py-1 rounded-md text-xs font-body font-medium transition-colors capitalize ${ className={`px-2.5 py-1 rounded-md text-xs font-body font-medium transition-colors capitalize ${
activityRoleFilter === r activityRoleFilter === r
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'bg-[#1a1a1a] text-[#888] hover:text-white' : 'bg-[#1a1a1a] text-[#888] hover:text-white'
}`} }`}
> >

View File

@@ -223,7 +223,7 @@ export default function AdminSuspensionsPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center"> <div className="min-h-screen bg-[#0d1117] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -288,7 +288,7 @@ export default function AdminSuspensionsPage() {
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${ className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${
activeTab === tab activeTab === tab
? 'text-[#3b82f6] border-[#3b82f6]' ? 'text-[#ffb800] border-[#ffb800]'
: 'text-[#555] border-transparent hover:text-[#888]' : 'text-[#555] border-transparent hover:text-[#888]'
}`} }`}
> >
@@ -304,7 +304,7 @@ export default function AdminSuspensionsPage() {
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
<> <>
@@ -336,7 +336,7 @@ export default function AdminSuspensionsPage() {
<tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#3b82f6] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#ffb800] font-bold text-sm flex-shrink-0 overflow-hidden">
{s.user?.avatar_url ? ( {s.user?.avatar_url ? (
<img src={s.user.avatar_url} alt={s.user.full_name || s.user.email} className="w-full h-full object-cover" /> <img src={s.user.avatar_url} alt={s.user.full_name || s.user.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -415,7 +415,7 @@ export default function AdminSuspensionsPage() {
<tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#3b82f6] text-xs font-bold flex-shrink-0"> <div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#ffb800] text-xs font-bold flex-shrink-0">
{(s.user?.full_name?.[0] || s.user?.email?.[0] || '?').toUpperCase()} {(s.user?.full_name?.[0] || s.user?.email?.[0] || '?').toUpperCase()}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
@@ -465,7 +465,7 @@ export default function AdminSuspensionsPage() {
placeholder="Search users..." placeholder="Search users..."
value={userSearch} value={userSearch}
onChange={(e) => setUserSearch(e.target.value)} onChange={(e) => setUserSearch(e.target.value)}
className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
</div> </div>
@@ -488,7 +488,7 @@ export default function AdminSuspensionsPage() {
<tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#3b82f6] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#ffb800] font-bold text-sm flex-shrink-0 overflow-hidden">
{u.avatar_url ? ( {u.avatar_url ? (
<img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" /> <img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -572,7 +572,7 @@ export default function AdminSuspensionsPage() {
<form onSubmit={handleSuspend} className="px-6 py-5 space-y-4"> <form onSubmit={handleSuspend} className="px-6 py-5 space-y-4">
{/* User info */} {/* User info */}
<div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3"> <div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3">
<div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#3b82f6] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#ffb800] font-bold text-sm flex-shrink-0 overflow-hidden">
{selectedUser.avatar_url ? ( {selectedUser.avatar_url ? (
<img src={selectedUser.avatar_url} alt={selectedUser.full_name || selectedUser.email} className="w-full h-full object-cover" /> <img src={selectedUser.avatar_url} alt={selectedUser.full_name || selectedUser.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -637,7 +637,7 @@ export default function AdminSuspensionsPage() {
<select <select
value={suspendDuration ?? ''} value={suspendDuration ?? ''}
onChange={(e) => setSuspendDuration(e.target.value ? parseInt(e.target.value) : null)} onChange={(e) => setSuspendDuration(e.target.value ? parseInt(e.target.value) : null)}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#ffb800]"
> >
{DURATION_OPTIONS.filter(d => d.value !== null).map((d) => ( {DURATION_OPTIONS.filter(d => d.value !== null).map((d) => (
<option key={d.value} value={d.value!}>{d.label}</option> <option key={d.value} value={d.value!}>{d.label}</option>
@@ -657,7 +657,7 @@ export default function AdminSuspensionsPage() {
onChange={(e) => setSuspendReason(e.target.value)} onChange={(e) => setSuspendReason(e.target.value)}
placeholder="Explain the reason for this action..." placeholder="Explain the reason for this action..."
rows={3} rows={3}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#3b82f6] resize-none" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#ffb800] resize-none"
/> />
</div> </div>
@@ -729,7 +729,7 @@ export default function AdminSuspensionsPage() {
onChange={(e) => setLiftReason(e.target.value)} onChange={(e) => setLiftReason(e.target.value)}
placeholder="Reason for lifting this restriction..." placeholder="Reason for lifting this restriction..."
rows={2} rows={2}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#3b82f6] resize-none" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#ffb800] resize-none"
/> />
</div> </div>
<div className="flex items-center gap-3 pt-1"> <div className="flex items-center gap-3 pt-1">

View File

@@ -246,7 +246,7 @@ export default function AdminUsersPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center"> <div className="min-h-screen bg-[#0d1117] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -276,7 +276,7 @@ export default function AdminUsersPage() {
</div> </div>
<button <button
onClick={() => setShowInviteModal(true)} onClick={() => setShowInviteModal(true)}
className="flex items-center gap-2 bg-[#3b82f6] hover:bg-[#2563eb] text-white text-sm font-semibold px-4 py-2 rounded-lg transition-colors" className="flex items-center gap-2 bg-[#ffb800] hover:bg-[#d99700] text-white text-sm font-semibold px-4 py-2 rounded-lg transition-colors"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.41 2 2 0 0 1 3.6 1.22h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.96a16 16 0 0 0 6 6l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 21.5 16.5z" /> <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.41 2 2 0 0 1 3.6 1.22h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.96a16 16 0 0 0 6 6l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 21.5 16.5z" />
@@ -306,7 +306,7 @@ export default function AdminUsersPage() {
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${ className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${
activeTab === tab activeTab === tab
? 'text-[#3b82f6] border-[#3b82f6]' ? 'text-[#ffb800] border-[#ffb800]'
: 'text-[#555] border-transparent hover:text-[#888]' : 'text-[#555] border-transparent hover:text-[#888]'
}`} }`}
> >
@@ -331,14 +331,14 @@ export default function AdminUsersPage() {
placeholder={activeTab === 'users' ? 'Search users...' : 'Search by email...'} placeholder={activeTab === 'users' ? 'Search users...' : 'Search by email...'}
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
{activeTab === 'users' && ( {activeTab === 'users' && (
<select <select
value={roleFilter} value={roleFilter}
onChange={(e) => setRoleFilter(e.target.value)} onChange={(e) => setRoleFilter(e.target.value)}
className="bg-[#111827] border border-[#1f2937] text-[#ccc] text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#3b82f6]" className="bg-[#111827] border border-[#1f2937] text-[#ccc] text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#ffb800]"
> >
<option value="all">All Roles</option> <option value="all">All Roles</option>
{ROLE_OPTIONS.map((r) => ( {ROLE_OPTIONS.map((r) => (
@@ -353,7 +353,7 @@ export default function AdminUsersPage() {
<div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden"> <div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filteredUsers.length === 0 ? ( ) : filteredUsers.length === 0 ? (
<div className="text-center py-16 text-[#555]"> <div className="text-center py-16 text-[#555]">
@@ -379,7 +379,7 @@ export default function AdminUsersPage() {
<tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#3b82f6] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#ffb800] font-bold text-sm flex-shrink-0 overflow-hidden">
{u.avatar_url ? ( {u.avatar_url ? (
<img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" /> <img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -418,7 +418,7 @@ export default function AdminUsersPage() {
<button <button
onClick={() => openEditRole(u)} onClick={() => openEditRole(u)}
disabled={processingIds.has(u.id)} disabled={processingIds.has(u.id)}
className="text-[#555] hover:text-[#3b82f6] transition-colors text-xs font-medium px-2 py-1 rounded hover:bg-[#1f2937] disabled:opacity-50" className="text-[#555] hover:text-[#ffb800] transition-colors text-xs font-medium px-2 py-1 rounded hover:bg-[#1f2937] disabled:opacity-50"
> >
Edit Role Edit Role
</button> </button>
@@ -455,7 +455,7 @@ export default function AdminUsersPage() {
<div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden"> <div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filteredInvitations.length === 0 ? ( ) : filteredInvitations.length === 0 ? (
<div className="text-center py-16 text-[#555]"> <div className="text-center py-16 text-[#555]">
@@ -465,7 +465,7 @@ export default function AdminUsersPage() {
<p className="text-sm">No invitations sent yet</p> <p className="text-sm">No invitations sent yet</p>
<button <button
onClick={() => setShowInviteModal(true)} onClick={() => setShowInviteModal(true)}
className="mt-3 text-[#3b82f6] hover:text-[#60a5fa] text-sm transition-colors" className="mt-3 text-[#ffb800] hover:text-[#ffd25a] text-sm transition-colors"
> >
Send your first invitation Send your first invitation
</button> </button>
@@ -491,7 +491,7 @@ export default function AdminUsersPage() {
<tr key={inv.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={inv.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#3b82f6] text-xs font-bold flex-shrink-0"> <div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#ffb800] text-xs font-bold flex-shrink-0">
{inv.email[0].toUpperCase()} {inv.email[0].toUpperCase()}
</div> </div>
<span className="text-white text-sm">{inv.email}</span> <span className="text-white text-sm">{inv.email}</span>
@@ -564,7 +564,7 @@ export default function AdminUsersPage() {
value={inviteEmail} value={inviteEmail}
onChange={(e) => setInviteEmail(e.target.value)} onChange={(e) => setInviteEmail(e.target.value)}
placeholder="contributor@example.com" placeholder="contributor@example.com"
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<div> <div>
@@ -574,7 +574,7 @@ export default function AdminUsersPage() {
<select <select
value={inviteRole} value={inviteRole}
onChange={(e) => setInviteRole(e.target.value)} onChange={(e) => setInviteRole(e.target.value)}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#ffb800]"
> >
<option value="contributor">Contributor can submit articles for review</option> <option value="contributor">Contributor can submit articles for review</option>
<option value="editor">Editor can publish and manage articles</option> <option value="editor">Editor can publish and manage articles</option>
@@ -590,7 +590,7 @@ export default function AdminUsersPage() {
onChange={(e) => setInviteMessage(e.target.value)} onChange={(e) => setInviteMessage(e.target.value)}
placeholder="Add a personal note to the invitation email..." placeholder="Add a personal note to the invitation email..."
rows={3} rows={3}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#3b82f6] resize-none" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#ffb800] resize-none"
/> />
</div> </div>
<div className="bg-[#0d1117] border border-[#1f2937] rounded-lg p-3"> <div className="bg-[#0d1117] border border-[#1f2937] rounded-lg p-3">
@@ -609,7 +609,7 @@ export default function AdminUsersPage() {
<button <button
type="submit" type="submit"
disabled={inviteSending || !inviteEmail.trim()} disabled={inviteSending || !inviteEmail.trim()}
className="flex-1 bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2" className="flex-1 bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2"
> >
{inviteSending ? ( {inviteSending ? (
<> <>
@@ -644,7 +644,7 @@ export default function AdminUsersPage() {
</div> </div>
<div className="px-6 py-5 space-y-4"> <div className="px-6 py-5 space-y-4">
<div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3"> <div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3">
<div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#3b82f6] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#ffb800] font-bold text-sm flex-shrink-0 overflow-hidden">
{editingUser.avatar_url ? ( {editingUser.avatar_url ? (
<img src={editingUser.avatar_url} alt={editingUser.full_name || editingUser.email} className="w-full h-full object-cover" /> <img src={editingUser.avatar_url} alt={editingUser.full_name || editingUser.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -666,7 +666,7 @@ export default function AdminUsersPage() {
key={r.value} key={r.value}
className={`flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${ className={`flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${
editRole === r.value editRole === r.value
? 'border-[#3b82f6] bg-[#3b82f6]/10' ? 'border-[#ffb800] bg-[#ffb800]/10'
: 'border-[#1f2937] hover:border-[#2a3a50]' : 'border-[#1f2937] hover:border-[#2a3a50]'
}`} }`}
> >
@@ -679,9 +679,9 @@ export default function AdminUsersPage() {
className="sr-only" className="sr-only"
/> />
<span className={`w-3.5 h-3.5 rounded-full border-2 flex items-center justify-center flex-shrink-0 ${ <span className={`w-3.5 h-3.5 rounded-full border-2 flex items-center justify-center flex-shrink-0 ${
editRole === r.value ? 'border-[#3b82f6]' : 'border-[#444]' editRole === r.value ? 'border-[#ffb800]' : 'border-[#444]'
}`}> }`}>
{editRole === r.value && <span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6]" />} {editRole === r.value && <span className="w-1.5 h-1.5 rounded-full bg-[#ffb800]" />}
</span> </span>
<span className={`text-xs font-semibold uppercase tracking-wide px-2 py-0.5 rounded ${r.color}`}> <span className={`text-xs font-semibold uppercase tracking-wide px-2 py-0.5 rounded ${r.color}`}>
{r.label} {r.label}
@@ -700,7 +700,7 @@ export default function AdminUsersPage() {
<button <button
onClick={handleEditSave} onClick={handleEditSave}
disabled={editSaving || editRole === editingUser.role} disabled={editSaving || editRole === editingUser.role}
className="flex-1 bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2" className="flex-1 bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2"
> >
{editSaving ? ( {editSaving ? (
<> <>

View File

@@ -44,7 +44,7 @@ export default function AdvertisePage() {
{/* Hero */} {/* Hero */}
{/* DO NOT OVERRIDE — Advertise page hero section */} {/* DO NOT OVERRIDE — Advertise page hero section */}
<div className="bg-[#0d1520] border-b border-[#1e3a5f] py-12"> <div className="bg-[#0d1520] border-b border-[#4a3500] py-12">
<div className="max-w-container mx-auto px-4 text-center"> <div className="max-w-container mx-auto px-4 text-center">
<span className="section-label mb-3 inline-block">Advertise</span> <span className="section-label mb-3 inline-block">Advertise</span>
<h1 className="font-heading text-white text-4xl font-bold mb-4">Reach Pro AV &amp; Live Production Professionals</h1> <h1 className="font-heading text-white text-4xl font-bold mb-4">Reach Pro AV &amp; Live Production Professionals</h1>
@@ -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-[#3b82f6] text-2xl font-bold mb-1">{stat.value}</div> <div className="font-heading text-[#ffb800] 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-[#3b82f6] font-bold text-xs uppercase tracking-wider">Placement</th> <th className="text-left py-3 px-4 text-[#ffb800] font-bold text-xs uppercase tracking-wider">Placement</th>
<th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Dimensions</th> <th className="text-left py-3 px-4 text-[#ffb800] font-bold text-xs uppercase tracking-wider">Dimensions</th>
<th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Location</th> <th className="text-left py-3 px-4 text-[#ffb800] font-bold text-xs uppercase tracking-wider">Location</th>
<th className="text-left py-3 px-4 text-[#3b82f6] font-bold text-xs uppercase tracking-wider">Rate</th> <th className="text-left py-3 px-4 text-[#ffb800] 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-[#3b82f6] font-medium">{zone.cpm}</td> <td className="py-3 px-4 text-[#ffb800] 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-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p> <p className="font-body text-[#ffb800] 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-[#3b82f6] text-sm hover:underline"> <a href="mailto:jeff@avbeat.com" className="font-body text-[#ffb800] 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-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-1">Publication</p> <p className="font-body text-[#ffb800] 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-[#3b82f6] text-xs font-bold uppercase tracking-wider mb-2">What We Offer</p> <p className="font-body text-[#ffb800] 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-[#3b82f6] mt-0.5"></span> <span className="text-[#ffb800] 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-[#3b82f6] p-8 text-center"> <div className="bg-[#111] border border-[#ffb800] p-8 text-center">
<div className="w-12 h-12 rounded-full bg-[#3b82f6]/20 flex items-center justify-center mx-auto mb-4"> <div className="w-12 h-12 rounded-full bg-[#ffb800]/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="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M5 12l4 4 10-10" stroke="#ffb800" 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-[#3b82f6] hover:underline">jeff@avbeat.com</a>. <a href="mailto:jeff@avbeat.com" className="text-[#ffb800] 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-[#3b82f6] hover:underline">jeff@avbeat.com</a> Or email directly: <a href="mailto:jeff@avbeat.com" className="text-[#ffb800] hover:underline">jeff@avbeat.com</a>
</p> </p>
</form> </form>
)} )}

View File

@@ -38,7 +38,7 @@ export async function POST(req: NextRequest) {
subject_prefix: subject_prefix || '', subject_prefix: subject_prefix || '',
header_text: header_text || '', header_text: header_text || '',
footer_text: footer_text || '', footer_text: footer_text || '',
accent_color: accent_color || '#3b82f6', accent_color: accent_color || '#ffb800',
article_blocks: article_blocks || [], article_blocks: article_blocks || [],
is_preset: false, is_preset: false,
}) })

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-[#3b82f6] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#ffb800] transition-colors">Home</Link>
<span>/</span> <span>/</span>
<Link href={sectionHref} className="hover:text-[#3b82f6] transition-colors">{sectionLabel}</Link> <Link href={sectionHref} className="hover:text-[#ffb800] 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-[#3b82f6] hover:underline"> <Link href={sectionHref} className="text-xs font-bold uppercase tracking-widest text-[#ffb800] 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-[#3b82f6] transition-colors text-[#e0e0e0]"> className="font-heading font-bold text-sm hover:text-[#ffb800] 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-[#3b82f6] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#ffb800] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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" />
@@ -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-[#3b82f6] hover:bg-[#1a1a1a] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#ffb800] 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-[#3b82f6] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="text-xs font-bold uppercase tracking-widest text-[#ffb800] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
{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-[#3b82f6] hover:text-[#3b82f6] transition-colors rounded-sm cursor-default"> className="px-3 py-1 text-xs font-body bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] hover:border-[#ffb800] hover:text-[#ffb800] 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-[#3b82f6] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="inline-flex items-center gap-2 text-[#ffb800] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5"> <p className="text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#ffb800] 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-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#ffb800] 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-[#3b82f6] font-body font-bold uppercase tracking-wider"> <span className="text-[#ffb800] 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-[#3b82f6] hover:underline font-body font-semibold uppercase tracking-wider" className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#ffb800] 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-[#3b82f6] text-white px-4 py-3 rounded-sm text-sm font-body shadow-lg z-50"> className="fixed bottom-4 right-4 bg-[#1a1a1a] border border-[#ffb800] text-white px-4 py-3 rounded-sm text-sm font-body shadow-lg z-50">
{saveToast} {saveToast}
</div> </div>
)} )}

View File

@@ -134,8 +134,8 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
{user ? ( {user ? (
<form onSubmit={handleSubmit} className="mb-8"> <form onSubmit={handleSubmit} className="mb-8">
<div className="flex gap-3"> <div className="flex gap-3">
<div className="w-8 h-8 rounded-full bg-[#1e3a5f] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5"> <div className="w-8 h-8 rounded-full bg-[#4a3500] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="font-body text-[10px] font-bold text-[#3b82f6]"> <span className="font-body text-[10px] font-bold text-[#ffb800]">
{getInitials(user.user_metadata?.full_name || user.email || "U")} {getInitials(user.user_metadata?.full_name || user.email || "U")}
</span> </span>
</div> </div>
@@ -146,10 +146,10 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
placeholder="Share your thoughts on this article..." placeholder="Share your thoughts on this article..."
rows={3} rows={3}
maxLength={1000} maxLength={1000}
className="w-full bg-[#1a1a1a] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444] resize-none" className="w-full bg-[#1a1a1a] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800] transition-colors placeholder-[#444] resize-none"
/> />
{error && ( {error && (
<p className="font-body text-xs text-[#cc0000] mt-1.5">{error}</p> <p className="font-body text-xs text-[#d60701] mt-1.5">{error}</p>
)} )}
<div className="flex items-center justify-between mt-2"> <div className="flex items-center justify-between mt-2">
<span className="font-body text-[11px] text-[#555]"> <span className="font-body text-[11px] text-[#555]">
@@ -158,7 +158,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
<button <button
type="submit" type="submit"
disabled={submitting || !newComment.trim()} disabled={submitting || !newComment.trim()}
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="bg-[#ffb800] hover:bg-[#d99700] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
{submitting ? "Posting..." : "Post Comment"} {submitting ? "Posting..." : "Post Comment"}
</button> </button>
</div> </div>
@@ -166,14 +166,14 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
</div> </div>
</form> </form>
) : ( ) : (
<div className="mb-8 bg-[#0d1520] border border-[#1e3a5f] rounded-sm p-5 flex items-center justify-between gap-4"> <div className="mb-8 bg-[#0d1520] border border-[#4a3500] rounded-sm p-5 flex items-center justify-between gap-4">
<div> <div>
<p className="font-body text-sm font-bold text-[#e0e0e0] mb-0.5">Join the conversation</p> <p className="font-body text-sm font-bold text-[#e0e0e0] mb-0.5">Join the conversation</p>
<p className="font-body text-xs text-[#666]">Sign in to post a comment on this article.</p> <p className="font-body text-xs text-[#666]">Sign in to post a comment on this article.</p>
</div> </div>
<Link <Link
href="/login" href="/login"
className="flex-shrink-0 bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors"> className="flex-shrink-0 bg-[#ffb800] hover:bg-[#d99700] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors">
Sign In Sign In
</Link> </Link>
</div> </div>
@@ -208,7 +208,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
const isOwn = user?.id === comment.user_id; const isOwn = user?.id === comment.user_id;
return ( return (
<div key={comment.id} className="flex gap-3 group"> <div key={comment.id} className="flex gap-3 group">
<div className="w-8 h-8 rounded-full bg-[#1a2535] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#1a1208] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5 overflow-hidden">
{comment.user_profiles?.avatar_url ? ( {comment.user_profiles?.avatar_url ? (
<img <img
src={comment.user_profiles.avatar_url} src={comment.user_profiles.avatar_url}
@@ -216,7 +216,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
) : ( ) : (
<span className="font-body text-[10px] font-bold text-[#3b82f6]"> <span className="font-body text-[10px] font-bold text-[#ffb800]">
{getInitials(name)} {getInitials(name)}
</span> </span>
)} )}
@@ -230,7 +230,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
onClick={() => handleDelete(comment.id)} onClick={() => handleDelete(comment.id)}
disabled={deletingId === comment.id} disabled={deletingId === comment.id}
aria-label="Delete comment" aria-label="Delete comment"
className="ml-auto opacity-0 group-hover:opacity-100 font-body text-[11px] text-[#555] hover:text-[#cc0000] transition-all disabled:opacity-30"> className="ml-auto opacity-0 group-hover:opacity-100 font-body text-[11px] text-[#555] hover:text-[#d60701] transition-all disabled:opacity-30">
{deletingId === comment.id ? "Deleting..." : "Delete"} {deletingId === comment.id ? "Deleting..." : "Delete"}
</button> </button>
)} )}

View File

@@ -148,7 +148,7 @@ export default function AuthorProfilePage() {
<h1 className="font-heading text-2xl md:text-3xl font-bold text-[#e8e8e8] leading-tight"> <h1 className="font-heading text-2xl md:text-3xl font-bold text-[#e8e8e8] leading-tight">
{loading ? "Loading…" : profile?.display_name || "Author"} {loading ? "Loading…" : profile?.display_name || "Author"}
</h1> </h1>
<p className="font-body text-sm text-[#3b82f6] mt-0.5"> <p className="font-body text-sm text-[#ffb800] mt-0.5">
{profile?.role || "Contributor"} {profile?.role || "Contributor"}
</p> </p>
<div className="flex flex-wrap items-center gap-3 mt-2"> <div className="flex flex-wrap items-center gap-3 mt-2">
@@ -180,7 +180,7 @@ export default function AuthorProfilePage() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label={`${profile.display_name} on Twitter`} aria-label={`${profile.display_name} on Twitter`}
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] hover:text-[#3b82f6] text-[#888] transition-colors rounded-sm" className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#ffb800] hover:text-[#ffb800] text-[#888] transition-colors rounded-sm"
> >
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <svg width="13" height="13" 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" /> <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" />
@@ -193,7 +193,7 @@ export default function AuthorProfilePage() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label={`${profile.display_name} on LinkedIn`} aria-label={`${profile.display_name} on LinkedIn`}
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] hover:text-[#3b82f6] text-[#888] transition-colors rounded-sm" className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#ffb800] hover:text-[#ffb800] text-[#888] transition-colors rounded-sm"
> >
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" /> <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
@@ -204,7 +204,7 @@ export default function AuthorProfilePage() {
<a <a
href={`mailto:${profile.email}`} href={`mailto:${profile.email}`}
aria-label={`Email ${profile.display_name}`} aria-label={`Email ${profile.display_name}`}
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] hover:text-[#3b82f6] text-[#888] transition-colors rounded-sm" className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#ffb800] hover:text-[#ffb800] text-[#888] transition-colors rounded-sm"
> >
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /> <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
@@ -223,7 +223,7 @@ export default function AuthorProfilePage() {
{profile.specialties.map((spec) => ( {profile.specialties.map((spec) => (
<span <span
key={spec} key={spec}
className="font-body text-xs text-[#3b82f6] bg-[#1e3a5f]/30 border border-[#1e3a5f] px-2.5 py-1 rounded-sm" className="font-body text-xs text-[#ffb800] bg-[#4a3500]/30 border border-[#4a3500] px-2.5 py-1 rounded-sm"
> >
{spec} {spec}
</span> </span>
@@ -237,7 +237,7 @@ export default function AuthorProfilePage() {
onClick={() => setActiveTab("articles")} onClick={() => setActiveTab("articles")}
className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${ className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${
activeTab === "articles" activeTab === "articles"
? "border-[#3b82f6] text-[#3b82f6]" ? "border-[#ffb800] text-[#ffb800]"
: "border-transparent text-[#666] hover:text-[#aaa]" : "border-transparent text-[#666] hover:text-[#aaa]"
}`} }`}
> >
@@ -247,7 +247,7 @@ export default function AuthorProfilePage() {
onClick={() => setActiveTab("about")} onClick={() => setActiveTab("about")}
className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${ className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${
activeTab === "about" activeTab === "about"
? "border-[#3b82f6] text-[#3b82f6]" ? "border-[#ffb800] text-[#ffb800]"
: "border-transparent text-[#666] hover:text-[#aaa]" : "border-transparent text-[#666] hover:text-[#aaa]"
}`} }`}
> >
@@ -271,7 +271,7 @@ export default function AuthorProfilePage() {
<Link <Link
key={article.slug} key={article.slug}
href={`/articles/${article.slug}`} href={`/articles/${article.slug}`}
className="group bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] transition-colors rounded-sm overflow-hidden article-card" className="group bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#ffb800] transition-colors rounded-sm overflow-hidden article-card"
> >
<div className="img-zoom h-44 overflow-hidden"> <div className="img-zoom h-44 overflow-hidden">
<AppImage <AppImage
@@ -284,13 +284,13 @@ export default function AuthorProfilePage() {
</div> </div>
<div className="p-4"> <div className="p-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#3b82f6]"> <span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#ffb800]">
{article.category} {article.category}
</span> </span>
<span className="font-body text-[10px] text-[#555]">·</span> <span className="font-body text-[10px] text-[#555]">·</span>
<span className="font-body text-[10px] text-[#555]">{article.readTime}</span> <span className="font-body text-[10px] text-[#555]">{article.readTime}</span>
</div> </div>
<h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#3b82f6] transition-colors leading-snug mb-2 line-clamp-2"> <h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#ffb800] transition-colors leading-snug mb-2 line-clamp-2">
{article.title} {article.title}
</h3> </h3>
<p className="font-body text-xs text-[#777] leading-relaxed line-clamp-2 mb-3"> <p className="font-body text-xs text-[#777] leading-relaxed line-clamp-2 mb-3">
@@ -353,7 +353,7 @@ export default function AuthorProfilePage() {
</p> </p>
<a <a
href={`tel:${profile.phone.replace(/\s+/g, "")}`} href={`tel:${profile.phone.replace(/\s+/g, "")}`}
className="font-body text-sm text-[#3b82f6] hover:underline" className="font-body text-sm text-[#ffb800] hover:underline"
> >
{profile.phone} {profile.phone}
</a> </a>
@@ -366,7 +366,7 @@ export default function AuthorProfilePage() {
</p> </p>
<a <a
href={`mailto:${profile.email}`} href={`mailto:${profile.email}`}
className="font-body text-sm text-[#3b82f6] hover:underline break-all" className="font-body text-sm text-[#ffb800] hover:underline break-all"
> >
{profile.email} {profile.email}
</a> </a>
@@ -381,7 +381,7 @@ export default function AuthorProfilePage() {
href={profile.website} href={profile.website}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-body text-sm text-[#3b82f6] hover:underline break-all" className="font-body text-sm text-[#ffb800] hover:underline break-all"
> >
{profile.website.replace(/^https?:\/\//, "").replace(/\/$/, "")} {profile.website.replace(/^https?:\/\//, "").replace(/\/$/, "")}
</a> </a>

View File

@@ -214,7 +214,7 @@ function renderMarkdown(text: string): string {
.replace(/^# (.+)$/gm, '<h1 class="text-xl font-bold text-white mt-5 mb-2">$1</h1>') .replace(/^# (.+)$/gm, '<h1 class="text-xl font-bold text-white mt-5 mb-2">$1</h1>')
.replace(/\*\*(.+?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>') .replace(/\*\*(.+?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>') .replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/`([^`]+)`/g, '<code class="bg-[#1a2535] text-[#3b82f6] px-1 py-0.5 rounded text-sm font-mono">$1</code>') .replace(/`([^`]+)`/g, '<code class="bg-[#1a1208] text-[#ffb800] px-1 py-0.5 rounded text-sm font-mono">$1</code>')
.replace(/^- (.+)$/gm, '<li class="ml-4 list-disc text-[#aaa]">$1</li>') .replace(/^- (.+)$/gm, '<li class="ml-4 list-disc text-[#aaa]">$1</li>')
.replace(/^\d+\. (.+)$/gm, '<li class="ml-4 list-decimal text-[#aaa]">$1</li>') .replace(/^\d+\. (.+)$/gm, '<li class="ml-4 list-decimal text-[#aaa]">$1</li>')
.replace(/\n\n/g, '</p><p class="mb-3 text-[#ccc] leading-relaxed">') .replace(/\n\n/g, '</p><p class="mb-3 text-[#ccc] leading-relaxed">')
@@ -294,7 +294,7 @@ function DraftPreviewModal({ content, title, penName, siteId, onClose, onSave }:
</button> </button>
<button <button
onClick={() => onSave(true)} onClick={() => onSave(true)}
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium" className="bg-[#ffb800] hover:bg-[#d99700] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
> >
Open in Editor Open in Editor
</button> </button>
@@ -353,7 +353,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
onClick={() => { setSelectedVariant(v.prompt); setCustomPrompt(''); }} onClick={() => { setSelectedVariant(v.prompt); setCustomPrompt(''); }}
className={`text-left text-xs px-3 py-2 rounded border transition-colors ${ className={`text-left text-xs px-3 py-2 rounded border transition-colors ${
selectedVariant === v.prompt selectedVariant === v.prompt
? 'border-[#3b82f6] bg-[#1e3a5f] text-white' ? 'border-[#ffb800] bg-[#4a3500] text-white'
: 'border-[#1e2a3a] text-[#aaa] hover:border-[#2a3a50] hover:text-white hover:bg-[#161f2e]' : 'border-[#1e2a3a] text-[#aaa] hover:border-[#2a3a50] hover:text-white hover:bg-[#161f2e]'
}`} }`}
> >
@@ -370,7 +370,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
onChange={e => { setCustomPrompt(e.target.value); setSelectedVariant(null); }} onChange={e => { setCustomPrompt(e.target.value); setSelectedVariant(null); }}
placeholder="e.g. Rewrite this focusing on the European market impact..." placeholder="e.g. Rewrite this focusing on the European market impact..."
rows={3} rows={3}
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#333] resize-none" className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#333] resize-none"
/> />
</div> </div>
@@ -386,7 +386,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
<button onClick={onClose} className="text-[#555] hover:text-[#aaa] text-sm transition-colors">Cancel</button> <button onClick={onClose} className="text-[#555] hover:text-[#aaa] text-sm transition-colors">Cancel</button>
<button <button
onClick={handleSubmit} onClick={handleSubmit}
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium" className="bg-[#ffb800] hover:bg-[#d99700] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
> >
Regenerate Regenerate
</button> </button>
@@ -418,7 +418,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
const found = PEN_NAME_PROFILES.find(p => p.name === e.target.value); const found = PEN_NAME_PROFILES.find(p => p.name === e.target.value);
if (found) side === 'left' ? setLeftPen(found) : setRightPen(found); if (found) side === 'left' ? setLeftPen(found) : setRightPen(found);
}} }}
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded px-2 py-1.5 focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded px-2 py-1.5 focus:outline-none focus:border-[#ffb800]"
> >
{PEN_NAME_PROFILES.map(p => ( {PEN_NAME_PROFILES.map(p => (
<option key={p.name} value={p.name}>{p.name} ({p.site})</option> <option key={p.name} value={p.name}>{p.name} ({p.site})</option>
@@ -434,7 +434,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
</span> </span>
<span className="text-white font-semibold text-sm">{profile.name}</span> <span className="text-white font-semibold text-sm">{profile.name}</span>
</div> </div>
<p className="text-[#3b82f6] text-xs">{profile.title}</p> <p className="text-[#ffb800] text-xs">{profile.title}</p>
</div> </div>
<div> <div>
@@ -458,7 +458,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
<div> <div>
<p className="text-[#555] text-[10px] uppercase tracking-wider font-bold mb-1">Example Lead</p> <p className="text-[#555] text-[10px] uppercase tracking-wider font-bold mb-1">Example Lead</p>
<p className="text-[#aaa] text-xs italic leading-relaxed border-l-2 border-[#3b82f6]/40 pl-3">"{profile.exampleLead}"</p> <p className="text-[#aaa] text-xs italic leading-relaxed border-l-2 border-[#ffb800]/40 pl-3">"{profile.exampleLead}"</p>
</div> </div>
<button <button
@@ -485,7 +485,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
<button <button
key={f} key={f}
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-[#ffb800] text-white' : 'bg-[#1e2a3a] text-[#aaa] hover:text-white'}`}
> >
{f === 'ALL' ? 'All Writers' : f === 'BB' ? 'AV Beat' : 'AV Beat'} {f === 'ALL' ? 'All Writers' : f === 'BB' ? 'AV Beat' : 'AV Beat'}
</button> </button>
@@ -514,7 +514,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
> >
<div className="flex items-center gap-2 mb-0.5"> <div className="flex items-center gap-2 mb-0.5">
<span className={`text-[9px] font-bold px-1 rounded ${p.site === 'AV' ? 'bg-orange-900/40 text-orange-400' : 'bg-blue-900/40 text-blue-400'}`}>{p.site}</span> <span className={`text-[9px] font-bold px-1 rounded ${p.site === 'AV' ? 'bg-orange-900/40 text-orange-400' : 'bg-blue-900/40 text-blue-400'}`}>{p.site}</span>
<span className="text-white text-xs font-medium group-hover:text-[#3b82f6] transition-colors">{p.name}</span> <span className="text-white text-xs font-medium group-hover:text-[#ffb800] transition-colors">{p.name}</span>
</div> </div>
<p className="text-[#555] text-[10px]">{p.title}</p> <p className="text-[#555] text-[10px]">{p.title}</p>
</button> </button>
@@ -560,14 +560,14 @@ function ResponseHistorySidebar({ messages, onJumpTo, onClose }: ResponseHistory
className="w-full text-left p-3 rounded border border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e] transition-colors group" className="w-full text-left p-3 rounded border border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e] transition-colors group"
> >
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="text-[10px] text-[#3b82f6] font-bold">Response #{idx + 1}</span> <span className="text-[10px] text-[#ffb800] font-bold">Response #{idx + 1}</span>
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${msg.provider === 'ollama' ? 'bg-green-500' : 'bg-yellow-500'}`} /> <span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${msg.provider === 'ollama' ? 'bg-green-500' : 'bg-yellow-500'}`} />
</div> </div>
<p className="text-[#aaa] text-xs line-clamp-3 leading-relaxed">{preview}</p> <p className="text-[#aaa] text-xs line-clamp-3 leading-relaxed">{preview}</p>
<div className="flex items-center gap-3 mt-2"> <div className="flex items-center gap-3 mt-2">
<span className="text-[10px] text-[#444]">{msg.timestamp.toLocaleTimeString()}</span> <span className="text-[10px] text-[#444]">{msg.timestamp.toLocaleTimeString()}</span>
<span className="text-[10px] text-[#444]">{wordCount} words</span> <span className="text-[10px] text-[#444]">{wordCount} words</span>
<span className="text-[10px] text-[#3b82f6] opacity-0 group-hover:opacity-100 transition-opacity">Jump </span> <span className="text-[10px] text-[#ffb800] opacity-0 group-hover:opacity-100 transition-opacity">Jump </span>
</div> </div>
</button> </button>
); );
@@ -927,7 +927,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
{/* Header */} {/* Header */}
<div className="bg-[#111827] border-b border-[#1e2a3a] px-4 py-3 flex items-center justify-between"> <div className="bg-[#111827] border-b border-[#1e2a3a] px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin" className="text-[#555] hover:text-[#3b82f6] text-sm transition-colors"> Dashboard</Link> <Link href="/admin" className="text-[#555] hover:text-[#ffb800] text-sm transition-colors"> Dashboard</Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
<h1 className="text-white font-bold text-sm tracking-wide">AI Console</h1> <h1 className="text-white font-bold text-sm tracking-wide">AI Console</h1>
</div> </div>
@@ -943,18 +943,18 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
onClick={() => setShowHistorySidebar(v => !v)} onClick={() => setShowHistorySidebar(v => !v)}
className={`text-xs px-2.5 py-1.5 rounded border transition-colors ${ className={`text-xs px-2.5 py-1.5 rounded border transition-colors ${
showHistorySidebar showHistorySidebar
? 'bg-[#1e3a5f] border-[#3b82f6]/40 text-[#3b82f6]' ? 'bg-[#4a3500] border-[#ffb800]/40 text-[#ffb800]'
: 'bg-[#1e2a3a] border-[#2a3a50] text-[#555] hover:text-[#aaa]' : 'bg-[#1e2a3a] border-[#2a3a50] text-[#555] hover:text-[#aaa]'
}`} }`}
title="Toggle response history sidebar" title="Toggle response history sidebar"
> >
History History
{assistantMessageCount > 0 && ( {assistantMessageCount > 0 && (
<span className="ml-1.5 bg-[#3b82f6] text-white text-[9px] font-bold px-1 py-0.5 rounded-full">{assistantMessageCount}</span> <span className="ml-1.5 bg-[#ffb800] text-white text-[9px] font-bold px-1 py-0.5 rounded-full">{assistantMessageCount}</span>
)} )}
</button> </button>
<Link href="/dashboard/ai-console/health" className="text-[#555] hover:text-[#3b82f6] text-xs transition-colors">Health</Link> <Link href="/dashboard/ai-console/health" className="text-[#555] hover:text-[#ffb800] text-xs transition-colors">Health</Link>
<Link href="/dashboard/ai-console/settings" className="text-[#555] hover:text-[#3b82f6] text-xs transition-colors">Settings</Link> <Link href="/dashboard/ai-console/settings" className="text-[#555] hover:text-[#ffb800] text-xs transition-colors">Settings</Link>
<button onClick={handleNewSession} className="text-xs bg-[#1e2a3a] hover:bg-[#253347] text-[#aaa] px-3 py-1.5 rounded transition-colors"> <button onClick={handleNewSession} className="text-xs bg-[#1e2a3a] hover:bg-[#253347] text-[#aaa] px-3 py-1.5 rounded transition-colors">
+ New Session + New Session
</button> </button>
@@ -1002,7 +1002,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
key={session.id} key={session.id}
className={`group p-2.5 rounded border cursor-pointer transition-colors ${ className={`group p-2.5 rounded border cursor-pointer transition-colors ${
currentSessionId === session.id currentSessionId === session.id
? 'border-[#3b82f6]/40 bg-[#1e2a3a]' ? 'border-[#ffb800]/40 bg-[#1e2a3a]'
: 'border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e]' : 'border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e]'
}`} }`}
> >
@@ -1046,11 +1046,11 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
onClick={() => { onClick={() => {
fetch('/api/ai/console?action=ping').then(r => r.json()).then(setAiStatus).catch(() => {}); fetch('/api/ai/console?action=ping').then(r => r.json()).then(setAiStatus).catch(() => {});
}} }}
className="text-[10px] text-[#555] hover:text-[#3b82f6] transition-colors" className="text-[10px] text-[#555] hover:text-[#ffb800] transition-colors"
> >
Refresh Refresh
</button> </button>
<Link href="/dashboard/ai-console/health" className="text-[10px] text-[#555] hover:text-[#3b82f6] transition-colors"> <Link href="/dashboard/ai-console/health" className="text-[10px] text-[#555] hover:text-[#ffb800] transition-colors">
Health Dashboard Health Dashboard
</Link> </Link>
</div> </div>
@@ -1066,7 +1066,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<select <select
value={selectedSite} value={selectedSite}
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-[#ffb800]"
> >
<option value={1}>AV Beat</option> <option value={1}>AV Beat</option>
<option value={2}>AV Beat</option> <option value={2}>AV Beat</option>
@@ -1077,7 +1077,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<select <select
value={selectedPenName} value={selectedPenName}
onChange={e => setSelectedPenName(e.target.value)} onChange={e => setSelectedPenName(e.target.value)}
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-[#ffb800]"
> >
{penNames.map(name => ( {penNames.map(name => (
<option key={name} value={name}>{name}</option> <option key={name} value={name}>{name}</option>
@@ -1086,7 +1086,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
</div> </div>
<button <button
onClick={() => setShowStyleGuides(true)} onClick={() => setShowStyleGuides(true)}
className="text-[10px] text-[#3b82f6] hover:text-[#60a5fa] transition-colors ml-auto" className="text-[10px] text-[#ffb800] hover:text-[#ffd25a] transition-colors ml-auto"
> >
View style guide View style guide
</button> </button>
@@ -1123,7 +1123,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
</div> </div>
)} )}
<div className={`rounded-lg px-4 py-3 ${ <div className={`rounded-lg px-4 py-3 ${
msg.role === 'user' ?'bg-[#1e3a5f] text-white text-sm' :'bg-[#111827] border border-[#1e2a3a] text-[#ccc] text-sm' msg.role === 'user' ?'bg-[#4a3500] text-white text-sm' :'bg-[#111827] border border-[#1e2a3a] text-[#ccc] text-sm'
}`}> }`}>
{msg.role === 'user' ? ( {msg.role === 'user' ? (
<p className="whitespace-pre-wrap">{msg.content}</p> <p className="whitespace-pre-wrap">{msg.content}</p>
@@ -1151,7 +1151,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
</button> </button>
<button <button
onClick={() => setShowDraftPreview(true)} onClick={() => setShowDraftPreview(true)}
className="text-[10px] text-[#3b82f6] hover:text-[#60a5fa] bg-[#111827] border border-[#3b82f6]/30 px-2 py-1 rounded transition-colors font-medium" className="text-[10px] text-[#ffb800] hover:text-[#ffd25a] bg-[#111827] border border-[#ffb800]/30 px-2 py-1 rounded transition-colors font-medium"
> >
Preview Draft Preview Draft
</button> </button>
@@ -1195,9 +1195,9 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<div className="flex justify-start"> <div className="flex justify-start">
<div className="bg-[#111827] border border-[#1e2a3a] rounded-lg px-4 py-3"> <div className="bg-[#111827] border border-[#1e2a3a] rounded-lg px-4 py-3">
<div className="flex gap-1"> <div className="flex gap-1">
<div className="w-1.5 h-1.5 bg-[#3b82f6] rounded-full animate-bounce" style={{ animationDelay: '0ms' }} /> <div className="w-1.5 h-1.5 bg-[#ffb800] rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<div className="w-1.5 h-1.5 bg-[#3b82f6] rounded-full animate-bounce" style={{ animationDelay: '150ms' }} /> <div className="w-1.5 h-1.5 bg-[#ffb800] rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<div className="w-1.5 h-1.5 bg-[#3b82f6] rounded-full animate-bounce" style={{ animationDelay: '300ms' }} /> <div className="w-1.5 h-1.5 bg-[#ffb800] rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div> </div>
</div> </div>
</div> </div>
@@ -1225,13 +1225,13 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
onChange={e => setUrlInput(e.target.value)} onChange={e => setUrlInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleFetchUrl()} onKeyDown={e => e.key === 'Enter' && handleFetchUrl()}
placeholder="https://example.com/press-release" placeholder="https://example.com/press-release"
className="flex-1 bg-[#1e2a3a] border border-[#2a3a50] text-white text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]" className="flex-1 bg-[#1e2a3a] border border-[#2a3a50] text-white text-sm rounded px-3 py-2 focus:outline-none focus:border-[#ffb800] placeholder-[#444]"
autoFocus autoFocus
/> />
<button <button
onClick={handleFetchUrl} onClick={handleFetchUrl}
disabled={fetchingUrl} disabled={fetchingUrl}
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white text-xs px-3 py-2 rounded transition-colors disabled:opacity-50" className="bg-[#ffb800] hover:bg-[#d99700] text-white text-xs px-3 py-2 rounded transition-colors disabled:opacity-50"
> >
{fetchingUrl ? 'Fetching...' : 'Add'} {fetchingUrl ? 'Fetching...' : 'Add'}
</button> </button>
@@ -1244,19 +1244,19 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<div className="flex gap-2 mb-2"> <div className="flex gap-2 mb-2">
<button <button
onClick={() => setShowUrlInput(!showUrlInput)} onClick={() => setShowUrlInput(!showUrlInput)}
className="text-xs text-[#555] hover:text-[#3b82f6] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors" className="text-xs text-[#555] hover:text-[#ffb800] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
> >
+ Add URL + Add URL
</button> </button>
<button <button
onClick={() => setInput(prev => prev + '\n\n[Paste story content here for reference]')} onClick={() => setInput(prev => prev + '\n\n[Paste story content here for reference]')}
className="text-xs text-[#555] hover:text-[#3b82f6] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors" className="text-xs text-[#555] hover:text-[#ffb800] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
> >
+ Add Story + Add Story
</button> </button>
<button <button
onClick={() => setInput(prev => prev + `\n\nWrite in the style of ${selectedPenName}.`)} onClick={() => setInput(prev => prev + `\n\nWrite in the style of ${selectedPenName}.`)}
className="text-xs text-[#555] hover:text-[#3b82f6] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors" className="text-xs text-[#555] hover:text-[#ffb800] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
> >
+ Add Style + Add Style
</button> </button>
@@ -1274,13 +1274,13 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
}} }}
placeholder="Ask the AI to write a story, generate headlines, translate content..." placeholder="Ask the AI to write a story, generate headlines, translate content..."
rows={3} rows={3}
className="flex-1 bg-[#111827] border border-[#1e2a3a] text-white text-sm rounded-lg px-4 py-3 focus:outline-none focus:border-[#3b82f6] placeholder-[#333] resize-none" className="flex-1 bg-[#111827] border border-[#1e2a3a] text-white text-sm rounded-lg px-4 py-3 focus:outline-none focus:border-[#ffb800] placeholder-[#333] resize-none"
/> />
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<button <button
onClick={() => handleSend()} onClick={() => handleSend()}
disabled={isStreaming || !input.trim()} disabled={isStreaming || !input.trim()}
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium flex-1" className="bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium flex-1"
> >
{isStreaming ? ( {isStreaming ? (
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">

View File

@@ -117,7 +117,7 @@ export default function AIConsoleSettingsPage() {
if (loading || loadingSettings) { if (loading || loadingSettings) {
return ( return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center"> <div className="min-h-screen bg-[#0d1117] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#3b82f6]/30 border-t-[#3b82f6] rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800]/30 border-t-[#ffb800] rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -127,14 +127,14 @@ export default function AIConsoleSettingsPage() {
{/* Header */} {/* Header */}
<div className="bg-[#111827] border-b border-[#1e2a3a] px-6 py-4 flex items-center justify-between"> <div className="bg-[#111827] border-b border-[#1e2a3a] px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/dashboard/ai-console" className="text-[#555] hover:text-[#3b82f6] text-sm transition-colors"> AI Console</Link> <Link href="/dashboard/ai-console" className="text-[#555] hover:text-[#ffb800] text-sm transition-colors"> AI Console</Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
<h1 className="text-white font-bold text-sm">AI Console Settings</h1> <h1 className="text-white font-bold text-sm">AI Console Settings</h1>
</div> </div>
<button <button
onClick={handleSave} onClick={handleSave}
disabled={saving} disabled={saving}
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium" className="bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
> >
{saving ? 'Saving...' : 'Save Settings'} {saving ? 'Saving...' : 'Save Settings'}
</button> </button>
@@ -177,7 +177,7 @@ export default function AIConsoleSettingsPage() {
value={settings.ollama_endpoint} value={settings.ollama_endpoint}
onChange={e => update('ollama_endpoint', e.target.value)} onChange={e => update('ollama_endpoint', e.target.value)}
placeholder="http://192.168.1.x:11434 or https://your-tunnel.ngrok.io" placeholder="http://192.168.1.x:11434 or https://your-tunnel.ngrok.io"
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] placeholder-[#333]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#ffb800] placeholder-[#333]"
/> />
<p className="text-[#444] text-[10px] mt-1"> <p className="text-[#444] text-[10px] mt-1">
Use direct IP if on the same network, or a tunnel URL (ngrok, Cloudflare Tunnel, Tailscale) for remote access. Use direct IP if on the same network, or a tunnel URL (ngrok, Cloudflare Tunnel, Tailscale) for remote access.
@@ -212,7 +212,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.ollama_model_default} value={settings.ollama_model_default}
onChange={e => update('ollama_model_default', e.target.value)} onChange={e => update('ollama_model_default', 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-[#ffb800]"
> >
<option value="">Auto (largest available)</option> <option value="">Auto (largest available)</option>
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)} {connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
@@ -223,7 +223,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.ollama_model_translation} value={settings.ollama_model_translation}
onChange={e => update('ollama_model_translation', e.target.value)} onChange={e => update('ollama_model_translation', 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-[#ffb800]"
> >
<option value="">Same as default</option> <option value="">Same as default</option>
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)} {connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
@@ -234,7 +234,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.ollama_model_background} value={settings.ollama_model_background}
onChange={e => update('ollama_model_background', e.target.value)} onChange={e => update('ollama_model_background', 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-[#ffb800]"
> >
<option value="">Same as default</option> <option value="">Same as default</option>
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)} {connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
@@ -250,7 +250,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('auto_failover', !settings.auto_failover)} onClick={() => update('auto_failover', !settings.auto_failover)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.auto_failover ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.auto_failover ? 'bg-[#ffb800]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.auto_failover ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.auto_failover ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -263,7 +263,7 @@ export default function AIConsoleSettingsPage() {
value={settings.failover_timeout_seconds} value={settings.failover_timeout_seconds}
onChange={e => update('failover_timeout_seconds', Number(e.target.value))} onChange={e => update('failover_timeout_seconds', Number(e.target.value))}
min={3} max={30} min={3} max={30}
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#3b82f6]" className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
</div> </div>
@@ -301,7 +301,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.default_site_id} value={settings.default_site_id}
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-[#ffb800]"
> >
<option value={1}>AV Beat</option> <option value={1}>AV Beat</option>
<option value={2}>AV Beat</option> <option value={2}>AV Beat</option>
@@ -312,7 +312,7 @@ export default function AIConsoleSettingsPage() {
<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)}
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-[#ffb800]"
> >
{BB_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)} {BB_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)}
</select> </select>
@@ -322,7 +322,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.default_pen_name_av} value={settings.default_pen_name_av}
onChange={e => update('default_pen_name_av', e.target.value)} onChange={e => update('default_pen_name_av', 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-[#ffb800]"
> >
{AV_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)} {AV_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)}
</select> </select>
@@ -335,7 +335,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('save_session_history', !settings.save_session_history)} onClick={() => update('save_session_history', !settings.save_session_history)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.save_session_history ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.save_session_history ? 'bg-[#ffb800]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.save_session_history ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.save_session_history ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -346,7 +346,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.history_retention_days} value={settings.history_retention_days}
onChange={e => update('history_retention_days', Number(e.target.value))} onChange={e => update('history_retention_days', 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-[#ffb800]"
> >
<option value={30}>30 days</option> <option value={30}>30 days</option>
<option value={60}>60 days</option> <option value={60}>60 days</option>
@@ -361,7 +361,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('streaming_enabled', !settings.streaming_enabled)} onClick={() => update('streaming_enabled', !settings.streaming_enabled)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.streaming_enabled ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.streaming_enabled ? 'bg-[#ffb800]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.streaming_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.streaming_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -380,7 +380,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('background_use_local', !settings.background_use_local)} onClick={() => update('background_use_local', !settings.background_use_local)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.background_use_local ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.background_use_local ? 'bg-[#ffb800]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.background_use_local ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.background_use_local ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -393,7 +393,7 @@ export default function AIConsoleSettingsPage() {
value={settings.background_timeout_seconds} value={settings.background_timeout_seconds}
onChange={e => update('background_timeout_seconds', Number(e.target.value))} onChange={e => update('background_timeout_seconds', Number(e.target.value))}
min={30} max={300} min={30} max={300}
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#3b82f6]" className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
@@ -404,7 +404,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('priority_queue_enabled', !settings.priority_queue_enabled)} onClick={() => update('priority_queue_enabled', !settings.priority_queue_enabled)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.priority_queue_enabled ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.priority_queue_enabled ? 'bg-[#ffb800]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.priority_queue_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.priority_queue_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -422,7 +422,7 @@ export default function AIConsoleSettingsPage() {
'Classification and extraction tasks', 'Classification and extraction tasks',
].map((item, i) => ( ].map((item, i) => (
<li key={i} className="flex items-center gap-2 text-xs text-[#555]"> <li key={i} className="flex items-center gap-2 text-xs text-[#555]">
<span className="w-4 h-4 rounded-full bg-[#1e2a3a] text-[#3b82f6] text-[10px] flex items-center justify-center font-bold flex-shrink-0">{i + 1}</span> <span className="w-4 h-4 rounded-full bg-[#1e2a3a] text-[#ffb800] text-[10px] flex items-center justify-center font-bold flex-shrink-0">{i + 1}</span>
{item} {item}
</li> </li>
))} ))}

View File

@@ -130,9 +130,9 @@ export default function OverviewTab({ companyId, selectedCompany }: Props) {
<Line <Line
type="monotone" type="monotone"
dataKey="revenue" dataKey="revenue"
stroke={selectedCompany === 'rmp' ? '#3b82f6' : '#a855f7'} stroke={selectedCompany === 'rmp' ? '#ffb800' : '#a855f7'}
strokeWidth={2} strokeWidth={2}
dot={{ fill: selectedCompany === 'rmp' ? '#3b82f6' : '#a855f7', r: 3 }} dot={{ fill: selectedCompany === 'rmp' ? '#ffb800' : '#a855f7', r: 3 }}
/> />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>

View File

@@ -269,13 +269,13 @@ export default function EditorialDashboard() {
function SortIcon({ col }: { col: typeof sortBy }) { function SortIcon({ col }: { col: typeof sortBy }) {
if (sortBy !== col) return <span className="text-[#444] ml-1"></span>; if (sortBy !== col) return <span className="text-[#444] ml-1"></span>;
return <span className="text-[#3b82f6] ml-1">{sortDir === 'desc' ? '↓' : '↑'}</span>; return <span className="text-[#ffb800] ml-1">{sortDir === 'desc' ? '↓' : '↑'}</span>;
} }
if (loading || !authChecked) { if (loading || !authChecked) {
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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -294,7 +294,7 @@ export default function EditorialDashboard() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link <Link
href="/admin/articles" href="/admin/articles"
className="px-3 py-1.5 text-xs font-semibold bg-[#3b82f6] hover:bg-blue-500 text-white rounded transition-colors" className="px-3 py-1.5 text-xs font-semibold bg-[#ffb800] hover:bg-blue-500 text-white rounded transition-colors"
> >
+ New Article + New Article
</Link> </Link>
@@ -344,7 +344,7 @@ export default function EditorialDashboard() {
onClick={() => setStatusFilter(s)} onClick={() => setStatusFilter(s)}
className={`px-3 py-1.5 text-xs font-semibold capitalize transition-colors ${ className={`px-3 py-1.5 text-xs font-semibold capitalize transition-colors ${
statusFilter === s statusFilter === s
? 'bg-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#666] hover:text-[#aaa]' : 'text-[#666] hover:text-[#aaa]'
}`} }`}
> >
@@ -360,7 +360,7 @@ export default function EditorialDashboard() {
placeholder="Search articles, clients, campaigns..." placeholder="Search articles, clients, campaigns..."
value={searchQuery} value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} onChange={e => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#1e1e1e] rounded-lg px-3 py-1.5 text-xs text-[#ccc] placeholder-[#444] focus:outline-none focus:border-[#3b82f6] transition-colors" className="w-full bg-[#111] border border-[#1e1e1e] rounded-lg px-3 py-1.5 text-xs text-[#ccc] placeholder-[#444] focus:outline-none focus:border-[#ffb800] transition-colors"
/> />
{searchQuery && ( {searchQuery && (
<button <button
@@ -379,7 +379,7 @@ export default function EditorialDashboard() {
<div className="bg-[#0d0d0d] border border-[#1e1e1e] rounded-xl overflow-hidden"> <div className="bg-[#0d0d0d] border border-[#1e1e1e] rounded-xl overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filtered.length === 0 ? ( ) : filtered.length === 0 ? (
<div className="text-center py-20 text-[#444] text-sm">No articles found</div> <div className="text-center py-20 text-[#444] text-sm">No articles found</div>
@@ -437,7 +437,7 @@ export default function EditorialDashboard() {
<span className="text-[#555]">{article.author_name}</span> <span className="text-[#555]">{article.author_name}</span>
)} )}
{article.category && ( {article.category && (
<span className="text-[#3b82f6]/70 bg-[#3b82f6]/10 px-1.5 py-0.5 rounded text-[10px]"> <span className="text-[#ffb800]/70 bg-[#ffb800]/10 px-1.5 py-0.5 rounded text-[10px]">
{article.category} {article.category}
</span> </span>
)} )}

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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] 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-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors" className="px-3 py-1.5 bg-[#ffb800] 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-[#3b82f6] text-white' ? 'bg-[#ffb800] 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-[#3b82f6] text-white' ? 'border-[#ffb800] 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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#ffb800] 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-[#3b82f6] hover:underline">{event.official_site}</a> className="text-[#ffb800] 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-[#3b82f6] text-white' ? 'bg-[#ffb800] 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-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50" className="flex-1 py-2 bg-[#ffb800] 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-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50" className="flex-1 py-2 bg-[#ffb800] 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

@@ -25,13 +25,13 @@ export default function Error({
<div className="flex gap-3 justify-center"> <div className="flex gap-3 justify-center">
<button <button
onClick={() => reset()} onClick={() => reset()}
className="px-4 py-2 bg-[#3b82f6] text-white text-sm rounded-sm hover:bg-[#2563eb] transition-colors" className="px-4 py-2 bg-[#ffb800] text-white text-sm rounded-sm hover:bg-[#d99700] transition-colors"
> >
Try again Try again
</button> </button>
<Link <Link
href="/home-page" href="/home-page"
className="px-4 py-2 border border-[#333] text-sm rounded-sm hover:border-[#3b82f6] transition-colors" className="px-4 py-2 border border-[#333] text-sm rounded-sm hover:border-[#ffb800] transition-colors"
> >
Home Home
</Link> </Link>

View File

@@ -74,7 +74,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-[#3b82f6]' }; if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#ffb800]' };
return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' }; return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' };
}; };
@@ -94,7 +94,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-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#666] hover:text-[#aaa]' : 'text-[#666] hover:text-[#aaa]'
}`} }`}
> >
@@ -142,11 +142,11 @@ 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-[#3b82f6] transition-colors truncate leading-snug"> <p className="text-white font-body text-sm font-semibold group-hover:text-[#ffb800] 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-[#3b82f6] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#ffb800] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
</div> </div>
@@ -279,10 +279,10 @@ export default function ForumCategoryPage() {
<Header /> <Header />
<main className="min-h-screen bg-[#111111]"> <main className="min-h-screen bg-[#111111]">
{/* Breadcrumb + Header */} {/* Breadcrumb + Header */}
<div className="bg-[#1a2535] border-b border-[#2a3a50]"> <div className="bg-[#1a1208] border-b border-[#2a3a50]">
<div className="max-w-container mx-auto px-4 py-8"> <div className="max-w-container mx-auto px-4 py-8">
<nav className="text-sm font-body text-[#666] mb-3"> <nav className="text-sm font-body text-[#666] mb-3">
<Link href="/forum" className="hover:text-[#3b82f6] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#ffb800] transition-colors">Forum</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<span className="text-[#aab4c4]">{category?.name || slug}</span> <span className="text-[#aab4c4]">{category?.name || slug}</span>
</nav> </nav>
@@ -299,14 +299,14 @@ export default function ForumCategoryPage() {
{isAuthenticated ? ( {isAuthenticated ? (
<button <button
onClick={() => setShowNewThread(!showNewThread)} onClick={() => setShowNewThread(!showNewThread)}
className="flex-shrink-0 bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors" className="flex-shrink-0 bg-[#ffb800] hover:bg-[#d99700] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors"
> >
+ New Thread + New Thread
</button> </button>
) : ( ) : (
<Link <Link
href={`/forum/login?next=${typeof window === 'undefined' ? '/forum' : encodeURIComponent(window.location.pathname)}`} href={`/forum/login?next=${typeof window === 'undefined' ? '/forum' : encodeURIComponent(window.location.pathname)}`}
className="flex-shrink-0 bg-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors" className="flex-shrink-0 bg-[#ffb800] hover:bg-[#d99700] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors"
> >
Sign in to post Sign in to post
</Link> </Link>
@@ -318,7 +318,7 @@ export default function ForumCategoryPage() {
<div className="max-w-container mx-auto px-4 py-6"> <div className="max-w-container mx-auto px-4 py-6">
{/* New Thread Form — auth-gated */} {/* New Thread Form — auth-gated */}
{showNewThread && isAuthenticated && ( {showNewThread && isAuthenticated && (
<form onSubmit={handleNewThread} className="bg-[#1a2535] border border-[#2a3a50] rounded-lg p-5 mb-6"> <form onSubmit={handleNewThread} className="bg-[#1a1208] border border-[#2a3a50] rounded-lg p-5 mb-6">
<h3 className="text-white font-heading font-semibold mb-4">Start a New Thread</h3> <h3 className="text-white font-heading font-semibold mb-4">Start a New Thread</h3>
<div className="space-y-3"> <div className="space-y-3">
<input <input
@@ -327,7 +327,7 @@ export default function ForumCategoryPage() {
value={newTitle} value={newTitle}
onChange={e => setNewTitle(e.target.value)} onChange={e => setNewTitle(e.target.value)}
required required
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]" 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-[#ffb800]"
/> />
<textarea <textarea
placeholder="What would you like to discuss? *" placeholder="What would you like to discuss? *"
@@ -335,14 +335,14 @@ export default function ForumCategoryPage() {
onChange={e => setNewBody(e.target.value)} onChange={e => setNewBody(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-[#3b82f6] resize-none" className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#ffb800] resize-none"
/> />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<ImageAttachButton onInsert={(md) => setNewBody((b) => (b || "") + md)} /> <ImageAttachButton onInsert={(md) => setNewBody((b) => (b || "") + md)} />
<span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span> <span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span>
</div> </div>
{newError && ( {newError && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{newError} {newError}
</div> </div>
)} )}
@@ -350,7 +350,7 @@ export default function ForumCategoryPage() {
<button <button
type="submit" type="submit"
disabled={submitting} disabled={submitting}
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
{submitting ? 'Posting...' : 'Post Thread'} {submitting ? 'Posting...' : 'Post Thread'}
</button> </button>
@@ -378,7 +378,7 @@ export default function ForumCategoryPage() {
placeholder="Search threads in this category..." placeholder="Search threads in this category..."
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-[#3b82f6] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors" className="w-full bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#ffb800] 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
@@ -401,7 +401,7 @@ export default function ForumCategoryPage() {
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-[#3b82f6] text-white' ? 'bg-[#ffb800] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -445,12 +445,12 @@ export default function ForumCategoryPage() {
<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-[#3b82f6] rounded-lg px-4 py-3 transition-all group" className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#ffb800] rounded-lg px-4 py-3 transition-all group"
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
{thread.status === 'pinned' && ( {thread.status === 'pinned' && (
<span className="text-xs bg-[#3b82f6]/20 text-[#3b82f6] px-2 py-0.5 rounded font-body font-semibold">PINNED</span> <span className="text-xs bg-[#ffb800]/20 text-[#ffb800] px-2 py-0.5 rounded font-body font-semibold">PINNED</span>
)} )}
{thread.status === 'closed' && ( {thread.status === 'closed' && (
<span className="text-xs bg-[#555]/20 text-[#888] px-2 py-0.5 rounded font-body">CLOSED</span> <span className="text-xs bg-[#555]/20 text-[#888] px-2 py-0.5 rounded font-body">CLOSED</span>
@@ -458,12 +458,12 @@ export default function ForumCategoryPage() {
{thread.reply_count === 0 && ( {thread.reply_count === 0 && (
<span className="text-xs bg-[#f59e0b]/10 text-[#f59e0b] px-2 py-0.5 rounded font-body">UNANSWERED</span> <span className="text-xs bg-[#f59e0b]/10 text-[#f59e0b] px-2 py-0.5 rounded font-body">UNANSWERED</span>
)} )}
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#3b82f6] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#ffb800] transition-colors truncate">
{thread.title} {thread.title}
</h3> </h3>
</div> </div>
<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-[#3b82f6] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#ffb800] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
</div> </div>

View File

@@ -33,7 +33,7 @@ function ForgotInner() {
<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-[#3b82f6] font-mono mb-2">Account recovery</div> <div className="text-[10px] uppercase tracking-widest text-[#ffb800] font-mono mb-2">Account recovery</div>
<h1 className="text-3xl font-heading font-bold text-white">Reset your password</h1> <h1 className="text-3xl font-heading font-bold text-white">Reset your password</h1>
<p className="text-[#888] font-body text-sm mt-2"> <p className="text-[#888] font-body text-sm mt-2">
Enter your email we&rsquo;ll send a link to set a new password. Enter your email we&rsquo;ll send a link to set a new password.
@@ -44,7 +44,7 @@ function ForgotInner() {
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"
> >
{err && ( {err && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{err} {err}
</div> </div>
)} )}
@@ -63,19 +63,19 @@ function ForgotInner() {
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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={busy || !email.trim()} disabled={busy || !email.trim()}
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors" className="w-full bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors"
> >
{busy ? "Sending…" : "Send reset link"} {busy ? "Sending…" : "Send reset link"}
</button> </button>
<div className="text-center text-xs font-body text-[#888] pt-2 border-t border-[#252525]"> <div className="text-center text-xs font-body text-[#888] pt-2 border-t border-[#252525]">
Remembered it?{" "} Remembered it?{" "}
<Link href="/forum/login" className="text-[#3b82f6] hover:underline font-semibold"> <Link href="/forum/login" className="text-[#ffb800] hover:underline font-semibold">
Back to sign in Back to sign in
</Link> </Link>
</div> </div>

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-[#3b82f6] font-mono mb-2">Welcome back</div> <div className="text-[10px] uppercase tracking-widest text-[#ffb800] 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.
@@ -65,7 +65,7 @@ function ForumLoginInner() {
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"
> >
{error && ( {error && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors" className="w-full bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] hover:underline" className="text-[#888] hover:text-[#ffb800] 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-[#3b82f6] hover:underline" className="text-[#ffb800] 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-[#3b82f6]' }; if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#ffb800]' };
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-[#3b82f6] text-white' ? 'bg-[#ffb800] 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-[#3b82f6] transition-colors truncate leading-snug"> <p className="text-white font-body text-sm font-semibold group-hover:text-[#ffb800] 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-[#3b82f6] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#ffb800] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{thread.forum_categories && ( {thread.forum_categories && (
<> · <span className="text-[#3b82f6]">{thread.forum_categories.name}</span></> <> · <span className="text-[#ffb800]">{thread.forum_categories.name}</span></>
)} )}
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
@@ -286,7 +286,7 @@ export default function ForumIndexPage() {
<main className="min-h-screen bg-[#111111]"> <main className="min-h-screen bg-[#111111]">
{/* Hero — width-aligned with content below + featured-box pattern */} {/* Hero — width-aligned with content below + featured-box pattern */}
<div className="max-w-container mx-auto px-4"> <div className="max-w-container mx-auto px-4">
<div className="bg-[#1a2535] border-b border-[#2a3a50] py-5 px-4"> <div className="bg-[#1a1208] border-b border-[#2a3a50] py-5 px-4">
<div className="flex items-start justify-between gap-4 flex-wrap"> <div className="flex items-start justify-between gap-4 flex-wrap">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
@@ -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-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors" className="inline-flex items-center gap-2 bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors" className="inline-flex items-center gap-2 bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] text-white' : 'text-[#888] hover:text-white' feedMode === 'discover' ? 'bg-[#ffb800] 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-[#3b82f6] text-white' : 'text-[#888] hover:text-white' feedMode === 'following' ? 'bg-[#ffb800] 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">
@@ -359,11 +359,11 @@ export default function ForumIndexPage() {
{feedMode === 'following' && ( {feedMode === 'following' && (
<div className="mb-8"> <div className="mb-8">
{!currentUserId && ( {!currentUserId && (
<div className="bg-[#1a2535] border border-[#2a3a50] rounded-lg p-8 text-center"> <div className="bg-[#1a1208] border border-[#2a3a50] rounded-lg p-8 text-center">
<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-[#3b82f6] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#2563eb] transition-colors" className="inline-block bg-[#ffb800] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#d99700] 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-[#3b82f6] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#2563eb] transition-colors" className="inline-block bg-[#ffb800] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#d99700] 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-[#3b82f6] rounded-lg px-4 py-3 transition-all group" className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#ffb800] 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-[#3b82f6] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#ffb800] 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-[#3b82f6] hover:underline" className="text-[#ffb800] 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-[#3b82f6] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors" className="w-full bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#ffb800] 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-[#3b82f6] rounded-lg pl-3 pr-8 py-2.5 text-sm font-body text-white focus:outline-none transition-colors cursor-pointer min-w-[160px]" className="appearance-none bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#ffb800] 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-[#3b82f6] text-white' ? 'bg-[#ffb800] 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-[#3b82f6] rounded-lg px-4 py-3 transition-all group" className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#ffb800] 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-[#3b82f6] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#ffb800] 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-[#3b82f6] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#ffb800] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{thread.forum_categories && ( {thread.forum_categories && (
<> · <span className="text-[#3b82f6]">{thread.forum_categories.name}</span></> <> · <span className="text-[#ffb800]">{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-[#3b82f6] rounded-lg p-4 transition-all group" className="flex items-center gap-4 bg-[#1a1a1a] hover:bg-[#1e2a3a] border border-[#252525] hover:border-[#ffb800] 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-[#3b82f6] transition-colors"> <h2 className="text-white font-heading font-semibold text-lg group-hover:text-[#ffb800] 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-[#3b82f6] flex-shrink-0 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#555] group-hover:text-[#ffb800] 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>
@@ -624,12 +624,12 @@ export default function ForumIndexPage() {
)} )}
{/* Forum guidelines */} {/* Forum guidelines */}
<div className="mt-8 bg-[#1a2535] border border-[#2a3a50] rounded-lg p-5"> <div className="mt-8 bg-[#1a1208] border border-[#2a3a50] rounded-lg p-5">
<h3 className="text-white font-heading font-semibold mb-2">Community Guidelines</h3> <h3 className="text-white font-heading font-semibold mb-2">Community Guidelines</h3>
<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-[#3b82f6]">AV Beat AI</span> assistant is available in threads to help answer questions it is clearly labeled</li> <li> The <span className="text-[#ffb800]">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

@@ -104,7 +104,7 @@ export default function EditForumProfilePage() {
<h1 className="text-2xl font-heading font-bold text-white">Edit Profile</h1> <h1 className="text-2xl font-heading font-bold text-white">Edit Profile</h1>
<Link <Link
href={`/forum/user/${profile.username}`} href={`/forum/user/${profile.username}`}
className="text-xs font-body text-[#3b82f6] hover:underline" className="text-xs font-body text-[#ffb800] hover:underline"
> >
View public profile View public profile
</Link> </Link>
@@ -115,7 +115,7 @@ export default function EditForumProfilePage() {
className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4" className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4"
> >
{error && ( {error && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -132,7 +132,7 @@ export default function EditForumProfilePage() {
onChange={(e) => update('username', e.target.value)} onChange={(e) => update('username', e.target.value)}
pattern="[A-Za-z0-9_]{3,24}" pattern="[A-Za-z0-9_]{3,24}"
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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
<p className="text-[11px] font-body text-[#666] mt-1"> <p className="text-[11px] font-body text-[#666] mt-1">
3-24 characters. Letters, numbers, underscore. 3-24 characters. Letters, numbers, underscore.
@@ -145,7 +145,7 @@ export default function EditForumProfilePage() {
value={profile.display_name} value={profile.display_name}
onChange={(e) => update('display_name', e.target.value)} onChange={(e) => update('display_name', e.target.value)}
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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</Field> </Field>
@@ -154,7 +154,7 @@ export default function EditForumProfilePage() {
value={profile.bio || ''} value={profile.bio || ''}
onChange={(e) => update('bio', e.target.value)} onChange={(e) => update('bio', e.target.value)}
rows={4} rows={4}
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] resize-none" 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-[#ffb800] resize-none"
/> />
</Field> </Field>
@@ -164,7 +164,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.role_title || ''} value={profile.role_title || ''}
onChange={(e) => update('role_title', e.target.value)} onChange={(e) => update('role_title', e.target.value)}
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</Field> </Field>
<Field label="Company"> <Field label="Company">
@@ -172,7 +172,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.company || ''} value={profile.company || ''}
onChange={(e) => update('company', e.target.value)} onChange={(e) => update('company', e.target.value)}
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</Field> </Field>
</div> </div>
@@ -183,7 +183,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.location_city || ''} value={profile.location_city || ''}
onChange={(e) => update('location_city', e.target.value)} onChange={(e) => update('location_city', e.target.value)}
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</Field> </Field>
<Field label="Country"> <Field label="Country">
@@ -191,7 +191,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.location_country || ''} value={profile.location_country || ''}
onChange={(e) => update('location_country', e.target.value)} onChange={(e) => update('location_country', e.target.value)}
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</Field> </Field>
</div> </div>
@@ -202,14 +202,14 @@ export default function EditForumProfilePage() {
value={profile.avatar_url || ''} value={profile.avatar_url || ''}
onChange={(e) => update('avatar_url', e.target.value)} onChange={(e) => update('avatar_url', e.target.value)}
placeholder="https://…" placeholder="https://…"
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</Field> </Field>
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors" className="bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors"
> >
{saving ? 'Saving…' : 'Save Profile'} {saving ? 'Saving…' : 'Save Profile'}
</button> </button>

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-[#3b82f6] font-mono mb-3">Membership</div> <div className="text-[10px] uppercase tracking-widest text-[#ffb800] 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-[#3b82f6] hover:underline">Contact us</Link> if you represent a manufacturer or agency. <Link href="/about/advertise" className="text-[#ffb800] hover:underline">Contact us</Link> if you represent a manufacturer or agency.
</div> </div>
</aside> </aside>
@@ -148,11 +148,11 @@ 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-[#3b82f6] font-mono mb-1">Create your account</div> <div className="text-[10px] uppercase tracking-widest text-[#ffb800] 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 && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors" className="w-full bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] underline">terms</Link> <Link href="/terms" className="text-[#888] hover:text-[#ffb800] underline">terms</Link>
{' '}and{' '} {' '}and{' '}
<Link href="/privacy" className="text-[#888] hover:text-[#3b82f6] underline">privacy policy</Link>. <Link href="/privacy" className="text-[#888] hover:text-[#ffb800] 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-[#3b82f6] hover:underline font-semibold" className="text-[#ffb800] hover:underline font-semibold"
> >
Sign in Sign in
</Link> </Link>

View File

@@ -43,7 +43,7 @@ function ResetInner() {
<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-[#3b82f6] font-mono mb-2">Reset password</div> <div className="text-[10px] uppercase tracking-widest text-[#ffb800] font-mono mb-2">Reset password</div>
<h1 className="text-3xl font-heading font-bold text-white">Set a new password</h1> <h1 className="text-3xl font-heading font-bold text-white">Set a new password</h1>
</div> </div>
{hasSession === false ? ( {hasSession === false ? (
@@ -53,7 +53,7 @@ function ResetInner() {
</p> </p>
<Link <Link
href="/forum/forgot-password" href="/forum/forgot-password"
className="inline-block bg-[#3b82f6] hover:bg-[#2563eb] text-white font-semibold text-sm px-5 py-2 rounded" className="inline-block bg-[#ffb800] hover:bg-[#d99700] text-white font-semibold text-sm px-5 py-2 rounded"
> >
Request a new link Request a new link
</Link> </Link>
@@ -64,7 +64,7 @@ function ResetInner() {
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"
> >
{err && ( {err && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{err} {err}
</div> </div>
)} )}
@@ -84,7 +84,7 @@ function ResetInner() {
autoComplete="new-password" autoComplete="new-password"
minLength={8} minLength={8}
required required
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<div> <div>
@@ -97,13 +97,13 @@ function ResetInner() {
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 text-sm focus:outline-none focus:border-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={busy} disabled={busy}
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-semibold text-sm px-5 py-3 rounded-lg" className="w-full bg-[#ffb800] hover:bg-[#d99700] disabled:opacity-50 text-white font-semibold text-sm px-5 py-3 rounded-lg"
> >
{busy ? "Saving…" : "Update password"} {busy ? "Saving…" : "Update password"}
</button> </button>

View File

@@ -67,13 +67,13 @@ 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-[#3b82f6] flex items-center justify-center flex-shrink-0"> <div className="w-9 h-9 rounded-full bg-[#4a3500] border border-[#ffb800] flex items-center justify-center flex-shrink-0">
<span className="text-[#3b82f6] text-xs font-bold">AI</span> <span className="text-[#ffb800] text-xs font-bold">AI</span>
</div> </div>
); );
} }
const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2); const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
const colors = ['#1e3a5f', '#1a3a2a', '#3a1a1a', '#2a1a3a', '#1a2a3a']; const colors = ['#4a3500', '#1a3a2a', '#3a1a1a', '#2a1a3a', '#1a2a3a'];
const colorIdx = name.charCodeAt(0) % colors.length; const colorIdx = name.charCodeAt(0) % colors.length;
return ( return (
<div <div
@@ -133,7 +133,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
} }
}; };
const scoreColor = counts.vote_score > 0 ? 'text-[#3b82f6]' : counts.vote_score < 0 ? 'text-red-400' : 'text-[#666]'; const scoreColor = counts.vote_score > 0 ? 'text-[#ffb800]' : 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-[#3b82f6]/20 border-[#3b82f6] text-[#3b82f6]' ? 'bg-[#ffb800]/20 border-[#ffb800] text-[#ffb800]'
: 'bg-transparent border-[#2a2a2a] text-[#666] hover:border-[#3b82f6] hover:text-[#3b82f6]' : 'bg-transparent border-[#2a2a2a] text-[#666] hover:border-[#ffb800] hover:text-[#ffb800]'
} 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-[#3b82f6] ml-1 transition-colors"> <Link href="/login" className="text-xs font-body text-[#555] hover:text-[#ffb800] 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-[#3b82f6] hover:underline font-body text-sm"> <Link href="/forum" className="mt-4 inline-block text-[#ffb800] hover:underline font-body text-sm">
Back to Forum Back to Forum
</Link> </Link>
</div> </div>
@@ -339,14 +339,14 @@ export default function ForumThreadPage() {
<Header /> <Header />
<main className="min-h-screen bg-[#111111]"> <main className="min-h-screen bg-[#111111]">
{/* Breadcrumb */} {/* Breadcrumb */}
<div className="bg-[#1a2535] border-b border-[#2a3a50]"> <div className="bg-[#1a1208] 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-[#3b82f6] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#ffb800] transition-colors">Forum</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
{category && ( {category && (
<> <>
<Link href={`/forum/${category.slug}`} className="hover:text-[#3b82f6] transition-colors">{category.name}</Link> <Link href={`/forum/${category.slug}`} className="hover:text-[#ffb800] 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-[#3b82f6] hover:underline">{thread.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#ffb800] 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>
@@ -395,7 +395,7 @@ export default function ForumThreadPage() {
<div <div
className={`border rounded-lg p-5 ${ className={`border rounded-lg p-5 ${
reply.is_ai_response reply.is_ai_response
? 'bg-[#0d1f35] border-[#1e3a5f]' ? 'bg-[#0d1f35] border-[#4a3500]'
: 'bg-[#1a1a1a] border-[#252525]' : 'bg-[#1a1a1a] border-[#252525]'
}`} }`}
> >
@@ -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-[#3b82f6] hover:underline">{reply.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(reply.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#ffb800] hover:underline">{reply.author_name}</Link>
{reply.is_ai_response && ( {reply.is_ai_response && (
<span className="text-xs bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/30 px-2 py-0.5 rounded font-body font-semibold"> <span className="text-xs bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/30 px-2 py-0.5 rounded font-body font-semibold">
AI Assistant AI Assistant
</span> </span>
)} )}
@@ -441,14 +441,14 @@ 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-[#4a3500] 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-[#3b82f6] flex items-center justify-center"> <div className="w-7 h-7 rounded-full bg-[#4a3500] border border-[#ffb800] flex items-center justify-center">
<span className="text-[#3b82f6] text-xs font-bold">AI</span> <span className="text-[#ffb800] 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-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/30 px-2 py-0.5 rounded font-body">AI Not a human</span> <span className="ml-2 text-xs bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/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-[#3b82f6] disabled:opacity-50" className="flex-1 bg-[#111] border border-[#2a3a50] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#3a5a7a] focus:outline-none focus:border-[#ffb800] disabled:opacity-50"
/> />
<button <button
type="submit" type="submit"
disabled={aiLoading || !aiQuestion.trim()} disabled={aiLoading || !aiQuestion.trim()}
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors flex-shrink-0" className="bg-[#ffb800] hover:bg-[#d99700] 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,21 +495,21 @@ 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-[#3b82f6] resize-none" className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#ffb800] 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)} />
<span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span> <span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span>
</div> </div>
{replyError && ( {replyError && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{replyError} {replyError}
</div> </div>
)} )}
<button <button
type="submit" type="submit"
disabled={submitting || !replyBody.trim()} disabled={submitting || !replyBody.trim()}
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#ffb800] hover:bg-[#d99700] 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>
@@ -518,7 +518,7 @@ export default function ForumThreadPage() {
)} )}
{thread.status !== 'closed' && !isAuthenticated && ( {thread.status !== 'closed' && !isAuthenticated && (
<div className="bg-[#0d1f35] border border-[#1e3a5f] rounded-lg p-5 text-center"> <div className="bg-[#0d1f35] border border-[#4a3500] rounded-lg p-5 text-center">
<h3 className="text-white font-heading font-semibold mb-1">Join the discussion</h3> <h3 className="text-white font-heading font-semibold mb-1">Join the discussion</h3>
<p className="text-[#7a9ab8] font-body text-sm mb-4"> <p className="text-[#7a9ab8] font-body text-sm mb-4">
Sign in or create a free account to post replies. Sign in or create a free account to post replies.
@@ -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-[#3b82f6] hover:bg-[#2563eb] text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] text-[#3b82f6] hover:bg-[#3b82f6]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="border border-[#ffb800] text-[#ffb800] hover:bg-[#ffb800]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
Register Register
</Link> </Link>

View File

@@ -124,7 +124,7 @@ export default function ForumUserPage() {
<div className="max-w-container mx-auto px-4 py-12"> <div className="max-w-container mx-auto px-4 py-12">
<h1 className="font-heading text-2xl font-bold text-[#e8e8e8] mb-2">User not found</h1> <h1 className="font-heading text-2xl font-bold text-[#e8e8e8] mb-2">User not found</h1>
<p className="font-body text-sm text-[#888]">No forum member with username "{username}".</p> <p className="font-body text-sm text-[#888]">No forum member with username "{username}".</p>
<Link href="/forum" className="font-body text-sm text-[#3b82f6] hover:underline mt-4 inline-block"> Back to The Crew Lounge</Link> <Link href="/forum" className="font-body text-sm text-[#ffb800] hover:underline mt-4 inline-block"> Back to The Crew Lounge</Link>
</div> </div>
</main> </main>
<Footer /> <Footer />
@@ -148,7 +148,7 @@ export default function ForumUserPage() {
{profile.avatar_url ? ( {profile.avatar_url ? (
<AppImage src={profile.avatar_url} alt={profile.display_name} width={96} height={96} className="w-full h-full object-cover" /> <AppImage src={profile.avatar_url} alt={profile.display_name} width={96} height={96} className="w-full h-full object-cover" />
) : ( ) : (
<span className="font-heading text-2xl font-bold text-[#3b82f6]"> <span className="font-heading text-2xl font-bold text-[#ffb800]">
{profile.display_name.charAt(0).toUpperCase()} {profile.display_name.charAt(0).toUpperCase()}
</span> </span>
)} )}
@@ -162,13 +162,13 @@ export default function ForumUserPage() {
{isOwnProfile && ( {isOwnProfile && (
<Link <Link
href="/forum/profile/edit" href="/forum/profile/edit"
className="text-xs font-body font-bold uppercase tracking-wider bg-[#3b82f6] hover:bg-[#2563eb] text-white px-3 py-1.5 rounded-sm transition-colors" className="text-xs font-body font-bold uppercase tracking-wider bg-[#ffb800] hover:bg-[#d99700] text-white px-3 py-1.5 rounded-sm transition-colors"
> >
Edit Profile Edit Profile
</Link> </Link>
)} )}
</div> </div>
<p className="font-body text-sm text-[#3b82f6] mt-0.5">@{profile.username}</p> <p className="font-body text-sm text-[#ffb800] mt-0.5">@{profile.username}</p>
<div className="flex flex-wrap items-center gap-2 mt-3"> <div className="flex flex-wrap items-center gap-2 mt-3">
{archetypeLabel && ( {archetypeLabel && (
@@ -225,8 +225,8 @@ export default function ForumUserPage() {
) : ( ) : (
<ul className="space-y-2 mb-8"> <ul className="space-y-2 mb-8">
{threads.map((t) => ( {threads.map((t) => (
<li key={t.id} className="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm p-3 hover:border-[#3b82f6]/50 transition-colors"> <li key={t.id} className="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm p-3 hover:border-[#ffb800]/50 transition-colors">
<Link href={`/forum/thread/${t.id}`} className="font-body text-sm font-semibold text-[#e0e0e0] hover:text-[#3b82f6] block"> <Link href={`/forum/thread/${t.id}`} className="font-body text-sm font-semibold text-[#e0e0e0] hover:text-[#ffb800] block">
{t.title} {t.title}
</Link> </Link>
<div className="flex items-center gap-3 mt-1 text-[11px] text-[#666]"> <div className="flex items-center gap-3 mt-1 text-[11px] text-[#666]">
@@ -252,7 +252,7 @@ export default function ForumUserPage() {
<ul className="space-y-2"> <ul className="space-y-2">
{replies.map((r) => ( {replies.map((r) => (
<li key={r.id} className="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm p-3"> <li key={r.id} className="bg-[#1a1a1a] border border-[#2a2a2a] rounded-sm p-3">
<Link href={`/forum/thread/${r.thread_id}`} className="font-body text-xs font-semibold text-[#3b82f6] hover:underline"> <Link href={`/forum/thread/${r.thread_id}`} className="font-body text-xs font-semibold text-[#ffb800] hover:underline">
{r.forum_threads?.title || "(thread)"} {r.forum_threads?.title || "(thread)"}
</Link> </Link>
<p className="font-body text-sm text-[#bfbfbf] leading-relaxed mt-1 line-clamp-3">{r.body}</p> <p className="font-body text-sm text-[#bfbfbf] leading-relaxed mt-1 line-clamp-3">{r.body}</p>

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-[#3b82f6] transition-colors"> className="bg-[#111] border border-[#222] overflow-hidden group hover:border-[#ffb800] 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-[#3b82f6] text-white font-body text-[9px] font-bold px-2 py-0.5 tracking-wider uppercase"> <span className="bg-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-3"> <h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-2 group-hover:text-[#ffb800] 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: "#3b82f6", color: "white", border: 0, cursor: "pointer", borderRadius: 2 }} style={{ padding: "8px 16px", background: "#ffb800", 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-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]" className="bg-[#111] border border-[#2a2a2a] text-[#aaa] font-body text-[11px] font-semibold uppercase tracking-wide rounded-sm pl-2.5 pr-7 py-1 appearance-none focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800]/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-[#3b82f6] ${ 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-[#ffb800] ${
feedMode === "pagination" ?"bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" feedMode === "pagination" ?"bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/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-[#3b82f6] ${ 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-[#ffb800] ${
feedMode === "infinite" ?"bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" feedMode === "infinite" ?"bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/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-[#3b82f6] transition-colors [color-scheme:dark]" className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#ffb800] 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-[#3b82f6] transition-colors [color-scheme:dark]" className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#ffb800] 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-[#3b82f6] whitespace-nowrap ${ className={`flex items-center gap-1.5 px-3 py-2 rounded-sm border font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] whitespace-nowrap ${
showAdvanced || hasAdvancedActive showAdvanced || hasAdvancedActive
? "bg-[#3b82f6]/15 text-[#3b82f6] border-[#3b82f6]/40" ? "bg-[#ffb800]/15 text-[#ffb800] border-[#ffb800]/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-[#3b82f6] inline-block ml-0.5" aria-label="Active filters" /> <span className="w-1.5 h-1.5 rounded-full bg-[#ffb800] 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-[#3b82f6] ${ className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${
isActive isActive
? "bg-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50" ? "bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/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-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]/30 transition-colors [color-scheme:dark]" className="w-full bg-[#111] border border-[#2a2a2a] text-[#ccc] font-body text-sm rounded-sm px-3 py-1.5 pr-8 appearance-none focus:outline-none focus:border-[#ffb800] focus:ring-1 focus:ring-[#ffb800]/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-[#3b82f6] ${ className={`px-3 py-1 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${
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-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" : sf === "Live" ?"bg-emerald-500/20 text-emerald-400 border border-emerald-500/40" :"bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/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-[#3b82f6] hover:underline focus:outline-none focus-visible:underline"> className="font-body text-[11px] text-[#ffb800] 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-[#3b82f6] hover:underline focus:outline-none focus-visible:underline"> className="ml-1 text-[#ffb800] 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-[#3b82f6] hover:underline focus:outline-none focus-visible:underline"> className="mt-4 font-body text-sm text-[#ffb800] 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-[#3b82f6] bg-gradient-to-r from-[#3b82f6]/8 to-transparent pl-3 -ml-2 mr-0' ? 'border-l-4 border-[#ffb800] bg-gradient-to-r from-[#ffb800]/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-[#3b82f6]" className="flex-shrink-0 img-zoom overflow-hidden w-[100px] h-[70px] sm:w-[140px] sm:h-[95px] md:w-[190px] md:h-[128px] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]"
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-[#3b82f6]/15 border border-[#3b82f6]/40 px-1.5 py-0.5 rounded uppercase tracking-widest" title={article?.promoted_advertiser ? `Advertiser coverage: ${article.promoted_advertiser}` : 'Advertiser coverage'}> <span className="font-body text-[9px] font-bold text-[#7aa7d4] bg-[#ffb800]/15 border border-[#ffb800]/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-[#3b82f6] uppercase tracking-wide"> <span className="font-body text-[10px] font-bold text-[#ffb800] 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-[#3b82f6] transition-colors duration-200 line-clamp-2"> <h3 className="font-heading text-[#e0e0e0] font-bold text-[0.875rem] md:text-[1rem] leading-snug mb-1 md:mb-1.5 group-hover:text-[#ffb800] transition-colors duration-200 line-clamp-2">
<Link <Link
href={articleHref} href={articleHref}
{...linkProps} {...linkProps}
className="focus:outline-none focus-visible:text-[#3b82f6] focus-visible:underline"> className="focus:outline-none focus-visible:text-[#ffb800] focus-visible:underline">
{article?.title} {article?.title}
</Link> </Link>
</h3> </h3>
@@ -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-[#3b82f6] hover:bg-[#1a1a1a] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#ffb800] 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-[#3b82f6] animate-bounce" style={{ animationDelay: "0ms" }} /> <span className="w-1.5 h-1.5 rounded-full bg-[#ffb800] animate-bounce" style={{ animationDelay: "0ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] animate-bounce" style={{ animationDelay: "150ms" }} /> <span className="w-1.5 h-1.5 rounded-full bg-[#ffb800] animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#3b82f6] animate-bounce" style={{ animationDelay: "300ms" }} /> <span className="w-1.5 h-1.5 rounded-full bg-[#ffb800] 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-[#3b82f6]" className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]"
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-[#3b82f6] ${page === p ? "active" : ""}`} className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${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-[#3b82f6]" className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]"
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

@@ -173,7 +173,7 @@ export default function FeaturedBento() {
type="button" type="button"
onClick={() => { goPrev(); setPaused(true); }} onClick={() => { goPrev(); setPaused(true); }}
aria-label="Previous stories" aria-label="Previous stories"
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#3b82f6] hover:border-[#3b82f6] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#ffb800] hover:border-[#ffb800] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<polyline points="15 18 9 12 15 6" /> <polyline points="15 18 9 12 15 6" />
</svg> </svg>
@@ -182,7 +182,7 @@ export default function FeaturedBento() {
type="button" type="button"
onClick={() => { goNext(); setPaused(true); }} onClick={() => { goNext(); setPaused(true); }}
aria-label="Next stories" aria-label="Next stories"
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#3b82f6] hover:border-[#3b82f6] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#ffb800] hover:border-[#ffb800] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<polyline points="9 18 15 12 9 6" /> <polyline points="9 18 15 12 9 6" />
</svg> </svg>
@@ -210,7 +210,7 @@ export default function FeaturedBento() {
/> />
<div className="hero-overlay absolute inset-0" /> <div className="hero-overlay absolute inset-0" />
<div className="absolute inset-0 flex flex-col justify-end p-3 sm:p-4 md:p-5"> <div className="absolute inset-0 flex flex-col justify-end p-3 sm:p-4 md:p-5">
<span className="inline-block bg-[#3b82f6] text-white font-body text-[10px] font-bold px-2 py-0.5 tracking-wider uppercase mb-2 w-fit"> <span className="inline-block bg-[#ffb800] text-white font-body text-[10px] font-bold px-2 py-0.5 tracking-wider uppercase mb-2 w-fit">
{hero?.category} {hero?.category}
</span> </span>
<h2 className="font-heading text-white text-lg sm:text-xl md:text-2xl font-bold leading-tight mb-2 max-w-xl"> <h2 className="font-heading text-white text-lg sm:text-xl md:text-2xl font-bold leading-tight mb-2 max-w-xl">
@@ -236,7 +236,7 @@ export default function FeaturedBento() {
<Link <Link
key={`${article?.slug}-${offset}-${i}`} key={`${article?.slug}-${offset}-${i}`}
href={`/articles/${article?.slug}`} href={`/articles/${article?.slug}`}
className="bg-[#1a1a1a] border border-[#2a2a2a] overflow-hidden group cursor-pointer card-accent-hover flex flex-col hover:border-[#3b82f6] transition-colors"> className="bg-[#1a1a1a] border border-[#2a2a2a] overflow-hidden group cursor-pointer card-accent-hover flex flex-col hover:border-[#ffb800] transition-colors">
<div className="relative overflow-hidden img-zoom h-[90px] sm:h-[110px] md:h-[120px]"> <div className="relative overflow-hidden img-zoom h-[90px] sm:h-[110px] md:h-[120px]">
<AppImage <AppImage
src={article?.image} src={article?.image}
@@ -246,13 +246,13 @@ export default function FeaturedBento() {
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 50vw, 22vw" sizes="(max-width: 640px) 50vw, (max-width: 1024px) 50vw, 22vw"
/> />
<div className="absolute top-0 left-0"> <div className="absolute top-0 left-0">
<span className="bg-[#3b82f6] text-white font-body text-[9px] font-bold px-1.5 py-0.5 tracking-wider uppercase"> <span className="bg-[#ffb800] text-white font-body text-[9px] font-bold px-1.5 py-0.5 tracking-wider uppercase">
{article?.category} {article?.category}
</span> </span>
</div> </div>
</div> </div>
<div className="p-2 md:p-2.5 flex-1 flex flex-col justify-between"> <div className="p-2 md:p-2.5 flex-1 flex flex-col justify-between">
<h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-3 group-hover:text-[#3b82f6] transition-colors duration-200 mb-1.5 md:mb-2"> <h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-3 group-hover:text-[#ffb800] transition-colors duration-200 mb-1.5 md:mb-2">
{article?.title} {article?.title}
</h3> </h3>
<span className="read-more inline-block text-[11px]">Read More »</span> <span className="read-more inline-block text-[11px]">Read More »</span>
@@ -270,7 +270,7 @@ export default function FeaturedBento() {
aria-label={`Go to position ${i + 1}`} aria-label={`Go to position ${i + 1}`}
className={`rounded-full transition-all focus:outline-none ${ className={`rounded-full transition-all focus:outline-none ${
i === offset i === offset
? "w-4 h-1.5 bg-[#3b82f6]" ? "w-4 h-1.5 bg-[#ffb800]"
: "w-1.5 h-1.5 bg-[#333] hover:bg-[#555]" : "w-1.5 h-1.5 bg-[#333] hover:bg-[#555]"
}`} }`}
/> />

View File

@@ -17,7 +17,7 @@ export default function NewsTicker() {
const doubled = [...headlines, ...headlines]; const doubled = [...headlines, ...headlines];
return ( return (
<div className="bg-[#cc0000] overflow-hidden border-b border-[#aa0000]"> <div className="bg-[#d60701] overflow-hidden border-b border-[#a30501]">
<div className="flex items-stretch h-8"> <div className="flex items-stretch h-8">
{/* Label */} {/* Label */}
<div className="flex-shrink-0 bg-[#080808] flex items-center px-4 z-10"> <div className="flex-shrink-0 bg-[#080808] flex items-center px-4 z-10">

View File

@@ -60,13 +60,13 @@ export default function NewsletterSignup() {
return ( return (
<section className="max-w-container mx-auto px-4" aria-labelledby={`${formId}-heading`}> <section className="max-w-container mx-auto px-4" aria-labelledby={`${formId}-heading`}>
<div className="bg-[#0d1520] border border-[#1e3a5f] py-8 md:py-10"> <div className="bg-[#0d1520] border border-[#4a3500] py-8 md:py-10">
<div className="max-w-2xl mx-auto text-center px-4"> <div className="max-w-2xl mx-auto text-center px-4">
{/* Header */} {/* Header */}
<div className="flex items-center justify-center gap-3 mb-3"> <div className="flex items-center justify-center gap-3 mb-3">
<div className="flex-1 h-px bg-[#1e3a5f] max-w-[80px]" /> <div className="flex-1 h-px bg-[#4a3500] max-w-[80px]" />
<span className="section-label" id={`${formId}-heading`}>Newsletter</span> <span className="section-label" id={`${formId}-heading`}>Newsletter</span>
<div className="flex-1 h-px bg-[#1e3a5f] max-w-[80px]" /> <div className="flex-1 h-px bg-[#4a3500] max-w-[80px]" />
</div> </div>
<p className="font-heading text-[#e0e0e0] text-xl md:text-2xl font-bold mb-1.5"> <p className="font-heading text-[#e0e0e0] text-xl md:text-2xl font-bold mb-1.5">
Stay Ahead of the Pro AV Industry Stay Ahead of the Pro AV Industry
@@ -77,12 +77,12 @@ export default function NewsletterSignup() {
{submitted ? ( {submitted ? (
<div <div
className="bg-[#1a2535] border border-[#3b82f6] rounded-sm px-6 py-8 text-center" className="bg-[#1a1208] border border-[#ffb800] rounded-sm px-6 py-8 text-center"
role="status" role="status"
aria-live="polite"> aria-live="polite">
<div className="w-10 h-10 rounded-full bg-[#3b82f6]/20 flex items-center justify-center mx-auto mb-3"> <div className="w-10 h-10 rounded-full bg-[#ffb800]/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="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M4 10l4 4 8-8" stroke="#ffb800" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<p className="font-heading text-[#e0e0e0] font-bold text-lg mb-1">You&apos;re subscribed!</p> <p className="font-heading text-[#e0e0e0] font-bold text-lg mb-1">You&apos;re subscribed!</p>
@@ -111,7 +111,7 @@ export default function NewsletterSignup() {
className="sr-only" className="sr-only"
aria-label={`Subscribe to ${cat} news`} aria-label={`Subscribe to ${cat} news`}
/> />
<span aria-hidden="true" className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#3b82f6] border-[#3b82f6]" : "border-[#444]"}`}> <span aria-hidden="true" className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#ffb800] border-[#ffb800]" : "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" />
@@ -147,7 +147,7 @@ export default function NewsletterSignup() {
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className={`btn-subscribe py-2.5 px-6 text-sm whitespace-nowrap focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d1520] ${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-[#ffb800] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d1520] ${loading ? "opacity-70 cursor-not-allowed" : ""}`}>
{loading ? "Subscribing..." : "Subscribe Free"} {loading ? "Subscribing..." : "Subscribe Free"}
</button> </button>
</div> </div>
@@ -164,7 +164,7 @@ export default function NewsletterSignup() {
<p className="font-body text-[#555] text-xs mt-3"> <p className="font-body text-[#555] text-xs mt-3">
No spam. Unsubscribe anytime. By subscribing you agree to our{" "} No spam. Unsubscribe anytime. By subscribing you agree to our{" "}
<a href="/privacy" className="text-[#3b82f6] hover:underline focus:outline-none focus-visible:underline"> <a href="/privacy" className="text-[#ffb800] hover:underline focus:outline-none focus-visible:underline">
Privacy Policy Privacy Policy
</a>. </a>.
</p> </p>

View File

@@ -156,14 +156,14 @@ export default function SpotlightCarousel() {
ref={prevBtnRef} ref={prevBtnRef}
onClick={() => go(-1)} onClick={() => go(-1)}
aria-label="Previous spotlight (Left arrow key)" aria-label="Previous spotlight (Left arrow key)"
className="carousel-btn w-7 h-7 border border-[#333] bg-[#1a1a1a] hover:border-[#3b82f6] hover:text-[#3b82f6] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="carousel-btn w-7 h-7 border border-[#333] bg-[#1a1a1a] hover:border-[#ffb800] hover:text-[#ffb800] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
<ChevronLeftIcon size={13} /> <ChevronLeftIcon size={13} />
</button> </button>
<button <button
ref={nextBtnRef} ref={nextBtnRef}
onClick={() => go(1)} onClick={() => go(1)}
aria-label="Next spotlight (Right arrow key)" aria-label="Next spotlight (Right arrow key)"
className="carousel-btn w-7 h-7 border border-[#333] bg-[#1a1a1a] hover:border-[#3b82f6] hover:text-[#3b82f6] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="carousel-btn w-7 h-7 border border-[#333] bg-[#1a1a1a] hover:border-[#ffb800] hover:text-[#ffb800] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
<ChevronRightIcon size={13} /> <ChevronRightIcon size={13} />
</button> </button>
</div> </div>
@@ -181,7 +181,7 @@ export default function SpotlightCarousel() {
key={`${startIndex}-${i}`} key={`${startIndex}-${i}`}
href={item.href} href={item.href}
role="listitem" role="listitem"
className={`spotlight-card group block spotlight-slide-in focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${ className={`spotlight-card group block spotlight-slide-in focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${
i >= 3 ? "hidden lg:block" : i >= 2 ? "hidden md:block" : "" i >= 3 ? "hidden lg:block" : i >= 2 ? "hidden md:block" : ""
}`} }`}
style={{ animationDelay: `${i * 50}ms` }} style={{ animationDelay: `${i * 50}ms` }}
@@ -201,10 +201,10 @@ export default function SpotlightCarousel() {
</div> </div>
</div> </div>
<div className="p-2.5 md:p-3 border-t border-[#2a2a2a]"> <div className="p-2.5 md:p-3 border-t border-[#2a2a2a]">
<p className="font-body text-[11px] font-bold text-[#3b82f6] uppercase tracking-wide mb-1"> <p className="font-body text-[11px] font-bold text-[#ffb800] uppercase tracking-wide mb-1">
{item.name} {item.name}
</p> </p>
<h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-2 group-hover:text-[#3b82f6] transition-colors duration-200"> <h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-2 group-hover:text-[#ffb800] transition-colors duration-200">
{item.title} {item.title}
</h3> </h3>
</div> </div>
@@ -221,7 +221,7 @@ export default function SpotlightCarousel() {
role="tab" role="tab"
aria-selected={Math.floor(startIndex / VISIBLE) === i} aria-selected={Math.floor(startIndex / VISIBLE) === i}
aria-label={`Go to spotlight page ${i + 1}`} aria-label={`Go to spotlight page ${i + 1}`}
className={`h-1.5 rounded-full transition-all duration-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] ${ className={`h-1.5 rounded-full transition-all duration-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${
Math.floor(startIndex / VISIBLE) === i Math.floor(startIndex / VISIBLE) === i
? "w-5 bg-accent" : "w-1.5 bg-[#333] hover:bg-[#555]" ? "w-5 bg-accent" : "w-1.5 bg-[#333] hover:bg-[#555]"
}`} }`}

View File

@@ -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-[#3b82f6] focus:text-white focus:rounded focus:font-bold focus:text-sm focus:outline-none" className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[10000] focus:px-4 focus:py-2 focus:bg-[#ffb800] 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

@@ -63,7 +63,7 @@ function LoginInner() {
className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4" className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4"
> >
{error && ( {error && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#d60701] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</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-[#3b82f6]" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ffb800]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold py-2 rounded-lg transition-colors" className="w-full bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] hover:underline"> <Link href="/client-login" className="text-[#ffb800] hover:underline">
Use the contributor portal Use the contributor portal
</Link> </Link>
</p> </p>

View File

@@ -84,7 +84,7 @@ export default function ManufacturerDirectoryClient({
value={q} value={q}
onChange={(e) => setQ(e.target.value)} onChange={(e) => setQ(e.target.value)}
placeholder="Search by company or product…" placeholder="Search by company or product…"
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#3b82f6]/40" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#ffb800]/40"
/> />
</div> </div>
</div> </div>
@@ -97,7 +97,7 @@ export default function ManufacturerDirectoryClient({
type="button" type="button"
onClick={() => setShowFilter("all")} onClick={() => setShowFilter("all")}
className={`px-3 py-1.5 rounded text-xs font-semibold ${ className={`px-3 py-1.5 rounded text-xs font-semibold ${
showFilter === "all" ? "bg-[#3b82f6] text-white" : "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]" showFilter === "all" ? "bg-[#ffb800] text-white" : "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]"
}`} }`}
> >
All shows <span className="ml-1 opacity-60">({manufacturers.length.toLocaleString()})</span> All shows <span className="ml-1 opacity-60">({manufacturers.length.toLocaleString()})</span>
@@ -106,7 +106,7 @@ export default function ManufacturerDirectoryClient({
type="button" type="button"
onClick={() => setShowFilter("both-major")} onClick={() => setShowFilter("both-major")}
className={`px-3 py-1.5 rounded text-xs font-semibold ${ className={`px-3 py-1.5 rounded text-xs font-semibold ${
showFilter === "both-major" ? "bg-[#3b82f6] text-white" : "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]" showFilter === "both-major" ? "bg-[#ffb800] text-white" : "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]"
}`} }`}
> >
NAB + IBC <span className="ml-1 opacity-60">({manufacturers.filter((m) => m.exhibits_nab && m.exhibits_ibc).length})</span> NAB + IBC <span className="ml-1 opacity-60">({manufacturers.filter((m) => m.exhibits_nab && m.exhibits_ibc).length})</span>
@@ -121,7 +121,7 @@ export default function ManufacturerDirectoryClient({
type="button" type="button"
onClick={() => setShowFilter(s.slug)} onClick={() => setShowFilter(s.slug)}
className={`px-3 py-1.5 rounded text-xs font-semibold ${ className={`px-3 py-1.5 rounded text-xs font-semibold ${
active ? "bg-[#3b82f6] text-white" : "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]" active ? "bg-[#ffb800] text-white" : "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]"
}`} }`}
title={`${s.name} · ${s.start_date}${count === 0 ? " · exhibitor list coming soon" : ""}`} title={`${s.name} · ${s.start_date}${count === 0 ? " · exhibitor list coming soon" : ""}`}
> >
@@ -144,7 +144,7 @@ export default function ManufacturerDirectoryClient({
onClick={() => setLetter(l)} onClick={() => setLetter(l)}
className={`min-w-[28px] h-7 px-1.5 rounded font-semibold ${ className={`min-w-[28px] h-7 px-1.5 rounded font-semibold ${
letter === l letter === l
? "bg-[#3b82f6] text-white" ? "bg-[#ffb800] text-white"
: "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]" : "bg-[#0d0d0d] border border-[#252525] text-[#aaa] hover:bg-[#1a1a1a]"
}`} }`}
> >
@@ -162,7 +162,7 @@ export default function ManufacturerDirectoryClient({
<Link <Link
key={m.slug} key={m.slug}
href={`/manufacturers/${m.slug}`} href={`/manufacturers/${m.slug}`}
className="group bg-[#0d0d0d] border border-[#252525] rounded-xl p-4 hover:border-[#3b82f6] hover:shadow-sm transition-all flex gap-3" className="group bg-[#0d0d0d] border border-[#252525] rounded-xl p-4 hover:border-[#ffb800] hover:shadow-sm transition-all flex gap-3"
> >
<div className="w-14 h-14 rounded-lg bg-[#1a1a1a] flex items-center justify-center overflow-hidden flex-shrink-0"> <div className="w-14 h-14 rounded-lg bg-[#1a1a1a] flex items-center justify-center overflow-hidden flex-shrink-0">
{m.logo_url ? ( {m.logo_url ? (
@@ -180,7 +180,7 @@ export default function ManufacturerDirectoryClient({
)} )}
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h3 className="font-semibold text-[#e0e0e0] truncate group-hover:text-[#3b82f6] transition-colors"> <h3 className="font-semibold text-[#e0e0e0] truncate group-hover:text-[#ffb800] transition-colors">
{m.company_name} {m.company_name}
</h3> </h3>
<div className="flex items-center gap-1.5 mt-1 mb-1.5 flex-wrap"> <div className="flex items-center gap-1.5 mt-1 mb-1.5 flex-wrap">
@@ -195,7 +195,7 @@ export default function ManufacturerDirectoryClient({
</span> </span>
)} )}
{m.featured && ( {m.featured && (
<span className="text-[10px] font-semibold uppercase tracking-wider bg-[#3b82f6]/15 text-[#3b82f6] px-1.5 py-0.5 rounded"> <span className="text-[10px] font-semibold uppercase tracking-wider bg-[#ffb800]/15 text-[#ffb800] px-1.5 py-0.5 rounded">
Featured Featured
</span> </span>
)} )}

View File

@@ -29,7 +29,7 @@ export default function BackLink() {
} }
} }
return ( return (
<Link href="/manufacturers" onClick={onClick} className="hover:text-[#3b82f6]"> <Link href="/manufacturers" onClick={onClick} className="hover:text-[#ffb800]">
Back Back
</Link> </Link>
); );

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-[#3b82f6]"> <a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#888] hover:text-[#ffb800]">
{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-[#3b82f6] hover:underline" className="text-[#ffb800] 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-[#3b82f6] hover:underline" className="text-[#ffb800] 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-[#3b82f6] hover:underline" className="text-[#ffb800] 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-[#3b82f6]" className="text-[#888] hover:text-[#ffb800]"
> >
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-[#3b82f6]" className="text-[#888] hover:text-[#ffb800]"
> >
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-[#3b82f6] hover:underline" className="text-[11px] text-[#ffb800] 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-[#3b82f6] hover:underline"> <a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#ffb800] 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-[#3b82f6] hover:underline"> <a href={`mailto:${company.contact_email}`} className="text-[#ffb800] 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-[#3b82f6] hover:underline"> <Link href={`/news?search=${encodeURIComponent(company.company_name)}`} className="text-xs text-[#ffb800] 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-[#3b82f6] hover:shadow-sm transition-all flex gap-3" className="group bg-[#0d0d0d] border border-[#252525] rounded-md p-3 hover:border-[#ffb800] 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-[#3b82f6] line-clamp-3"> <h3 className="text-xs font-semibold text-[#e0e0e0] leading-snug group-hover:text-[#ffb800] 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-[#3b82f6] hover:underline" className="text-[#ffb800] 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-[#3b82f6] hover:underline"> <a href="mailto:editors@avbeat.com?subject=Manufacturer%20Directory%20Update" className="text-[#ffb800] hover:underline">
Contact our editors Contact our editors
</a> </a>
. .

View File

@@ -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-[#3b82f6] hover:underline"> <Link href="/news?search=NAB+2026" className="text-xs text-[#ffb800] 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-[#3b82f6] hover:shadow-sm transition-all" className="group bg-[#0d0d0d] border border-[#252525] rounded-lg overflow-hidden hover:border-[#ffb800] 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-[#3b82f6] line-clamp-3 mb-1"> <h3 className="font-semibold text-[#e0e0e0] text-sm leading-snug group-hover:text-[#ffb800] 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-[#3b82f6] hover:underline"> <Link href="/manufacturers" className="text-xs text-[#ffb800] 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-[#3b82f6] transition-all flex items-center gap-2.5" className="bg-[#0d0d0d] border border-[#252525] rounded-md p-2.5 hover:border-[#ffb800] 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-[#3b82f6] hover:underline">Browse the full directory </Link> <Link href="/manufacturers" className="text-[#ffb800] 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-[#3b82f6] transition-colors" className="w-full bg-[#1a1a1a] border border-[#2a2a2a] text-[#e0e0e0] placeholder-[#555] text-sm font-body pl-9 pr-4 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] 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-[#3b82f6] transition-colors [color-scheme:dark]" className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] 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-[#3b82f6] transition-colors [color-scheme:dark]" className="bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] 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-[#3b82f6] transition-colors cursor-pointer" className="appearance-none bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] text-xs font-body pl-3 pr-8 py-2.5 rounded-sm focus:outline-none focus:border-[#ffb800] 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-[#3b82f6] border-[#3b82f6] text-white" ? "bg-[#ffb800] border-[#ffb800] text-white"
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]" : "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#ffb800] hover:text-[#ffb800]"
}`} }`}
> >
{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-[#3b82f6] text-xs font-body hover:underline">Clear filters</button> <button onClick={clearFilters} className="mt-3 text-[#ffb800] 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-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">{article.category || "NEWS"}</span> <span className="text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2"> <h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#ffb800] 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-[#3b82f6] uppercase tracking-widest mb-3 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] border-[#3b82f6] text-white" ? "bg-[#ffb800] border-[#ffb800] text-white"
: "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]" : "bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#ffb800] hover:text-[#ffb800]"
}`} }`}
> >
{tag} {tag}
@@ -345,8 +345,8 @@ export default function NewsPage({
))} ))}
</div> </div>
</div> </div>
<div className="bg-[#0d1520] border border-[#1e3a5f] p-4"> <div className="bg-[#0d1520] border border-[#4a3500] p-4">
<h3 className="font-body font-bold text-xs text-[#3b82f6] uppercase tracking-widest mb-2">Newsletter</h3> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] transition-colors"> <Link href="/home-page" className="hover:text-[#ffb800] transition-colors">
Home Home
</Link> </Link>
<span>/</span> <span>/</span>
<Link href="/news" className="hover:text-[#3b82f6] transition-colors"> <Link href="/news" className="hover:text-[#ffb800] 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-[#3b82f6] hover:underline"> className="text-xs font-bold uppercase tracking-widest text-[#ffb800] 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-[#3b82f6] transition-colors text-[#e0e0e0]"> className="font-heading font-bold text-sm hover:text-[#ffb800] 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-[#3b82f6] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#ffb800] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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" />
@@ -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-[#3b82f6] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#ffb800] hover:bg-[#1a1a1a] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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-[#3b82f6] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="text-xs font-bold uppercase tracking-widest text-[#ffb800] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
{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-[#3b82f6] hover:text-[#3b82f6] transition-colors rounded-sm cursor-default"> className="px-3 py-1 text-xs font-body bg-[#1a1a1a] border border-[#2a2a2a] text-[#888] hover:border-[#ffb800] hover:text-[#ffb800] 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-[#3b82f6] text-[#888] hover:text-[#3b82f6] text-xs font-bold uppercase tracking-widest transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="flex items-center gap-2 px-4 py-2 bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#ffb800] text-[#888] hover:text-[#ffb800] text-xs font-bold uppercase tracking-widest transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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-[#3b82f6] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="inline-flex items-center gap-2 text-[#ffb800] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5"> <p className="text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#ffb800] 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-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#ffb800] 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-[#3b82f6] font-body font-bold uppercase tracking-wider"> <span className="text-[#ffb800] 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-[#3b82f6] hover:underline font-body font-semibold uppercase tracking-wider" className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#ffb800] hover:underline font-body font-semibold uppercase tracking-wider"
> >
Browse all forum threads Browse all forum threads
</Link> </Link>
@@ -602,7 +602,7 @@ export default function NewsArticleDetailClient({
)} )}
{/* Newsletter Signup */} {/* Newsletter Signup */}
<div className="bg-[#0d1520] border border-[#1e3a5f] p-5"> <div className="bg-[#0d1520] border border-[#4a3500] p-5">
<h3 className="font-heading text-[#e0e0e0] font-bold mb-2">Stay Updated</h3> <h3 className="font-heading text-[#e0e0e0] font-bold mb-2">Stay Updated</h3>
<p className="font-body text-[#777] text-xs mb-4"> <p className="font-body text-[#777] text-xs mb-4">
Get the latest pro AV industry news delivered to your inbox. Get the latest pro AV industry news delivered to your inbox.
@@ -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-[#3b82f6] hover:text-[#3b82f6] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="w-8 h-8 flex items-center justify-center border border-[#2a2a2a] text-[#666] hover:border-[#ffb800] hover:text-[#ffb800] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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-[#3b82f6] hover:text-[#3b82f6] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]"> className="w-8 h-8 flex items-center justify-center border border-[#2a2a2a] text-[#666] hover:border-[#ffb800] hover:text-[#ffb800] disabled:opacity-30 disabled:cursor-not-allowed transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<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-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider"> <span className="text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2 mb-1.5"> <h3 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug group-hover:text-[#ffb800] 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-[#3b82f6]" : "bg-[#333]" i === carouselIndex ? "bg-[#ffb800]" : "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-[#3b82f6]"> className="absolute top-4 right-4 text-[#555] hover:text-[#e0e0e0] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]">
<svg <svg
width="18" width="18"
height="18" height="18"
@@ -771,13 +771,13 @@ export default function NewsArticleDetailClient({
</button> </button>
<div className="flex items-center gap-3 mb-5"> <div className="flex items-center gap-3 mb-5">
<div className="w-9 h-9 rounded-sm bg-[#1a2a3a] border border-[#1e3a5f] flex items-center justify-center flex-shrink-0"> <div className="w-9 h-9 rounded-sm bg-[#1a2a3a] border border-[#4a3500] flex items-center justify-center flex-shrink-0">
<svg <svg
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="#3b82f6" stroke="#ffb800"
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-[#3b82f6]">*</span> Recipient Email <span className="text-[#ffb800]">*</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-[#3b82f6] text-[#e0e0e0] placeholder-[#444] text-sm font-body px-3 py-2.5 rounded-sm outline-none transition-colors" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] focus:border-[#ffb800] 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-[#3b82f6] text-[#e0e0e0] placeholder-[#444] text-sm font-body px-3 py-2.5 rounded-sm outline-none transition-colors resize-none" className="w-full bg-[#0d0d0d] border border-[#2a2a2a] focus:border-[#ffb800] 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-[#3b82f6] text-white px-4 py-3 rounded-sm text-sm font-body shadow-lg z-50"> className="fixed bottom-4 right-4 bg-[#1a1a1a] border border-[#ffb800] 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 || "#3b82f6"; const accent = newsletter.accent_color || "#ffb800";
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-[#3b82f6] font-bold mr-1">{block.category}:</span> <span className="text-[#ffb800] 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-[#3b82f6] text-xs font-medium"> <span className="font-body text-[#ffb800] text-xs font-medium">
Issue #{newsletter.id.slice(0, 6).toUpperCase()} Issue #{newsletter.id.slice(0, 6).toUpperCase()}
</span> </span>
</div> </div>
@@ -181,12 +181,12 @@ function SignupCTA() {
return ( return (
<section <section
className="bg-[#0d1520] border border-[#1e3a5f] rounded-sm p-6 md:p-8" className="bg-[#0d1520] border border-[#4a3500] rounded-sm p-6 md:p-8"
aria-labelledby={`${formId}-heading`}> aria-labelledby={`${formId}-heading`}>
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="flex-1 h-px bg-[#1e3a5f]" /> <div className="flex-1 h-px bg-[#4a3500]" />
<span className="section-label" id={`${formId}-heading`}>Subscribe</span> <span className="section-label" id={`${formId}-heading`}>Subscribe</span>
<div className="flex-1 h-px bg-[#1e3a5f]" /> <div className="flex-1 h-px bg-[#4a3500]" />
</div> </div>
<h3 className="font-heading text-[#e0e0e0] text-xl font-bold text-center mb-1"> <h3 className="font-heading text-[#e0e0e0] text-xl font-bold text-center mb-1">
Never Miss an Issue Never Miss an Issue
@@ -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-[#3b82f6]/20 flex items-center justify-center mx-auto mb-3"> <div className="w-10 h-10 rounded-full bg-[#ffb800]/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="#3b82f6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M4 10l4 4 8-8" stroke="#ffb800" 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-[#3b82f6] border-[#3b82f6]" : "border-[#444]"}`}> className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#ffb800] border-[#ffb800]" : "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-[#3b82f6] ${status === "loading" ? "opacity-70 cursor-not-allowed" : ""}`}> className={`btn-subscribe py-2.5 px-6 text-sm whitespace-nowrap focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${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-[#3b82f6] hover:underline"> <Link href="/privacy" className="text-[#ffb800] 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-[#3b82f6] ${ className={`font-body text-xs px-3 py-1.5 rounded-sm border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${
selectedTopic === topic selectedTopic === topic
? "bg-[#3b82f6] border-[#3b82f6] text-white" ? "bg-[#ffb800] border-[#ffb800] 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-[#3b82f6] hover:text-[#60a5fa] transition-colors underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="font-body text-xs text-[#ffb800] hover:text-[#ffd25a] transition-colors underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
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-[#3b82f6] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="font-body text-xs text-[#ffb800] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
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-[#3b82f6] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="font-body text-xs text-[#ffb800] hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
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-[#3b82f6] ${ className={`w-full text-left font-body text-sm py-1.5 px-2 rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${
selectedTopic === topic selectedTopic === topic
? "text-[#3b82f6] bg-[#3b82f6]/10" ? "text-[#ffb800] bg-[#ffb800]/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-[#3b82f6] ${ className={`w-full text-left font-body text-sm py-1.5 px-2 rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] ${
selectedYear === String(year) selectedYear === String(year)
? "text-[#3b82f6] bg-[#3b82f6]/10" ? "text-[#ffb800] bg-[#ffb800]/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-[#3b82f6] 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-[#ffb800] 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-[#3b82f6] underline hover:text-blue-400 transition-colors">privacy@avbeat.com</a></p> <p>Email: <a href="mailto:privacy@avbeat.com" className="text-[#ffb800] underline hover:text-blue-400 transition-colors">privacy@avbeat.com</a></p>
</address> </address>
</section> </section>

View File

@@ -95,7 +95,7 @@ function Avatar({ name, size = 'lg' }: { name: string; size?: 'sm' | 'lg' }) {
.join('') .join('')
.toUpperCase() .toUpperCase()
.slice(0, 2); .slice(0, 2);
const colors = ['#1e3a5f', '#1a3a2a', '#3a1a1a', '#2a1a3a', '#1a2a3a']; const colors = ['#4a3500', '#1a3a2a', '#3a1a1a', '#2a1a3a', '#1a2a3a'];
const colorIdx = name.charCodeAt(0) % colors.length; const colorIdx = name.charCodeAt(0) % colors.length;
const sizeClass = size === 'lg' ? 'w-20 h-20 text-2xl' : 'w-9 h-9 text-xs'; const sizeClass = size === 'lg' ? 'w-20 h-20 text-2xl' : 'w-9 h-9 text-xs';
return ( return (
@@ -113,7 +113,7 @@ function ReputationBadge({ rep }: { rep: number }) {
let color = 'text-[#888] border-[#333]'; let color = 'text-[#888] border-[#333]';
if (rep >= 1000) { label = 'Expert'; color = 'text-yellow-400 border-yellow-700'; } if (rep >= 1000) { label = 'Expert'; color = 'text-yellow-400 border-yellow-700'; }
else if (rep >= 500) { label = 'Veteran'; color = 'text-purple-400 border-purple-700'; } else if (rep >= 500) { label = 'Veteran'; color = 'text-purple-400 border-purple-700'; }
else if (rep >= 200) { label = 'Contributor'; color = 'text-[#3b82f6] border-[#3b82f6]/50'; } else if (rep >= 200) { label = 'Contributor'; color = 'text-[#ffb800] border-[#ffb800]/50'; }
else if (rep >= 50) { label = 'Member'; color = 'text-green-400 border-green-700'; } else if (rep >= 50) { label = 'Member'; color = 'text-green-400 border-green-700'; }
return ( return (
<span className={`font-body text-xs font-bold uppercase tracking-wider px-2 py-0.5 border rounded-sm ${color}`}> <span className={`font-body text-xs font-bold uppercase tracking-wider px-2 py-0.5 border rounded-sm ${color}`}>
@@ -130,9 +130,9 @@ const BADGE_CONFIG: Record<string, { label: string; icon: React.ReactNode; color
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" /> <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg> </svg>
), ),
color: 'text-[#3b82f6]', color: 'text-[#ffb800]',
bg: 'bg-[#1e3a5f]/40', bg: 'bg-[#4a3500]/40',
border: 'border-[#3b82f6]/40', border: 'border-[#ffb800]/40',
description: 'Active community contributor', description: 'Active community contributor',
}, },
mentor: { mentor: {
@@ -362,7 +362,7 @@ export default function UserProfilePage() {
<Header /> <Header />
<div className="max-w-container mx-auto px-4 py-16 text-center"> <div className="max-w-container mx-auto px-4 py-16 text-center">
<p className="font-body text-[#888] text-sm">{error || 'User not found.'}</p> <p className="font-body text-[#888] text-sm">{error || 'User not found.'}</p>
<Link href="/forum" className="mt-4 inline-block font-body text-xs font-bold uppercase tracking-wider text-[#3b82f6] hover:underline"> <Link href="/forum" className="mt-4 inline-block font-body text-xs font-bold uppercase tracking-wider text-[#ffb800] hover:underline">
Back to Forum Back to Forum
</Link> </Link>
</div> </div>
@@ -387,9 +387,9 @@ export default function UserProfilePage() {
<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-[#3b82f6] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#ffb800] transition-colors">Home</Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<Link href="/forum" className="hover:text-[#3b82f6] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#ffb800] transition-colors">Forum</Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<span className="text-[#888]">{profile.full_name || 'User Profile'}</span> <span className="text-[#888]">{profile.full_name || 'User Profile'}</span>
</nav> </nav>
@@ -415,8 +415,8 @@ export default function UserProfilePage() {
disabled={followLoading} disabled={followLoading}
className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-1.5 rounded-sm border transition-colors disabled:opacity-60 ${ className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-1.5 rounded-sm border transition-colors disabled:opacity-60 ${
followData.isFollowing followData.isFollowing
? 'bg-[#1e3a5f] text-[#3b82f6] border-[#3b82f6]/50 hover:bg-red-900/30 hover:text-red-400 hover:border-red-700/50' ? 'bg-[#4a3500] text-[#ffb800] border-[#ffb800]/50 hover:bg-red-900/30 hover:text-red-400 hover:border-red-700/50'
: 'bg-[#3b82f6] text-white border-[#3b82f6] hover:bg-[#2563eb]' : 'bg-[#ffb800] text-white border-[#ffb800] hover:bg-[#d99700]'
}`} }`}
> >
{followLoading ? '...' : followData.isFollowing ? 'Following' : '+ Follow'} {followLoading ? '...' : followData.isFollowing ? 'Following' : '+ Follow'}
@@ -425,7 +425,7 @@ export default function UserProfilePage() {
{currentUserId === userId && ( {currentUserId === userId && (
<Link <Link
href="/account" href="/account"
className="font-body text-xs font-bold uppercase tracking-wider text-[#888] hover:text-[#3b82f6] border border-[#2a2a2a] hover:border-[#3b82f6] px-3 py-1 rounded-sm transition-colors" className="font-body text-xs font-bold uppercase tracking-wider text-[#888] hover:text-[#ffb800] border border-[#2a2a2a] hover:border-[#ffb800] px-3 py-1 rounded-sm transition-colors"
> >
Edit Profile Edit Profile
</Link> </Link>
@@ -449,7 +449,7 @@ export default function UserProfilePage() {
href={profile.website.startsWith('http') ? profile.website : `https://${profile.website}`} href={profile.website.startsWith('http') ? profile.website : `https://${profile.website}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1 hover:text-[#3b82f6] transition-colors" className="flex items-center gap-1 hover:text-[#ffb800] transition-colors"
> >
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="10" />
@@ -477,7 +477,7 @@ export default function UserProfilePage() {
{/* Reputation & Stats Row */} {/* Reputation & Stats Row */}
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-3 mt-6 pt-5 border-t border-[#2a2a2a]"> <div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-3 mt-6 pt-5 border-t border-[#2a2a2a]">
<div className="text-center"> <div className="text-center">
<div className="font-heading text-2xl font-bold text-[#3b82f6]">{(profile.reputation || 0).toLocaleString()}</div> <div className="font-heading text-2xl font-bold text-[#ffb800]">{(profile.reputation || 0).toLocaleString()}</div>
<div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Reputation</div> <div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Reputation</div>
</div> </div>
<div className="text-center"> <div className="text-center">
@@ -493,11 +493,11 @@ export default function UserProfilePage() {
<div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Upvotes</div> <div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Upvotes</div>
</div> </div>
<div className="text-center cursor-pointer" onClick={() => setActiveTab('followers')}> <div className="text-center cursor-pointer" onClick={() => setActiveTab('followers')}>
<div className="font-heading text-2xl font-bold text-[#e0e0e0] hover:text-[#3b82f6] transition-colors">{followData.followerCount}</div> <div className="font-heading text-2xl font-bold text-[#e0e0e0] hover:text-[#ffb800] transition-colors">{followData.followerCount}</div>
<div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Followers</div> <div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Followers</div>
</div> </div>
<div className="text-center cursor-pointer" onClick={() => setActiveTab('following')}> <div className="text-center cursor-pointer" onClick={() => setActiveTab('following')}>
<div className="font-heading text-2xl font-bold text-[#e0e0e0] hover:text-[#3b82f6] transition-colors">{followData.followingCount}</div> <div className="font-heading text-2xl font-bold text-[#e0e0e0] hover:text-[#ffb800] transition-colors">{followData.followingCount}</div>
<div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Following</div> <div className="font-body text-xs text-[#666] uppercase tracking-wider mt-0.5">Following</div>
</div> </div>
</div> </div>
@@ -511,7 +511,7 @@ export default function UserProfilePage() {
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`font-body text-sm font-bold uppercase tracking-wider px-5 py-3 border-b-2 transition-colors flex items-center gap-2 whitespace-nowrap ${ className={`font-body text-sm font-bold uppercase tracking-wider px-5 py-3 border-b-2 transition-colors flex items-center gap-2 whitespace-nowrap ${
activeTab === tab.id activeTab === tab.id
? 'border-[#3b82f6] text-[#3b82f6]' ? 'border-[#ffb800] text-[#ffb800]'
: 'border-transparent text-[#666] hover:text-[#aaa]' : 'border-transparent text-[#666] hover:text-[#aaa]'
}`} }`}
> >
@@ -549,17 +549,17 @@ export default function UserProfilePage() {
<div key={thread.id} className="group"> <div key={thread.id} className="group">
<Link <Link
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
className="font-body text-sm text-[#ccc] hover:text-[#3b82f6] transition-colors line-clamp-2 leading-snug" className="font-body text-sm text-[#ccc] hover:text-[#ffb800] transition-colors line-clamp-2 leading-snug"
> >
{thread.title} {thread.title}
</Link> </Link>
<div className="flex items-center gap-3 mt-1 text-xs font-body text-[#555]"> <div className="flex items-center gap-3 mt-1 text-xs font-body text-[#555]">
{thread.forum_categories && ( {thread.forum_categories && (
<Link href={`/forum/${thread.forum_categories.slug}`} className="text-[#3b82f6]/70 hover:text-[#3b82f6]"> <Link href={`/forum/${thread.forum_categories.slug}`} className="text-[#ffb800]/70 hover:text-[#ffb800]">
{thread.forum_categories.name} {thread.forum_categories.name}
</Link> </Link>
)} )}
<span className={`font-semibold ${(thread.vote_score || 0) >= 0 ? 'text-[#3b82f6]' : 'text-red-400'}`}> <span className={`font-semibold ${(thread.vote_score || 0) >= 0 ? 'text-[#ffb800]' : 'text-red-400'}`}>
{(thread.vote_score || 0) > 0 ? '+' : ''}{thread.vote_score || 0} pts {(thread.vote_score || 0) > 0 ? '+' : ''}{thread.vote_score || 0} pts
</span> </span>
<span>{thread.reply_count || 0} replies</span> <span>{thread.reply_count || 0} replies</span>
@@ -585,7 +585,7 @@ export default function UserProfilePage() {
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<Link <Link
href={`/forum/${cat.slug}`} href={`/forum/${cat.slug}`}
className="font-body text-xs font-semibold text-[#aaa] hover:text-[#3b82f6] transition-colors" className="font-body text-xs font-semibold text-[#aaa] hover:text-[#ffb800] transition-colors"
> >
{cat.name} {cat.name}
</Link> </Link>
@@ -593,7 +593,7 @@ export default function UserProfilePage() {
</div> </div>
<div className="h-1.5 bg-[#2a2a2a] rounded-full overflow-hidden"> <div className="h-1.5 bg-[#2a2a2a] rounded-full overflow-hidden">
<div <div
className="h-full bg-[#3b82f6] rounded-full transition-all duration-500" className="h-full bg-[#ffb800] rounded-full transition-all duration-500"
style={{ width: `${(cat.count / maxCategoryCount) * 100}%` }} style={{ width: `${(cat.count / maxCategoryCount) * 100}%` }}
/> />
</div> </div>
@@ -621,7 +621,7 @@ export default function UserProfilePage() {
.map((entry, idx) => ( .map((entry, idx) => (
<div key={idx} className="flex items-start gap-3 py-2 border-b border-[#1e1e1e] last:border-0"> <div key={idx} className="flex items-start gap-3 py-2 border-b border-[#1e1e1e] last:border-0">
<span className={`font-body text-xs font-bold uppercase tracking-wider px-2 py-0.5 rounded-sm flex-shrink-0 mt-0.5 ${ <span className={`font-body text-xs font-bold uppercase tracking-wider px-2 py-0.5 rounded-sm flex-shrink-0 mt-0.5 ${
entry.type === 'thread' ? 'bg-[#1e3a5f] text-[#3b82f6]' : 'bg-[#1a2a1a] text-green-400' entry.type === 'thread' ? 'bg-[#4a3500] text-[#ffb800]' : 'bg-[#1a2a1a] text-green-400'
}`}> }`}>
{entry.type === 'thread' ? 'Thread' : 'Reply'} {entry.type === 'thread' ? 'Thread' : 'Reply'}
</span> </span>
@@ -629,7 +629,7 @@ export default function UserProfilePage() {
{entry.type === 'thread' ? ( {entry.type === 'thread' ? (
<Link <Link
href={`/forum/thread/${(entry.item as ForumThread).id}`} href={`/forum/thread/${(entry.item as ForumThread).id}`}
className="font-body text-sm text-[#ccc] hover:text-[#3b82f6] transition-colors line-clamp-1" className="font-body text-sm text-[#ccc] hover:text-[#ffb800] transition-colors line-clamp-1"
> >
{(entry.item as ForumThread).title} {(entry.item as ForumThread).title}
</Link> </Link>
@@ -637,7 +637,7 @@ export default function UserProfilePage() {
<div> <div>
<Link <Link
href={`/forum/thread/${(entry.item as ForumReply).thread_id}`} href={`/forum/thread/${(entry.item as ForumReply).thread_id}`}
className="font-body text-sm text-[#ccc] hover:text-[#3b82f6] transition-colors line-clamp-1" className="font-body text-sm text-[#ccc] hover:text-[#ffb800] transition-colors line-clamp-1"
> >
Re: {(entry.item as ForumReply).forum_threads?.title || 'Thread'} Re: {(entry.item as ForumReply).forum_threads?.title || 'Thread'}
</Link> </Link>
@@ -671,14 +671,14 @@ export default function UserProfilePage() {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<Link <Link
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
className="font-body text-sm font-semibold text-[#ccc] hover:text-[#3b82f6] transition-colors line-clamp-2" className="font-body text-sm font-semibold text-[#ccc] hover:text-[#ffb800] transition-colors line-clamp-2"
> >
{thread.title} {thread.title}
</Link> </Link>
<p className="font-body text-xs text-[#555] line-clamp-1 mt-1">{thread.body}</p> <p className="font-body text-xs text-[#555] line-clamp-1 mt-1">{thread.body}</p>
<div className="flex flex-wrap items-center gap-3 mt-2 text-xs font-body text-[#555]"> <div className="flex flex-wrap items-center gap-3 mt-2 text-xs font-body text-[#555]">
{thread.forum_categories && ( {thread.forum_categories && (
<Link href={`/forum/${thread.forum_categories.slug}`} className="text-[#3b82f6]/70 hover:text-[#3b82f6]"> <Link href={`/forum/${thread.forum_categories.slug}`} className="text-[#ffb800]/70 hover:text-[#ffb800]">
{thread.forum_categories.name} {thread.forum_categories.name}
</Link> </Link>
)} )}
@@ -688,7 +688,7 @@ export default function UserProfilePage() {
</div> </div>
</div> </div>
<div className="text-right flex-shrink-0"> <div className="text-right flex-shrink-0">
<div className={`font-heading text-lg font-bold ${(thread.vote_score || 0) >= 0 ? 'text-[#3b82f6]' : 'text-red-400'}`}> <div className={`font-heading text-lg font-bold ${(thread.vote_score || 0) >= 0 ? 'text-[#ffb800]' : 'text-red-400'}`}>
{(thread.vote_score || 0) > 0 ? '+' : ''}{thread.vote_score || 0} {(thread.vote_score || 0) > 0 ? '+' : ''}{thread.vote_score || 0}
</div> </div>
<div className="font-body text-xs text-[#555]">score</div> <div className="font-body text-xs text-[#555]">score</div>
@@ -714,13 +714,13 @@ export default function UserProfilePage() {
<div key={reply.id} className="p-4 hover:bg-[#1e1e1e] transition-colors"> <div key={reply.id} className="p-4 hover:bg-[#1e1e1e] transition-colors">
<Link <Link
href={`/forum/thread/${reply.thread_id}`} href={`/forum/thread/${reply.thread_id}`}
className="font-body text-xs font-bold uppercase tracking-wider text-[#3b82f6]/70 hover:text-[#3b82f6] transition-colors mb-1 block" className="font-body text-xs font-bold uppercase tracking-wider text-[#ffb800]/70 hover:text-[#ffb800] transition-colors mb-1 block"
> >
Re: {reply.forum_threads?.title || 'Thread'} Re: {reply.forum_threads?.title || 'Thread'}
</Link> </Link>
<p className="font-body text-sm text-[#aaa] line-clamp-3 leading-relaxed">{reply.body}</p> <p className="font-body text-sm text-[#aaa] line-clamp-3 leading-relaxed">{reply.body}</p>
<div className="flex items-center gap-3 mt-2 text-xs font-body text-[#555]"> <div className="flex items-center gap-3 mt-2 text-xs font-body text-[#555]">
<span className={`font-semibold ${(reply.vote_score || 0) >= 0 ? 'text-[#3b82f6]' : 'text-red-400'}`}> <span className={`font-semibold ${(reply.vote_score || 0) >= 0 ? 'text-[#ffb800]' : 'text-red-400'}`}>
{(reply.vote_score || 0) > 0 ? '+' : ''}{reply.vote_score || 0} pts {(reply.vote_score || 0) > 0 ? '+' : ''}{reply.vote_score || 0} pts
</span> </span>
<span>{timeAgo(reply.created_at)}</span> <span>{timeAgo(reply.created_at)}</span>
@@ -782,7 +782,7 @@ function FollowUserList({
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<Link <Link
href={`/profile/${u.id}`} href={`/profile/${u.id}`}
className="font-body text-sm font-semibold text-[#ccc] hover:text-[#3b82f6] transition-colors" className="font-body text-sm font-semibold text-[#ccc] hover:text-[#ffb800] transition-colors"
> >
{u.full_name || 'Anonymous User'} {u.full_name || 'Anonymous User'}
</Link> </Link>
@@ -796,7 +796,7 @@ function FollowUserList({
{currentUserId && currentUserId !== u.id && ( {currentUserId && currentUserId !== u.id && (
<Link <Link
href={`/profile/${u.id}`} href={`/profile/${u.id}`}
className="font-body text-xs font-bold uppercase tracking-wider text-[#888] hover:text-[#3b82f6] border border-[#2a2a2a] hover:border-[#3b82f6] px-3 py-1 rounded-sm transition-colors" className="font-body text-xs font-bold uppercase tracking-wider text-[#888] hover:text-[#ffb800] border border-[#2a2a2a] hover:border-[#ffb800] px-3 py-1 rounded-sm transition-colors"
> >
View View
</Link> </Link>

View File

@@ -120,8 +120,8 @@ export default function ReadingListPage() {
<aside className="float-right ml-6 mb-6 hidden lg:block max-w-[300px]"><SidebarAdStack /></aside> <aside className="float-right ml-6 mb-6 hidden lg:block max-w-[300px]"><SidebarAdStack /></aside>
{/* Toast */} {/* Toast */}
{toast && ( {toast && (
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 bg-[#1a2535] border border-[#3b82f6] text-[#e0e0e0] font-body text-sm px-5 py-3 rounded-sm shadow-lg flex items-center gap-2"> <div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 bg-[#1a1208] border border-[#ffb800] text-[#e0e0e0] font-body text-sm px-5 py-3 rounded-sm shadow-lg flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ffb800" strokeWidth="2.5" aria-hidden="true">
<polyline points="20 6 9 17 4 12" /> <polyline points="20 6 9 17 4 12" />
</svg> </svg>
{toast} {toast}
@@ -132,7 +132,7 @@ export default function ReadingListPage() {
<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-[#3b82f6] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#ffb800] transition-colors">Home</Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<span className="text-[#888]">Reading List</span> <span className="text-[#888]">Reading List</span>
</nav> </nav>
@@ -143,7 +143,7 @@ export default function ReadingListPage() {
{/* Title Row */} {/* Title Row */}
<div className="flex items-center justify-between mb-8 pb-4 border-b border-[#2a2a2a]"> <div className="flex items-center justify-between mb-8 pb-4 border-b border-[#2a2a2a]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" strokeWidth="2" aria-hidden="true"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffb800" strokeWidth="2" aria-hidden="true">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" /> <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
</svg> </svg>
<h1 className="font-heading text-xl md:text-2xl font-bold text-[#e8e8e8]">Reading List</h1> <h1 className="font-heading text-xl md:text-2xl font-bold text-[#e8e8e8]">Reading List</h1>
@@ -156,7 +156,7 @@ export default function ReadingListPage() {
{savedArticles.length > 0 && ( {savedArticles.length > 0 && (
<button <button
onClick={handleClearAll} onClick={handleClearAll}
className="font-body text-xs text-[#555] hover:text-red-400 transition-colors uppercase tracking-wide focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6]"> className="font-body text-xs text-[#555] hover:text-red-400 transition-colors uppercase tracking-wide focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800]">
Clear All Clear All
</button> </button>
)} )}
@@ -246,12 +246,12 @@ export default function ReadingListPage() {
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{item.articleCategory && ( {item.articleCategory && (
<span className="inline-block font-body text-[9px] font-bold uppercase tracking-widest text-[#3b82f6] mb-1"> <span className="inline-block font-body text-[9px] font-bold uppercase tracking-widest text-[#ffb800] mb-1">
{item.articleCategory} {item.articleCategory}
</span> </span>
)} )}
<Link href={`/articles/${item.articleSlug}`}> <Link href={`/articles/${item.articleSlug}`}>
<h2 className="font-heading text-sm md:text-base font-bold text-[#e0e0e0] group-hover:text-[#3b82f6] transition-colors leading-snug line-clamp-2 mb-1"> <h2 className="font-heading text-sm md:text-base font-bold text-[#e0e0e0] group-hover:text-[#ffb800] transition-colors leading-snug line-clamp-2 mb-1">
{item.articleTitle} {item.articleTitle}
</h2> </h2>
</Link> </Link>
@@ -278,7 +278,7 @@ export default function ReadingListPage() {
onClick={() => handleRemove(item.id, item.articleSlug)} onClick={() => handleRemove(item.id, item.articleSlug)}
disabled={removingId === item.id} disabled={removingId === item.id}
aria-label={`Remove "${item.articleTitle}" from reading list`} aria-label={`Remove "${item.articleTitle}" from reading list`}
className="flex-shrink-0 w-7 h-7 flex items-center justify-center text-[#444] hover:text-red-400 hover:bg-[#2a1a1a] rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#3b82f6] disabled:opacity-50"> className="flex-shrink-0 w-7 h-7 flex items-center justify-center text-[#444] hover:text-red-400 hover:bg-[#2a1a1a] rounded-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] disabled:opacity-50">
{removingId === item.id ? ( {removingId === item.id ? (
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="animate-spin" aria-hidden="true"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="animate-spin" aria-hidden="true">
<circle cx="12" cy="12" r="10" strokeOpacity="0.25" /> <circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
@@ -304,7 +304,7 @@ export default function ReadingListPage() {
</span> </span>
<Link <Link
href={`/articles/${item.articleSlug}`} href={`/articles/${item.articleSlug}`}
className="font-body text-[10px] font-bold uppercase tracking-wide text-[#3b82f6] hover:text-[#2563eb] transition-colors"> className="font-body text-[10px] font-bold uppercase tracking-wide text-[#ffb800] hover:text-[#d99700] transition-colors">
Read Article Read Article
</Link> </Link>
</div> </div>

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-[#3b82f6] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#ffb800] 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-[#3b82f6] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#ffb800] 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>
@@ -121,7 +121,7 @@ export default function RegisterPage() {
<form onSubmit={handleRegister} className="space-y-5" noValidate> <form onSubmit={handleRegister} className="space-y-5" noValidate>
{/* Error */} {/* Error */}
{error && ( {error && (
<div className="bg-[#1a0a0a] border border-[#cc0000] text-[#ff6b6b] font-body text-sm px-4 py-3 rounded-sm flex items-start gap-2"> <div className="bg-[#1a0a0a] border border-[#d60701] text-[#ff6b6b] font-body text-sm px-4 py-3 rounded-sm flex items-start gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="mt-0.5 flex-shrink-0" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="mt-0.5 flex-shrink-0" aria-hidden="true">
<circle cx="12" cy="12" r="10" /><line x1="15" y1="9" x2="9" y2="15" /><line x1="9" y1="9" x2="15" y2="15" /> <circle cx="12" cy="12" r="10" /><line x1="15" y1="9" x2="9" y2="15" /><line x1="9" y1="9" x2="15" y2="15" />
</svg> </svg>
@@ -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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50" className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#ffb800] focus-visible:ring-1 focus-visible:ring-[#ffb800] 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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50" className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#ffb800] focus-visible:ring-1 focus-visible:ring-[#ffb800] 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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50" className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#ffb800] focus-visible:ring-1 focus-visible:ring-[#ffb800] 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-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50" className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#ffb800] focus-visible:ring-1 focus-visible:ring-[#ffb800] 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-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-sm font-bold uppercase tracking-wider rounded-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"> className="w-full h-10 bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] text-[#888] hover:text-[#3b82f6] font-body text-sm font-bold uppercase tracking-wider rounded-sm transition-colors flex items-center justify-center"> className="block w-full h-10 border border-[#2a2a2a] hover:border-[#ffb800] text-[#888] hover:text-[#ffb800] 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-[#3b82f6] text-xs font-body hover:underline"> <Link href="/news" className="text-[#ffb800] 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-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider"> <span className="text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2"> <h2 className="font-heading text-[#e0e0e0] text-base font-bold leading-snug mb-2 group-hover:text-[#ffb800] 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

@@ -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-[#3b82f6] font-body text-[11px] underline"> <Link href="/technology" className="text-[#666] hover:text-[#ffb800] 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-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50" ? "bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/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-[#3b82f6]/20 text-[#3b82f6] border border-[#3b82f6]/50" ? "bg-[#ffb800]/20 text-[#ffb800] border border-[#ffb800]/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-[#3b82f6] hover:underline font-body text-sm"> <Link href="/technology" className="inline-block mt-3 text-[#ffb800] 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-[#3b82f6] transition-colors p-5"> className="flex flex-col md:flex-row gap-6 bg-[#111] border border-[#222] overflow-hidden group hover:border-[#ffb800] 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-[#3b82f6] font-body text-[10px] font-bold uppercase tracking-wider">{articles[0].category}</span> <span className="text-[#ffb800] 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-[#3b82f6] transition-colors"> <h2 className="font-heading text-[#e0e0e0] text-xl font-bold leading-tight mb-3 group-hover:text-[#ffb800] 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-[#3b82f6] transition-colors"> className="flex gap-4 bg-[#111] border border-[#222] p-4 group hover:border-[#ffb800] 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-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider">{article.category}</span> <span className="text-[#ffb800] 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-[#3b82f6] transition-colors line-clamp-2"> <h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-1 group-hover:text-[#ffb800] 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-[#3b82f6] hover:underline">legal@avbeat.com</a>.</p> <p>Questions about these Terms? Contact us at <a href="mailto:legal@avbeat.com" className="text-[#ffb800] hover:underline">legal@avbeat.com</a>.</p>
</section> </section>
</div> </div>
</main> </main>

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-[#3b82f6]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#ffb800]" 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-[#3b82f6] uppercase tracking-widest"> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] transition-colors flex items-center gap-1" className="text-[10px] font-body text-[#555] hover:text-[#ffb800] 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-[#3b82f6] border-[#3b82f6] text-white' ? 'bg-[#ffb800] border-[#ffb800] text-white'
: 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]' : 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#ffb800] hover:text-[#ffb800]'
}`} }`}
> >
{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-[#3b82f6] text-white px-3 py-1 rounded-sm hover:bg-[#2563eb] transition-colors" className="text-[10px] font-body bg-[#ffb800] text-white px-3 py-1 rounded-sm hover:bg-[#d99700] 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-[#3b82f6] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5"> <p className="text-[#ffb800] 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-[#3b82f6] transition-colors"> <p className="font-heading text-[#e0e0e0] text-xs font-semibold leading-snug line-clamp-2 group-hover:text-[#ffb800] 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-[#3b82f6]" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#ffb800]" 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-[#3b82f6] transition-colors flex items-center gap-1 ml-4 shrink-0" className="text-[11px] font-body text-[#555] hover:text-[#ffb800] 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-[#3b82f6] border-[#3b82f6] text-white' ? 'bg-[#ffb800] border-[#ffb800] text-white'
: 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#3b82f6] hover:text-[#3b82f6]' : 'bg-[#1a1a1a] border-[#2a2a2a] text-[#888] hover:border-[#ffb800] hover:text-[#ffb800]'
}`} }`}
> >
{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-[#3b82f6] text-white px-4 py-1.5 rounded-sm hover:bg-[#2563eb] transition-colors" className="text-xs font-body bg-[#ffb800] text-white px-4 py-1.5 rounded-sm hover:bg-[#d99700] 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-[#3b82f6] 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-[#ffb800] 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-[#3b82f6] transition-colors"> <h3 className="font-heading text-[#e0e0e0] text-sm font-semibold leading-snug line-clamp-2 group-hover:text-[#ffb800] 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-[#3b82f6]" className="bb-browse-item gap-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800]"
> >
<span>ABOUT</span> <span>ABOUT</span>
<span aria-hidden="true" className="text-[10px]"></span> <span aria-hidden="true" className="text-[10px]"></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-[#3b82f6] hover:underline">Cancel reply</button> <button onClick={() => setReplyTo(null)} className="text-[#ffb800] 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-[#3b82f6]" className="w-full bg-[#111] border border-[#252525] rounded text-white text-sm font-body p-2 resize-none focus:outline-none focus:border-[#ffb800]"
/> />
{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,27 +179,27 @@ export default function ArticleComments({ articleSlug }: Props) {
<button <button
onClick={submit} onClick={submit}
disabled={submitting || !body.trim()} disabled={submitting || !body.trim()}
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" className="bg-[#ffb800] hover:bg-[#d99700] 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>
</div> </div>
</div> </div>
) : ( ) : (
<div className="bg-gradient-to-r from-[#0d1520] to-[#0d0d0d] border border-[#1e3a5f] rounded-md p-5 mb-6 text-center"> <div className="bg-gradient-to-r from-[#0d1520] to-[#0d0d0d] border border-[#4a3500] rounded-md p-5 mb-6 text-center">
<p className="text-white font-body text-sm mb-3"> <p className="text-white font-body text-sm mb-3">
Sign in to join the discussion. Vote, reply, and follow industry conversations. Sign in to join the discussion. Vote, reply, and follow industry conversations.
</p> </p>
<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-[#3b82f6] hover:bg-[#2563eb] text-white font-body text-xs font-semibold px-5 py-2 rounded" className="bg-[#ffb800] hover:bg-[#d99700] 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-[#3b82f6] text-[#3b82f6] hover:bg-[#3b82f6]/10 font-body text-xs font-semibold px-5 py-2 rounded" className="border border-[#ffb800] text-[#ffb800] hover:bg-[#ffb800]/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-[#3b82f6] text-xs font-bold flex items-center justify-center flex-shrink-0"> <div className="w-7 h-7 rounded-full bg-[#1a1208] text-[#ffb800] 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-[#60a5fa] px-1.5 py-0.5 rounded">AI</span> <span className="ml-1.5 text-[9px] uppercase tracking-widest bg-[#4a3500] text-[#ffd25a] 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-[#3b82f6] hover:underline text-[11px]"> <button onClick={() => onReply(comment.id)} className="text-[#ffb800] hover:underline text-[11px]">
Reply Reply
</button> </button>
)} )}

View File

@@ -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-[#60a5fa] hover:underline">$1</a>' '<a href="$2" class="text-[#ffd25a] hover:underline">$1</a>'
); );
out = out.replace(/\n/g, "<br/>"); out = out.replace(/\n/g, "<br/>");
return out; return out;

View File

@@ -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-[#3b82f6] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#ffb800] 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-[#3b82f6] transition-colors truncate"> <span className="font-heading text-[#e0e0e0] text-sm font-bold group-hover:text-[#ffb800] 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-[#3b82f6] font-bold flex-shrink-0"> <div className="w-12 h-12 rounded bg-[#1a1208] flex items-center justify-center text-[#ffb800] 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-[#3b82f6] block truncate"> <Link href={`/manufacturers/${preview.slug}`} className="font-semibold text-white hover:text-[#ffb800] 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-[#60a5fa] hover:text-[#93c5fd] hover:underline line-clamp-2"> <Link href={`/news/${s.slug}`} className="text-xs text-[#ffd25a] 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-[#60a5fa] hover:text-[#93c5fd] hover:underline"> <Link href={`/manufacturers/${preview.slug}`} className="text-xs font-semibold text-[#ffd25a] 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-[#3b82f6]"> <a href={preview.website} target="_blank" rel="noopener noreferrer" className="text-[10px] text-[#888] hover:text-[#ffb800]">
{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-[#3b82f6] underline hover:text-blue-400 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors" className="text-[#ffb800] underline hover:text-blue-400 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#ffb800] 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-[#3b82f6] 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-[#ffb800] 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-[#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]" className="px-5 py-2 text-sm font-body font-bold text-white bg-[#ffb800] rounded hover:bg-blue-500 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb800] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d1117]"
aria-label="Accept all cookies" aria-label="Accept all cookies"
> >
Accept All Accept All

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