Files
avbeat-com/src/app/home-page/page.tsx
Ryan Salazar a9823b1d8a ticker: replace LiveWire with dual-row neon ticker (news + events)
Row 1 (Live Wire, scrolls →): most recent imported + AI-rewritten
articles, neon-green label, monospaced [HH:MM] [SOURCE_CODE] prefix.
Row 2 (On Calendar, scrolls ←, slower): upcoming events from
bb.events (confirmed/tentative, start_date >= today), neon-cyan
label, [Mon DD] [SHORT_NAME] prefix.

Defensive: each query is in its own try/catch so a partial failure
renders an empty row rather than crashing the page — that was the
silent build crash that took down the prior DoubleTicker incarnation.
If BOTH queries return zero rows we render nothing instead of two
blank bars.

CSS tokens added to redesign-tokens.css:
- bb-marquee--reverse + bb-marquee--slow modifiers
- .bb-neon-bar / .bb-neon-label / .bb-neon-time / .bb-neon-code
  (scoped class names so this is the ONLY place on the site that
  uses the neon palette — rest of the site keeps dark-navy + blue)

User cancelled the wholesale neon redesign; only the dual-ticker
keeps the neon green/cyan styling.
2026-05-20 07:02:15 +00:00

188 lines
6.6 KiB
TypeScript

import React, { Suspense } from "react";
import type { Metadata } from "next";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import FeaturedBentoFromDb from '@/components/FeaturedBentoFromDb';
import FeaturedCarouselServer from '@/components/FeaturedCarouselServer';
import DoubleTicker from '@/components/DoubleTicker';
import NewsTicker from "./components/NewsTicker";
import FeaturedBento from "./components/FeaturedBento";
import SpotlightCarousel from "./components/SpotlightCarousel";
import ArticleFeed from "./components/ArticleFeed";
import ScrollRevealSection from "@/components/ScrollRevealSection";
import NewsletterSignup from "./components/NewsletterSignup";
export const metadata: Metadata = {
title: 'Broadcast Beat — Broadcast Engineering News & Insights',
description: 'The digital platform for broadcast engineering professionals. Breaking news, deep-dive features, and industry spotlights from NAB to IBC and beyond.',
alternates: {
canonical: '/home-page',
},
openGraph: {
title: 'Broadcast Beat — Broadcast News',
description: 'Breaking news and insights for broadcast engineering professionals.',
url: '/home-page',
type: 'website',
images: [
{
url: '/assets/images/og-image.png',
width: 1200,
height: 630,
alt: 'Broadcast Beat — Broadcast Engineering News & Insights',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Broadcast Beat — Broadcast News',
description: 'Breaking news and insights for broadcast engineering professionals.',
images: ['/assets/images/og-image.png'],
},
};
/* ── Skeleton placeholders ─────────────────────────────── */
function TickerSkeleton() {
return (
<div className="w-full h-8 skeleton" style={{ borderRadius: 0 }} />
);
}
function LeaderboardSkeleton() {
return (
<div className="max-w-container mx-auto px-4 py-3">
<div className="skeleton h-[90px] w-full rounded-sm" />
</div>
);
}
function BentoSkeleton() {
return (
<section className="max-w-container mx-auto px-4 py-4 md:py-6">
<div className="flex items-center gap-3 mb-4">
<div className="skeleton h-4 w-20 rounded-sm" />
<div className="flex-1 h-px bg-[#2a2a2a]" />
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-3 md:gap-4">
<div className="lg:col-span-7 skeleton rounded-sm h-[260px] sm:h-[320px] md:h-[380px]" />
<div className="lg:col-span-5 grid grid-cols-2 gap-3 md:gap-4">
{[...Array(4)]?.map((_, i) => (
<div key={i} className="skeleton-card">
<div className="skeleton h-[110px] sm:h-[130px]" />
<div className="p-2 space-y-1.5">
<div className="skeleton h-3 w-16 rounded-sm" />
<div className="skeleton h-4 w-full rounded-sm" />
<div className="skeleton h-4 w-3/4 rounded-sm" />
</div>
</div>
))}
</div>
</div>
</section>
);
}
function CarouselSkeleton() {
return (
<div className="py-6 px-4">
<div className="max-w-container mx-auto">
<div className="flex items-center gap-3 mb-4">
<div className="skeleton h-4 w-24 rounded-sm" />
<div className="flex-1 h-px bg-[#2a2a2a]" />
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{[...Array(4)]?.map((_, i) => (
<div key={i} className="skeleton-card">
<div className="skeleton h-[140px]" />
<div className="p-3 space-y-2">
<div className="skeleton h-3 w-16 rounded-sm" />
<div className="skeleton h-4 w-full rounded-sm" />
<div className="skeleton h-4 w-2/3 rounded-sm" />
</div>
</div>
))}
</div>
</div>
</div>
);
}
function FeedSkeleton() {
return (
<section className="max-w-container mx-auto px-4 py-4 md:py-6">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8">
<div className="lg:col-span-8 space-y-1">
<div className="flex items-center gap-3 mb-4">
<div className="skeleton h-4 w-20 rounded-sm" />
<div className="flex-1 h-px bg-[#2a2a2a]" />
</div>
{[...Array(5)]?.map((_, i) => (
<div key={i} className="flex gap-4 py-4 border-b border-[#222]">
<div className="skeleton flex-shrink-0 w-[140px] h-[95px] rounded-sm" />
<div className="flex-1 space-y-2">
<div className="skeleton h-3 w-24 rounded-sm" />
<div className="skeleton h-5 w-full rounded-sm" />
<div className="skeleton h-5 w-4/5 rounded-sm" />
<div className="skeleton h-3 w-32 rounded-sm" />
</div>
</div>
))}
</div>
<div className="lg:col-span-4 space-y-4">
<div className="skeleton h-[250px] rounded-sm" />
<div className="skeleton h-[200px] rounded-sm" />
</div>
</div>
</section>
);
}
export default function HomePage() {
return (
<div className="min-h-screen bg-background">
{/* Sticky top bar + nav */}
<Header />
{/* H1 Hero Heading */}
<div className="sr-only">
<h1>Broadcast Beat Broadcast Engineering News & Insights</h1>
</div>
{/* Scrolling news ticker */}
<div className="section-enter section-enter-1">
<Suspense fallback={<TickerSkeleton />}>
<DoubleTicker />
</Suspense>
</div>
{/* Featured: cinematic hero + 4-up rail below (FeaturedBentoFromDb) */}
<ScrollRevealSection className="section-enter section-enter-3">
<Suspense fallback={<BentoSkeleton />}>
<FeaturedBentoFromDb />
</Suspense>
</ScrollRevealSection>
{/* Spotlight carousel — temporarily hidden (old stories)
<ScrollRevealSection className="section-enter section-enter-4" delay={1}>
<Suspense fallback={<CarouselSkeleton />}>
<SpotlightCarousel />
</Suspense>
</ScrollRevealSection>
*/}
{/* Article feed + sidebar */}
<ScrollRevealSection className="section-enter section-enter-5" delay={2}>
<Suspense fallback={<FeedSkeleton />}>
<ArticleFeed />
</Suspense>
</ScrollRevealSection>
{/* Newsletter signup */}
<ScrollRevealSection className="section-enter section-enter-5">
<NewsletterSignup />
</ScrollRevealSection>
{/* Footer */}
<Footer />
</div>
);
}