diff --git a/src/app/home-page/page.tsx b/src/app/home-page/page.tsx index b179687..5c0d830 100644 --- a/src/app/home-page/page.tsx +++ b/src/app/home-page/page.tsx @@ -4,7 +4,7 @@ import Header from "@/components/Header"; import Footer from "@/components/Footer"; import FeaturedBentoFromDb from '@/components/FeaturedBentoFromDb'; import FeaturedCarouselServer from '@/components/FeaturedCarouselServer'; -import LiveWireTicker from '@/components/LiveWireTicker'; +import DoubleTicker from '@/components/DoubleTicker'; import NewsTicker from "./components/NewsTicker"; import FeaturedBento from "./components/FeaturedBento"; import SpotlightCarousel from "./components/SpotlightCarousel"; @@ -147,10 +147,10 @@ export default function HomePage() {

Broadcast Beat — Broadcast Engineering News & Insights

- {/* Scrolling news ticker */} + {/* Scrolling news + forum tickers */}
}> - +
diff --git a/src/components/DoubleTicker.tsx b/src/components/DoubleTicker.tsx new file mode 100644 index 0000000..a76a06f --- /dev/null +++ b/src/components/DoubleTicker.tsx @@ -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 ( +
+ {/* LANE 1 — LIVE WIRE (red) */} +
+ + +
+
+ {[...wireItems, ...wireItems].map((item, i) => ( + + + {item.title} + + ))} +
+
+
+ + {/* LANE 2 — RECENT FORUM POSTS (teal-green) */} +
+ + +
+
+ {[...forumItems, ...forumItems].map((item, i) => ( + + + {item.title} + + ))} +
+
+
+
+ ); +} diff --git a/src/components/LiveWireTicker.tsx b/src/components/LiveWireTicker.tsx deleted file mode 100644 index 11d2235..0000000 --- a/src/components/LiveWireTicker.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import Link from "next/link"; -import { createClient } from "@supabase/supabase-js"; - -export const dynamic = "force-dynamic"; - -interface Item { - slug: string; - title: string; - published_at: string; - source_code: string; -} - -const SOURCE_CODES: { match: RegExp; code: string }[] = [ - { match: /blackmagic|davinci/i, code: "BMD" }, - { match: /magewell/i, code: "MGW" }, - { match: /aja/i, code: "AJA" }, - { match: /sony/i, code: "SONY" }, - { match: /matrox/i, code: "MTRX" }, - { match: /telestream/i, code: "TLST" }, - { match: /grass valley/i, code: "GV" }, - { match: /lawo/i, code: "LAWO" }, - { match: /calrec/i, code: "CLRC" }, - { match: /zixi/i, code: "ZIXI" }, - { match: /liveu/i, code: "LVU" }, - { match: /netflix/i, code: "NFLX" }, - { match: /youtube/i, code: "YT" }, - { match: /amazon|aws/i, code: "AWS" }, -]; - -function codeFor(title: string): string { - for (const { match, code } of SOURCE_CODES) if (match.test(title)) return code; - return "WIRE"; -} - -function hhmm(iso: string): string { - const d = new Date(iso); - const h = String(d.getUTCHours()).padStart(2, "0"); - const m = String(d.getUTCMinutes()).padStart(2, "0"); - return `${h}:${m}`; -} - -export default async function LiveWireTicker() { - 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 [wp, ai] = await Promise.all([ - sb - .from("wp_imported_posts") - .select("wp_slug, title, wp_published_at") - .eq("status", "published") - .order("wp_published_at", { ascending: false }) - .limit(12), - sb - .from("ai_rewritten_articles") - .select("slug, title, published_at") - .eq("status", "published") - .order("published_at", { ascending: false, nullsFirst: false }) - .limit(6), - ]); - - const items: Item[] = []; - for (const r of (wp.data || []) as any[]) { - items.push({ - slug: r.wp_slug, - title: r.title, - published_at: r.wp_published_at, - source_code: codeFor(r.title || ""), - }); - } - for (const r of (ai.data || []) as any[]) { - items.push({ - slug: r.slug, - title: r.title, - published_at: r.published_at, - source_code: codeFor(r.title || ""), - }); - } - items.sort( - (a, b) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime() - ); - - const display = items.slice(0, 14); - - return ( -
-
- - Live wire - -
-
- {[...display, ...display].map((it, i) => ( - - [{hhmm(it.published_at)}] - [{it.source_code}] - {it.title} - - ))} -
-
-
-
- ); -} diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css index 15f4bde..a07e74f 100644 --- a/src/styles/tailwind.css +++ b/src/styles/tailwind.css @@ -807,4 +807,197 @@ h1, h2, h3 { .ProseMirror strong { font-weight: 700; color: #fff; } .ProseMirror em { font-style: italic; } .ProseMirror u { text-decoration: underline; } -.ProseMirror s { text-decoration: line-through; } \ No newline at end of file +.ProseMirror s { text-decoration: line-through; } + +/* ===================== + DOUBLE TICKER (LIVE WIRE + RECENT FORUM POSTS) + ===================== */ +.bb-ticker-stack { + width: 100%; + border-bottom: 1px solid #1a1a1a; +} + +.bb-ticker { + display: flex; + align-items: stretch; + height: 34px; + width: 100%; + overflow: hidden; + position: relative; +} + +.bb-ticker--wire { + background: linear-gradient(90deg, #0a0a0a 0%, #161616 50%, #0a0a0a 100%); + border-bottom: 1px solid #1f1212; +} + +.bb-ticker--forum { + background: linear-gradient(90deg, #08120e 0%, #0f1d18 50%, #08120e 100%); +} + +.bb-ticker-label { + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 8px; + padding: 0 16px; + font-family: var(--font-body); + font-size: 11px; + font-weight: 800; + letter-spacing: 0.22em; + color: #ffffff; + white-space: nowrap; + position: relative; + z-index: 2; +} + +.bb-ticker-label--red { + background: linear-gradient(90deg, #d40000 0%, #a30000 100%); + box-shadow: 0 0 18px rgba(212, 0, 0, 0.45), inset 0 -1px 0 rgba(255, 255, 255, 0.05); + clip-path: polygon(0 0, 100% 0, calc(100% - 10px) 100%, 0 100%); + padding-right: 24px; +} + +.bb-ticker-label--green { + background: linear-gradient(90deg, #0c8a63 0%, #086149 100%); + box-shadow: 0 0 18px rgba(12, 138, 99, 0.40), inset 0 -1px 0 rgba(255, 255, 255, 0.05); + clip-path: polygon(0 0, 100% 0, calc(100% - 10px) 100%, 0 100%); + padding-right: 24px; +} + +.bb-ticker-pulse { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 999px; + background: #ffffff; + box-shadow: 0 0 8px rgba(255, 255, 255, 0.9); + animation: bb-ticker-pulse-anim 1.4s ease-in-out infinite; +} + +.bb-ticker-pulse--green { + animation-duration: 1.8s; +} + +@keyframes bb-ticker-pulse-anim { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(1.25); } +} + +.bb-ticker-mask { + flex: 1; + min-width: 0; + overflow: hidden; + position: relative; + -webkit-mask-image: linear-gradient(to right, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%); + mask-image: linear-gradient(to right, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%); +} + +.bb-ticker-track { + display: inline-flex; + align-items: center; + width: max-content; + white-space: nowrap; + animation-name: bb-ticker-scroll; + animation-timing-function: linear; + animation-iteration-count: infinite; + will-change: transform; +} + +/* Different speeds so the two lanes never sync up — feels alive */ +.bb-ticker-track--wire { animation-duration: 65s; } +.bb-ticker-track--forum { animation-duration: 82s; } + +.bb-ticker-track:hover, +.bb-ticker-track:focus-within { + animation-play-state: paused; +} + +@keyframes bb-ticker-scroll { + 0% { transform: translate3d(0, 0, 0); } + 100% { transform: translate3d(-50%, 0, 0); } +} + +.bb-ticker-item { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 0 22px; + height: 34px; + text-decoration: none; + font-family: var(--font-body); + font-size: 13px; + font-weight: 500; + letter-spacing: 0.005em; + color: #d8d8d8; + transition: color 0.18s ease; + border-right: 1px solid rgba(255, 255, 255, 0.05); +} + +.bb-ticker-item--wire:hover { color: #ff6b6b; } +.bb-ticker-item--forum:hover { color: #5eead4; } + +.bb-ticker-bullet { + flex-shrink: 0; + font-weight: 700; + color: rgba(255, 255, 255, 0.35); +} + +.bb-ticker-item--wire:hover .bb-ticker-bullet { color: #ff6b6b; } +.bb-ticker-item--forum:hover .bb-ticker-bullet { color: #5eead4; } + +/* ===================== + BROWSE NAV BAR (sits below the 728x90 header banner) + ===================== */ +.bb-browse-bar { + background: linear-gradient(180deg, #0b0b0b 0%, #0e0e0e 100%); + border-top: 1px solid #1a1a1a; + border-bottom: 1px solid #1a1a1a; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); +} + +.bb-browse-row { + display: flex; + align-items: stretch; + gap: 0; + height: 44px; +} + +.bb-browse-item { + position: relative; + display: inline-flex; + align-items: center; + padding: 0 16px; + font-family: var(--font-body); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #cccccc; + text-decoration: none; + border-right: 1px solid #1a1a1a; + transition: background 0.15s ease, color 0.15s ease; +} + +.bb-browse-item:first-of-type { + border-left: 1px solid #1a1a1a; +} + +.bb-browse-item:hover, +.bb-browse-item[aria-expanded="true"] { + background: #161616; + color: var(--color-accent); +} + +.bb-browse-item .bb-browse-caret { + display: inline-block; + margin-left: 6px; + font-size: 8px; + opacity: 0.7; + transform: translateY(-1px); + transition: transform 0.15s ease; +} + +.bb-browse-item[aria-expanded="true"] .bb-browse-caret { + transform: rotate(180deg) translateY(1px); +} \ No newline at end of file