feat(brand+promotion): brand-gradient system, restyled /about/* + team + contact, alias-based client matcher

Three coupled rollouts in one shot.

PART A — client highlight matcher (DB migration applied separately):
- promoted-articles.ts: AdvertiserName now carries brand_aliases; matcher
  iterates legal name + every alias with word-boundary regex and picks the
  longest match across all candidates so "Magewell Launches…" resolves to
  "Magewell Electronics Co., Ltd." via its Magewell alias.
- New isStoryAboutClient gate: hard-accept at position 0, accept if any
  editorial frame ("Product Spotlight:", "Review:", "First Look", …) appears
  in the prefix, hard-reject if the prefix ends with a partner/usage phrase
  ("with", "using", "powered by", "dealer of", "integration with", "used",
  "deploys", "runs on", "leverages", …), otherwise accept within a 32-char
  subject window. Validated against 5,000 historical posts: 27 partner
  false-positives blocked, 3 editorial-frame stories correctly saved, the
  Magewell article now highlights.

PART B — restyling /about, /about/team, /about/contact:
- New .brand-gradient utility: #2563EB→#1E3A8A linear-gradient with a
  16%-opacity diagonal sheen that sweeps in the first 30% of a 7s cycle
  and rests offscreen-right for the rest. Respects prefers-reduced-motion
  (band parked statically at 6% opacity).
- Team page: dark legacy hero (red radial, #ff5a55 eyebrows) replaced with
  brand-gradient hero; outer text wrapper #e5e7eb → #0F172A so section
  headings stop being white-on-white; all serif H1/H2/H3 → font-display;
  red accents (#ff5a55 / #ff6b66 / rgba(214,7,1)) → blue (#93C5FD / #7DD3FC
  / rgba(29,78,216)).
- About page: H1 + Stat values → font-display; Stat cards get a brand-
  gradient top stripe.
- Contact page: outer wrapper #e5e7eb → #0F172A (fixes the white-on-white
  Contact H1); H1 gets explicit text-[#0F172A] + font-display; dark contact
  cards keep dark bg but gain a brand-gradient top stripe + explicit
  text-[#e5e7eb] body + brighter cyan eyebrow/link colors.

PART C — richer Industry News client highlight:
- ArticleFeed: client-promoted row swaps the static border-l-4 for an
  absolute-positioned .brand-gradient strip on the left edge (sheen pulses
  through). Row gets a slightly richer bg tint (from-#1D4ED8/12 via /4).
- HIGHLIGHT pill now uses .brand-gradient instead of the static blue chip.

Also shipping the earlier-staged work:
- AnimatedLogo onDark: white ring around tile + brighter (#E2E8F0) tagline
  + cyan accent rule for the blue header.
- Article-prose: em/strong/code dark colors (em fixes the trailing
  copyright/trademark ghost text), hr line color, figcaption readable.
- ArticleComments: "Sign in to join the discussion" bar uses brand-gradient
  + white/outline buttons that read on blue.
- Forum: Discover/Following, Newest/Top Voted/Unanswered, Sign in to post,
  Start a thread, Create account — slim pills, brand-gradient active.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 18:37:15 +00:00
parent 0b2ed8cd7f
commit f44add70c7
9 changed files with 257 additions and 83 deletions

View File

@@ -93,21 +93,20 @@ export default async function TeamPage() {
return (
<div className="min-h-screen bg-background">
<Header />
<div className="max-w-container mx-auto px-4 py-12 text-[#e5e7eb]">
<div className="max-w-container mx-auto px-4 py-12 text-[#0F172A]">
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10">
<main>
{/* Hero */}
<header className="relative overflow-hidden rounded-xl mb-10 border border-[#1a1f2a] bg-gradient-to-br from-[#0d1118] via-[#0b0f17] to-[#0d1118]">
<div className="absolute inset-0 opacity-[0.04] bg-[radial-gradient(ellipse_at_top_right,#d60701,transparent_60%)]" aria-hidden />
<div className="relative px-6 py-10 sm:px-10 sm:py-14">
<div className="inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.22em] text-[#ff5a55] mb-3">
<span className="w-6 h-px bg-[#1D4ED8]" />
{/* Hero — brand-gradient (animated sheen) */}
<header className="brand-gradient rounded-xl mb-10">
<div className="px-6 py-10 sm:px-10 sm:py-14">
<div className="inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.22em] text-white/85 mb-3">
<span className="w-6 h-px bg-white/60" />
AV Beat &middot; editorial
</div>
<h1 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight">
<h1 className="font-display text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight text-white">
The team behind every byline
</h1>
<p className="mt-4 text-[#9ca3af] text-base md:text-lg max-w-2xl leading-relaxed">
<p className="mt-4 text-white/80 text-base md:text-lg max-w-2xl leading-relaxed">
Beats covered by trained AI personas working under house style, plus the
business and support people who keep things moving.
</p>
@@ -117,7 +116,7 @@ export default async function TeamPage() {
{/* Editorial team — three-col grid on desktop, larger portraits */}
<section className="mb-14">
<div className="flex items-baseline justify-between mb-5">
<h2 className="font-serif text-2xl md:text-3xl font-bold tracking-tight">Editorial</h2>
<h2 className="font-display text-2xl md:text-3xl font-bold tracking-tight">Editorial</h2>
<span className="text-[10px] font-mono uppercase tracking-[0.18em] text-[#6c7686]">
{team.length} {team.length === 1 ? "writer" : "writers"}
</span>
@@ -129,7 +128,7 @@ export default async function TeamPage() {
return (
<li
key={p.slug}
className="group relative rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 hover:border-[#1D4ED8]/50 hover:shadow-[0_0_0_1px_rgba(214,7,1,0.15)] transition-all"
className="group relative rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 hover:border-[#1D4ED8]/50 hover:shadow-[0_0_0_1px_rgba(29,78,216,0.20)] transition-all"
>
{/* Top accent */}
<div className="absolute top-0 left-5 right-5 h-px bg-gradient-to-r from-transparent via-[#1D4ED8]/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" aria-hidden />
@@ -146,16 +145,16 @@ export default async function TeamPage() {
/>
</a>
) : (
<div className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] bg-[#141a25] flex items-center justify-center font-serif font-bold text-2xl text-[#6c7686] flex-shrink-0">
<div className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] bg-[#141a25] flex items-center justify-center font-display font-bold text-2xl text-[#6c7686] flex-shrink-0">
{initials(p.name)}
</div>
)}
<div className="flex-1 min-w-0">
<a href={`/authors/${p.slug}`} className="block hover:underline">
<h3 className="font-serif text-xl font-bold leading-tight">{p.name}</h3>
<h3 className="font-display text-xl font-bold leading-tight">{p.name}</h3>
</a>
{(p.beat || p.slug === "ryan-salazar") && (
<div className="inline-block mt-1.5 px-2 py-0.5 rounded-sm bg-[#1D4ED8]/10 border border-[#1D4ED8]/25 text-[10px] font-mono uppercase tracking-[0.12em] text-[#ff6b66]">
<div className="inline-block mt-1.5 px-2 py-0.5 rounded-sm bg-[#1D4ED8]/15 border border-[#1D4ED8]/35 text-[10px] font-mono uppercase tracking-[0.12em] text-[#93C5FD]">
{displayBeat(p)}
</div>
)}
@@ -173,11 +172,11 @@ export default async function TeamPage() {
<ObfuscatedEmail
user={e.user}
domain={e.domain}
className="text-[#aab1bd] hover:text-[#ff6b66] font-mono"
className="text-[#aab1bd] hover:text-[#7DD3FC] font-mono"
/>
<a
href={`/authors/${p.slug}`}
className="text-[10px] font-mono uppercase tracking-[0.12em] text-[#6c7686] hover:text-[#ff6b66]"
className="text-[10px] font-mono uppercase tracking-[0.12em] text-[#6c7686] hover:text-[#7DD3FC]"
>
View
</a>
@@ -193,14 +192,14 @@ export default async function TeamPage() {
{ops.length > 0 && (
<section>
<div className="flex items-baseline justify-between mb-5">
<h2 className="font-serif text-2xl md:text-3xl font-bold tracking-tight">
<h2 className="font-display text-2xl md:text-3xl font-bold tracking-tight">
Accounts &amp; support
</h2>
<span className="text-[10px] font-mono uppercase tracking-[0.18em] text-[#6c7686]">
{ops.length} {ops.length === 1 ? "contact" : "contacts"}
</span>
</div>
<p className="text-[#9ca3af] text-sm mb-5 max-w-2xl">
<p className="text-[#475569] text-sm mb-5 max-w-2xl">
For billing, accounts receivable, vendor onboarding, sales, and
general support questions these are the people to reach.
</p>
@@ -220,13 +219,13 @@ export default async function TeamPage() {
className="w-20 h-20 rounded-full border-2 border-[#1a1f2a] flex-shrink-0 object-cover"
/>
) : (
<div className="w-20 h-20 rounded-full border-2 border-[#1a1f2a] flex-shrink-0 bg-[#141a25] flex items-center justify-center font-serif font-bold text-xl text-[#6c7686]">
<div className="w-20 h-20 rounded-full border-2 border-[#1a1f2a] flex-shrink-0 bg-[#141a25] flex items-center justify-center font-display font-bold text-xl text-[#6c7686]">
{initials(p.display_name)}
</div>
)}
<div className="flex-1 min-w-0">
<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-[#FFFFFF] border border-[#1D4ED8]/30 text-[10px] font-mono uppercase tracking-[0.12em] text-[#7ba0ff]">
<h3 className="font-display text-lg font-bold leading-tight">{p.display_name}</h3>
<div className="inline-block mt-1 px-2 py-0.5 rounded-sm bg-[#1D4ED8]/15 border border-[#1D4ED8]/35 text-[10px] font-mono uppercase tracking-[0.12em] text-[#93C5FD]">
{p.role}
</div>
<div className="text-xs text-[#aab1bd] mt-3 space-y-1">
@@ -235,13 +234,13 @@ export default async function TeamPage() {
if (!e) return null;
return (
<div className="font-mono">
<ObfuscatedEmail user={e.user} domain={e.domain} className="hover:text-[#ff6b66]" />
<ObfuscatedEmail user={e.user} domain={e.domain} className="hover:text-[#7DD3FC]" />
</div>
);
})()}
{p.phone && (
<div>
<a href={`tel:${p.phone.replace(/[^\d+]/g, '')}`} className="hover:text-[#ff6b66] font-mono">
<a href={`tel:${p.phone.replace(/[^\d+]/g, '')}`} className="hover:text-[#7DD3FC] font-mono">
{p.phone}{p.extension ? ` ext. ${p.extension}` : ''}
</a>
</div>