home: replace single LiveWire ticker with stacked dual ticker
Two horizontal lanes stacked on top of each other:
Lane 1 LIVE WIRE red gradient label, pulsing white dot,
latest 14 published articles, 65s loop
Lane 2 RECENT FORUM POSTS green gradient label, pulsing white dot,
latest 14 forum threads, 82s loop
The two lanes use intentionally different animation durations (65s vs
82s) so they never sync up — feels alive like a real wire room. Both
loops use CSS translateX with a duplicated content track for seamless
infinite scroll. Edges fade via mask-image so items dissolve in/out
rather than appearing on a hard line.
Both lanes pause on hover (the whole row) so users can stop motion
to read or click. Each item is a real <Link>; clicking opens the
article or thread page.
Server component — data is fetched once at request time from
bb.wp_imported_posts and bb.forum_threads, cached at the page's
revalidate boundary. No client JS needed.
Old LiveWireTicker.tsx is removed (orphaned, not referenced elsewhere).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
93
src/components/DoubleTicker.tsx
Normal file
93
src/components/DoubleTicker.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import Link from "next/link";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
interface TickerItem {
|
||||
href: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default async function DoubleTicker() {
|
||||
const sb = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb") as "public" },
|
||||
auth: { persistSession: false },
|
||||
}
|
||||
);
|
||||
|
||||
const [wireRes, forumRes] = await Promise.all([
|
||||
sb
|
||||
.from("wp_imported_posts")
|
||||
.select("wp_slug, title")
|
||||
.eq("status", "published")
|
||||
.order("wp_published_at", { ascending: false, nullsFirst: false })
|
||||
.limit(14),
|
||||
sb
|
||||
.from("forum_threads")
|
||||
.select("id, title")
|
||||
.order("last_reply_at", { ascending: false, nullsFirst: false })
|
||||
.limit(14),
|
||||
]);
|
||||
|
||||
const wire: TickerItem[] = (wireRes.data || []).map((r: any) => ({
|
||||
href: `/news/${r.wp_slug}`,
|
||||
title: r.title,
|
||||
}));
|
||||
const forum: TickerItem[] = (forumRes.data || []).map((r: any) => ({
|
||||
href: `/forum/thread/${r.id}`,
|
||||
title: r.title,
|
||||
}));
|
||||
|
||||
// Fallback to keep layout sensible if either feed is empty
|
||||
const wireItems = wire.length > 0 ? wire : [{ href: "/news", title: "Latest broadcast industry news loading…" }];
|
||||
const forumItems = forum.length > 0 ? forum : [{ href: "/forum", title: "Join the conversation in the forum" }];
|
||||
|
||||
return (
|
||||
<div className="bb-ticker-stack" aria-label="Site activity tickers">
|
||||
{/* LANE 1 — LIVE WIRE (red) */}
|
||||
<div className="bb-ticker bb-ticker--wire" role="region" aria-label="Live wire — latest articles">
|
||||
<span className="bb-ticker-label bb-ticker-label--red">
|
||||
<span className="bb-ticker-pulse" aria-hidden="true" />
|
||||
LIVE WIRE
|
||||
</span>
|
||||
<div className="bb-ticker-mask">
|
||||
<div className="bb-ticker-track bb-ticker-track--wire">
|
||||
{[...wireItems, ...wireItems].map((item, i) => (
|
||||
<Link
|
||||
key={`wire-${i}`}
|
||||
href={item.href}
|
||||
className="bb-ticker-item bb-ticker-item--wire">
|
||||
<span className="bb-ticker-bullet" aria-hidden="true">›</span>
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* LANE 2 — RECENT FORUM POSTS (teal-green) */}
|
||||
<div className="bb-ticker bb-ticker--forum" role="region" aria-label="Recent forum posts">
|
||||
<span className="bb-ticker-label bb-ticker-label--green">
|
||||
<span className="bb-ticker-pulse bb-ticker-pulse--green" aria-hidden="true" />
|
||||
RECENT FORUM POSTS
|
||||
</span>
|
||||
<div className="bb-ticker-mask">
|
||||
<div className="bb-ticker-track bb-ticker-track--forum">
|
||||
{[...forumItems, ...forumItems].map((item, i) => (
|
||||
<Link
|
||||
key={`forum-${i}`}
|
||||
href={item.href}
|
||||
className="bb-ticker-item bb-ticker-item--forum">
|
||||
<span className="bb-ticker-bullet" aria-hidden="true">›</span>
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user