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

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

View File

@@ -305,7 +305,7 @@ export default function ArticleFeed() {
{/* Section header */}
<div className="flex items-center gap-3 mb-4 md:mb-5 flex-wrap">
<span className="section-label">Industry News</span>
<div className="flex-1 h-px bg-[#3a322b]" />
<div className="flex-1 h-px bg-[#DCE6F2]" />
{/* Sort dropdown */}
<div className="flex items-center gap-1.5">
<span className="font-body text-[10px] text-[#555] uppercase tracking-wide hidden sm:inline">Sort:</span>
@@ -314,7 +314,7 @@ export default function ArticleFeed() {
value={sortOrder}
onChange={(e) => { setSortOrder(e.target.value as SortOrder); setPage(1); setInfiniteCount(ARTICLES_PER_PAGE); }}
aria-label="Sort articles"
className="bg-[#111] border border-[#3a322b] 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-[#F0A623] focus:ring-1 focus:ring-[#F0A623]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]"
className="bg-[#111] border border-[#DCE6F2] 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-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]"
>
<option value="recent">Recent</option>
<option value="trending">Trending</option>
@@ -328,13 +328,13 @@ export default function ArticleFeed() {
{/* Feed mode toggle */}
<div className="flex items-center gap-1.5" role="group" aria-label="Feed display mode">
<span className="font-body text-[10px] text-[#555] uppercase tracking-wide hidden sm:inline">View:</span>
<div className="flex items-center bg-[#111] border border-[#3a322b] rounded-sm p-0.5">
<div className="flex items-center bg-[#111] border border-[#DCE6F2] rounded-sm p-0.5">
<button
onClick={() => handleFeedModeChange("pagination")}
aria-pressed={feedMode === "pagination"}
title="Pagination mode"
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${
feedMode === "pagination" ?"bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
feedMode === "pagination" ?"bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="1" y="1" width="10" height="2.5" rx="0.5" fill="currentColor" opacity="0.9"/>
@@ -347,8 +347,8 @@ export default function ArticleFeed() {
onClick={() => handleFeedModeChange("infinite")}
aria-pressed={feedMode === "infinite"}
title="Infinite scroll mode"
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${
feedMode === "infinite" ?"bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
feedMode === "infinite" ?"bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="1" y="1" width="10" height="2" rx="0.5" fill="currentColor" opacity="0.9"/>
@@ -372,7 +372,7 @@ export default function ArticleFeed() {
value={dateFrom}
onChange={(e) => { setDateFrom(e.target.value); setPage(1); }}
max={dateTo || undefined}
className="bg-[#231d18] border border-[#3a322b] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#F0A623] transition-colors [color-scheme:dark]"
className="bg-[#FFFFFF] border border-[#DCE6F2] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#1D4ED8] transition-colors [color-scheme:dark]"
title="From date"
aria-label="Filter articles from date"
/>
@@ -382,7 +382,7 @@ export default function ArticleFeed() {
value={dateTo}
onChange={(e) => { setDateTo(e.target.value); setPage(1); }}
min={dateFrom || undefined}
className="bg-[#231d18] border border-[#3a322b] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#F0A623] transition-colors [color-scheme:dark]"
className="bg-[#FFFFFF] border border-[#DCE6F2] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#1D4ED8] transition-colors [color-scheme:dark]"
title="To date"
aria-label="Filter articles to date"
/>
@@ -401,17 +401,17 @@ export default function ArticleFeed() {
onClick={() => setShowAdvanced((v) => !v)}
aria-expanded={showAdvanced}
aria-controls={`${feedId}-advanced`}
className={`flex items-center gap-1.5 px-3 py-2 rounded-sm border font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] 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-[#1D4ED8] whitespace-nowrap ${
showAdvanced || hasAdvancedActive
? "bg-[#F0A623]/15 text-[#F0A623] border-[#F0A623]/40"
: "bg-[#111] text-[#666] border-[#3a322b] hover:text-[#aaa] hover:border-[#3a3a3a]"
? "bg-[#1D4ED8]/15 text-[#1D4ED8] border-[#1D4ED8]/40"
: "bg-[#111] text-[#666] border-[#DCE6F2] hover:text-[#aaa] hover:border-[#3a3a3a]"
}`}>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M1 3h10M3 6h6M5 9h2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
</svg>
More
{hasAdvancedActive && (
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] inline-block ml-0.5" aria-label="Active filters" />
<span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] inline-block ml-0.5" aria-label="Active filters" />
)}
</button>
</div>
@@ -428,10 +428,10 @@ export default function ArticleFeed() {
type="button"
onClick={() => { setActiveCategory(cat); setPage(1); }}
aria-pressed={isActive}
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${
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-[#1D4ED8] ${
isActive
? "bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/50"
: "bg-[#111] text-[#888] border border-[#3a322b] hover:text-[#ccc] hover:border-[#3a3a3a]"
? "bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/50"
: "bg-[#111] text-[#888] border border-[#DCE6F2] hover:text-[#ccc] hover:border-[#3a3a3a]"
}`}>
{cat}
</button>
@@ -444,7 +444,7 @@ export default function ArticleFeed() {
{showAdvanced && (
<div
id={`${feedId}-advanced`}
className="bg-[#1c1815] border border-[#3a322b] rounded-sm p-3 space-y-3"
className="bg-[#F8FAFC] border border-[#DCE6F2] rounded-sm p-3 space-y-3"
role="group"
aria-label="Advanced filters">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
@@ -458,7 +458,7 @@ export default function ArticleFeed() {
id={`${feedId}-author`}
value={authorFilter}
onChange={(e) => { setAuthorFilter(e.target.value); setPage(1); }}
className="w-full bg-[#111] border border-[#3a322b] text-[#ccc] font-body text-sm rounded-sm px-3 py-1.5 pr-8 appearance-none focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623]/30 transition-colors [color-scheme:dark]"
className="w-full bg-[#111] border border-[#DCE6F2] text-[#ccc] font-body text-sm rounded-sm px-3 py-1.5 pr-8 appearance-none focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8]/30 transition-colors [color-scheme:dark]"
aria-label="Filter by author">
<option value="All">All Authors</option>
{uniqueAuthors.map((author) => (
@@ -475,7 +475,7 @@ export default function ArticleFeed() {
{/* Source filter inside advanced panel */}
<div className="space-y-1">
<span className="font-body text-[10px] text-[#555] uppercase tracking-wide block">Source</span>
<div className="flex items-center gap-1 bg-[#111] border border-[#3a322b] rounded-sm p-0.5 w-fit" role="group" aria-label="Filter by source">
<div className="flex items-center gap-1 bg-[#111] border border-[#DCE6F2] rounded-sm p-0.5 w-fit" role="group" aria-label="Filter by source">
{SOURCE_FILTERS.map((sf) => {
const isActive = sourceFilter === sf;
return (
@@ -483,10 +483,10 @@ export default function ArticleFeed() {
key={sf}
onClick={() => { setSourceFilter(sf); setPage(1); }}
aria-pressed={isActive}
className={`px-3 py-1 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${
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-[#1D4ED8] ${
isActive
? sf === "Imported" ?"bg-amber-500/20 text-amber-400 border border-amber-500/40"
: sf === "Live" ?"bg-emerald-500/20 text-emerald-400 border border-emerald-500/40" :"bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
: sf === "Live" ?"bg-emerald-500/20 text-emerald-400 border border-emerald-500/40" :"bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}>
{sf}
{sf === "Imported" && importedCount > 0 && (
@@ -507,7 +507,7 @@ export default function ArticleFeed() {
<button
type="button"
onClick={() => { setAuthorFilter("All"); setDateFrom(""); setDateTo(""); setPage(1); }}
className="font-body text-[11px] text-[#F0A623] hover:underline focus:outline-none focus-visible:underline">
className="font-body text-[11px] text-[#1D4ED8] hover:underline focus:outline-none focus-visible:underline">
Clear advanced filters
</button>
</div>
@@ -537,7 +537,7 @@ export default function ArticleFeed() {
{sortedFiltered.length === 0 && (
<button
onClick={clearAllFilters}
className="ml-1 text-[#F0A623] hover:underline focus:outline-none focus-visible:underline">
className="ml-1 text-[#1D4ED8] hover:underline focus:outline-none focus-visible:underline">
Clear filters
</button>
)}
@@ -557,7 +557,7 @@ export default function ArticleFeed() {
</p>
<button
onClick={clearAllFilters}
className="mt-4 font-body text-sm text-[#F0A623] hover:underline focus:outline-none focus-visible:underline">
className="mt-4 font-body text-sm text-[#1D4ED8] hover:underline focus:outline-none focus-visible:underline">
Clear filters
</button>
</div>
@@ -585,7 +585,7 @@ export default function ArticleFeed() {
const promotedClass = promoted === 'premium'
? 'border-l-4 border-amber-400 bg-gradient-to-r from-amber-500/10 to-transparent pl-3 -ml-2 mr-0'
: promoted === 'client'
? 'border-l-4 border-[#F0A623] bg-gradient-to-r from-[#F0A623]/8 to-transparent pl-3 -ml-2 mr-0'
? 'border-l-4 border-[#1D4ED8] bg-gradient-to-r from-[#1D4ED8]/8 to-transparent pl-3 -ml-2 mr-0'
: '';
return (
<Fragment key={`feed-${i}`}>
@@ -593,7 +593,7 @@ export default function ArticleFeed() {
<Link
href={articleHref}
{...linkProps}
className="flex-shrink-0 img-zoom overflow-hidden w-[100px] h-[70px] sm:w-[140px] sm:h-[95px] md:w-[190px] md:h-[128px] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"
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-[#1D4ED8]"
tabIndex={-1}
aria-hidden="true"
>
@@ -614,21 +614,21 @@ export default function ArticleFeed() {
</span>
)}
{promoted === 'client' && (
<span className="font-body text-[9px] font-bold text-[#7aa7d4] bg-[#F0A623]/15 border border-[#F0A623]/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-[#1D4ED8]/15 border border-[#1D4ED8]/40 px-1.5 py-0.5 rounded uppercase tracking-widest" title={article?.promoted_advertiser ? `Advertiser coverage: ${article.promoted_advertiser}` : 'Advertiser coverage'}>
Advertiser
</span>
)}
{article?.category && article.category.toLowerCase() !== "legacy" && (
<span className="font-body text-[10px] font-bold text-[#F0A623] uppercase tracking-wide">
<span className="font-body text-[10px] font-bold text-[#1D4ED8] uppercase tracking-wide">
{article.category}
</span>
)}
</div>
<h3 className="font-heading text-[#e0e0e0] font-bold text-[0.875rem] md:text-[1rem] leading-snug mb-1 md:mb-1.5 group-hover:text-[#F0A623] 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-[#1D4ED8] transition-colors duration-200 line-clamp-2">
<Link
href={articleHref}
{...linkProps}
className="focus:outline-none focus-visible:text-[#F0A623] focus-visible:underline">
className="focus:outline-none focus-visible:text-[#1D4ED8] focus-visible:underline">
{article?.title}
</Link>
</h3>
@@ -667,42 +667,42 @@ export default function ArticleFeed() {
const shareUrl = `https://avbeat.com${articleHref.startsWith('/') ? articleHref : `/articles/${article?.slug}`}`;
const shareTitle = article?.title || '';
return (
<div className="flex items-center gap-1.5 mt-2 pt-2 border-t border-[#231d18]">
<div className="flex items-center gap-1.5 mt-2 pt-2 border-t border-[#FFFFFF]">
<span className="text-[#444] text-[9px] font-body uppercase tracking-wider mr-1">Share:</span>
<a href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on LinkedIn" title="LinkedIn"
onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#0a66c2] hover:bg-[#231d18] transition-colors">
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#0a66c2] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 0h-14C2.24 0 0 2.24 0 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5V5c0-2.76-2.24-5-5-5zM8 19H5V8h3v11zM6.5 6.73a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5zM20 19h-3v-5.6c0-3.37-4-3.12-4 0V19h-3V8h3v1.77c1.4-2.59 7-2.78 7 2.48V19z"/></svg>
</a>
<a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}&via=BroadcastBeat`}
target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)"
onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-white hover:bg-[#231d18] transition-colors">
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-white hover:bg-[#FFFFFF] transition-colors">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a>
<a href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on Facebook" title="Facebook"
onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#1877f2] hover:bg-[#231d18] transition-colors">
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#1877f2] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12S0 5.446 0 12.073C0 18.062 4.388 23.027 10.125 23.927v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</a>
<a href={`https://www.reddit.com/submit?url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(shareTitle)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on Reddit" title="Reddit"
onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#ff4500] hover:bg-[#231d18] transition-colors">
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#ff4500] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249z"/></svg>
</a>
<a href={`https://api.whatsapp.com/send?text=${encodeURIComponent(shareTitle + ' ' + shareUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on WhatsApp" title="WhatsApp"
onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#25d366] hover:bg-[#231d18] transition-colors">
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#25d366] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M.057 24l1.687-6.163a11.867 11.867 0 0 1-1.587-5.945C.16 5.335 5.495 0 12.05 0a11.817 11.817 0 0 1 8.413 3.488 11.824 11.824 0 0 1 3.48 8.414c-.003 6.557-5.338 11.892-11.893 11.892a11.9 11.9 0 0 1-5.688-1.448L.057 24zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981z"/></svg>
</a>
<a href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`}
aria-label="Share via email" title="Email"
onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#F0A623] hover:bg-[#231d18] transition-colors">
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#1D4ED8] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
@@ -735,9 +735,9 @@ export default function ArticleFeed() {
{isLoadingMore && (
<div className="flex items-center justify-center gap-2 py-6" aria-live="polite" aria-label="Loading more articles">
<div className="flex gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] animate-bounce" style={{ animationDelay: "0ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] animate-bounce" style={{ animationDelay: "300ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] animate-bounce" style={{ animationDelay: "0ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] animate-bounce" style={{ animationDelay: "300ms" }} />
</div>
<span className="font-body text-xs text-[#555]">Loading more articles</span>
</div>
@@ -762,7 +762,7 @@ export default function ArticleFeed() {
</p>
<div className="flex items-center gap-1" role="navigation" aria-label="Pagination">
<button
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]"
disabled={page === 1}
onClick={() => { setPage(Math.max(1, page - 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label="Previous page">
@@ -786,7 +786,7 @@ export default function ArticleFeed() {
) : (
<button
key={p}
className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${page === p ? "active" : ""}`}
className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${page === p ? "active" : ""}`}
onClick={() => { setPage(p as number); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label={`Page ${p}`}
aria-current={page === p ? "page" : undefined}>
@@ -796,7 +796,7 @@ export default function ArticleFeed() {
);
})()}
<button
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]"
disabled={page === totalPages}
onClick={() => { setPage(Math.min(totalPages, page + 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label="Next page">