Files
avbeat-com/src/app/home-page/components/FeaturedBento.tsx
Claude d43f78b161 av: clone broadcastbeat sources + AV Beat rebrand (Phase 1)
- Replaces the prior Rocket scaffold (saved on pre-bb-clone-rollback branch)
- Domain: broadcastbeat.com -> avbeat.com
- Brand: Broadcast Beat -> AV Beat
- Slug: broadcastbeat -> avbeat (package, identifiers)
- Schema fallback: bb -> av (env var name unchanged)
- Placeholder AV BEAT logo (gold->red gradient) at /assets/logos/av.svg
- All BB/RMP logo references repointed

Outstanding before public DNS swap:
  - Supabase av schema bootstrap (mirror of bb tables)
  - WordPress archive import (avbeat-com -> av.articles)
  - Coolify env vars
  - dev-avbeat.onsethost.com staging deploy + visual review
2026-06-02 15:32:56 +00:00

281 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useState, useEffect, useCallback } from "react";
import AppImage from "@/components/ui/AppImage";
import { ArrowRightIcon } from "@/components/ui/Icons";
import Link from "next/link";
// Fixed hero — only changes when manually updated
const hero = {
title: "Sony BRC-AM7 + HXC-FZ90: Better Together in the Field",
category: "BROADCAST",
excerpt: "Sony's latest camera pairing delivers unprecedented flexibility for field production teams, combining 4K HDR capture with compact form factors designed for the realities of live broadcast.",
image: "https://www.avbeat.com/wp-content/uploads/BRC-AM7_012-Mid.png",
alt: "Sony BRC-AM7 broadcast camera in field setting",
slug: "sony-brc-am7-hxc-fz90",
};
// Last 15 stories from the homepage feed — these rotate in the secondary cards
const rotatingStories = [
{
title: "Sony\'s BVM-HX1710: The Compact Powerhouse Changing How Broadcast & Post See Their Work",
category: "POST PRODUCTION",
image: "https://www.avbeat.com/wp-content/uploads/bvmhx1710_3q_250131_02-copy-scaled.png",
alt: "Sony BVM-HX1710 broadcast monitor three-quarter view",
slug: "sony-bvm-hx1710",
},
{
title: "The Top 5 Challenges in MCRs & NOCs—And How to Stay Ahead",
category: "BROADCAST",
image: "https://www.avbeat.com/wp-content/uploads/data-insight-ip-monitoring-1024x597.pngw3_.webp",
alt: "IP monitoring dashboard showing data insights for MCR operations",
slug: "top-5-challenges-mcrs-nocs",
},
{
title: "TESSERA SQ200: THE NEW PACE-SETTER IN LED PROCESSING",
category: "FEATURED",
image: "https://www.avbeat.com/wp-content/uploads/Hero-Image_SQ200-Gen-3_Sep2025_CMYK.jpg",
alt: "Tessera SQ200 Gen 3 LED processor hero image",
slug: "tessera-sq200-led-processing",
},
{
title: "Backlight adds real-time collaboration to Iconik, AI-powered livestreams to Wildmoka at IBC2025",
category: "BROADCAST",
image: "https://www.avbeat.com/wp-content/uploads/Iconik-scaled.jpeg",
alt: "Iconik media asset management platform interface",
slug: "backlight-iconik-wildmoka-ibc2025",
},
{
title: "Telestream Expands Its Cloud Services with the Introduction of UP",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "Cloud services infrastructure for broadcast production environments",
slug: "telestream-expands-cloud-services-up",
},
{
title: "Operative Launches AOS Configuration for Digital-First Monetization",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "Digital monetization analytics dashboard for broadcast advertising",
slug: "operative-launches-aos-configuration",
},
{
title: "Calrec Redefines Broadcast Workflows at NAB 2026 with its Most Powerful Audio Lineup Yet",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "Calrec audio mixing console for broadcast production at NAB 2026",
slug: "calrec-redefines-broadcast-workflows-nab-2026",
},
{
title: "Ease Live Powers Interactive Premier Padel Experiences on Red Bull TV as Viewership Surges 30%",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "Interactive sports broadcast experience on streaming platform",
slug: "ease-live-premier-padel-red-bull-tv",
},
{
title: "From Metadata to Meaning: Why Semantic Intelligence Is Media\'s Next Competitive Advantage",
category: "FEATURED",
image: "/assets/images/article-placeholder.svg",
alt: "Media metadata intelligence and content analytics visualization",
slug: "from-metadata-to-meaning-semantic-intelligence",
},
{
title: "Mediagenix Title Management Accelerates Content Monetization Through Advanced Semantic Intelligence",
category: "POST PRODUCTION",
image: "/assets/images/article-placeholder.svg",
alt: "Content management system for media title management and monetization",
slug: "mediagenix-title-management",
},
{
title: "Emergent Launches Fusion: The \'Interactive Anything\' Platform Transforming Data-Driven Storytelling",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "Interactive data storytelling platform for broadcast and enterprise use",
slug: "emergent-launches-fusion-platform",
},
{
title: "Matrox Video Enables the Next Era of Software-Defined Media Production at NAB 2026",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "Matrox video production hardware and software at NAB 2026 exhibition",
slug: "matrox-video-software-defined-nab-2026",
},
{
title: "Video is King: 2026 Iconik Media Stats Report Finds Video Consumes 64% of Storage Needs",
category: "FEATURED",
image: "/assets/images/article-placeholder.svg",
alt: "Video storage statistics and media asset management data visualization",
slug: "video-is-king-2026-iconik-media-stats",
},
{
title: "QuickLink\'s Latest StudioEdge Models to Make North American Debut at NAB 2026",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "QuickLink StudioEdge broadcast production equipment at NAB exhibition",
slug: "quicklink-studioedge-nab-2026",
},
{
title: "Utah Scientific Expands Technology Partner Program With Integrations From Audinate, Bitfocus, and Skaarhoj",
category: "BROADCAST",
image: "/assets/images/article-placeholder.svg",
alt: "Broadcast routing and control system technology partner integration",
slug: "utah-scientific-partner-program",
},
];
// Number of secondary cards visible at once
const CARDS_PER_PAGE = 4;
// Auto-rotate interval in ms
const AUTO_ROTATE_MS = 5000;
export default function FeaturedBento() {
// offset = index of first visible secondary card
const [offset, setOffset] = useState(0);
const [paused, setPaused] = useState(false);
const maxOffset = rotatingStories?.length - CARDS_PER_PAGE;
const goNext = useCallback(() => {
setOffset((prev) => (prev >= maxOffset ? 0 : prev + 1));
}, [maxOffset]);
const goPrev = useCallback(() => {
setOffset((prev) => (prev <= 0 ? maxOffset : prev - 1));
}, [maxOffset]);
// Auto-rotate
useEffect(() => {
if (paused) return;
const timer = setInterval(goNext, AUTO_ROTATE_MS);
return () => clearInterval(timer);
}, [paused, goNext]);
const visibleCards = rotatingStories?.slice(offset, offset + CARDS_PER_PAGE);
return (
<section className="max-w-container mx-auto px-4 py-4 md:py-6">
{/* Section header */}
<div className="flex items-center gap-3 mb-4 md:mb-5">
<span className="section-label">Featured</span>
<div className="flex-1 h-px bg-[#2a2a2a]" />
{/* Navigation arrows */}
<div
className="flex items-center gap-1.5"
role="group"
aria-label="Featured stories pagination"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "ArrowLeft") { e.preventDefault(); goPrev(); setPaused(true); }
if (e.key === "ArrowRight") { e.preventDefault(); goNext(); setPaused(true); }
}}
>
<button
type="button"
onClick={() => { goPrev(); setPaused(true); }}
aria-label="Previous stories"
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#3b82f6] hover:border-[#3b82f6] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<polyline points="15 18 9 12 15 6" />
</svg>
</button>
<button
type="button"
onClick={() => { goNext(); setPaused(true); }}
aria-label="Next stories"
className="w-8 h-8 flex items-center justify-center bg-[#1a1a1a] border border-[#3a3a3a] hover:bg-[#3b82f6] hover:border-[#3b82f6] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<polyline points="9 18 15 12 9 6" />
</svg>
</button>
{/* Position indicator */}
<span className="font-body text-[11px] text-[#888] ml-1">
{offset + 1}{Math.min(offset + CARDS_PER_PAGE, rotatingStories?.length)}/{rotatingStories?.length}
</span>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-3 md:gap-4">
{/* Hero Article — FIXED, only changes when manually updated */}
<Link
href={`/articles/${hero?.slug}`}
className="lg:col-span-7 img-zoom rounded-sm overflow-hidden relative group cursor-pointer block"
style={{ minHeight: 260 }}>
<div className="relative w-full h-[260px] sm:h-[320px] md:h-[380px] lg:h-full lg:min-h-[400px]">
<AppImage
src={hero?.image}
alt={hero?.alt}
fill
className="object-cover w-full h-full"
priority={true}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 100vw, 58vw"
/>
<div className="hero-overlay absolute inset-0" />
<div className="absolute inset-0 flex flex-col justify-end p-3 sm:p-4 md:p-5">
<span className="inline-block bg-[#3b82f6] text-white font-body text-[10px] font-bold px-2 py-0.5 tracking-wider uppercase mb-2 w-fit">
{hero?.category}
</span>
<h2 className="font-heading text-white text-lg sm:text-xl md:text-2xl font-bold leading-tight mb-2 max-w-xl">
{hero?.title}
</h2>
<p className="text-white/80 font-body text-sm leading-relaxed max-w-lg hidden sm:block mb-3">
{hero?.excerpt}
</p>
<span className="inline-flex items-center gap-1.5 text-white font-body font-bold text-xs uppercase tracking-wider group-hover:text-[#93c5fd] transition-colors">
Read More
<ArrowRightIcon size={12} />
</span>
</div>
</div>
</Link>
{/* Secondary 2x2 Grid — rotating stories */}
<div
className="lg:col-span-5 grid grid-cols-2 gap-2 md:gap-3"
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}>
{visibleCards?.map((article, i) => (
<Link
key={`${article?.slug}-${offset}-${i}`}
href={`/articles/${article?.slug}`}
className="bg-[#1a1a1a] border border-[#2a2a2a] overflow-hidden group cursor-pointer card-accent-hover flex flex-col hover:border-[#3b82f6] transition-colors">
<div className="relative overflow-hidden img-zoom h-[90px] sm:h-[110px] md:h-[120px]">
<AppImage
src={article?.image}
alt={article?.alt}
fill
className="object-cover w-full h-full"
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 50vw, 22vw"
/>
<div className="absolute top-0 left-0">
<span className="bg-[#3b82f6] text-white font-body text-[9px] font-bold px-1.5 py-0.5 tracking-wider uppercase">
{article?.category}
</span>
</div>
</div>
<div className="p-2 md:p-2.5 flex-1 flex flex-col justify-between">
<h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-3 group-hover:text-[#3b82f6] transition-colors duration-200 mb-1.5 md:mb-2">
{article?.title}
</h3>
<span className="read-more inline-block text-[11px]">Read More »</span>
</div>
</Link>
))}
</div>
</div>
{/* Dot indicators */}
<div className="flex justify-end items-center gap-1 mt-3">
{Array.from({ length: maxOffset + 1 })?.map((_, i) => (
<button
key={i}
onClick={() => { setOffset(i); setPaused(true); }}
aria-label={`Go to position ${i + 1}`}
className={`rounded-full transition-all focus:outline-none ${
i === offset
? "w-4 h-1.5 bg-[#3b82f6]"
: "w-1.5 h-1.5 bg-[#333] hover:bg-[#555]"
}`}
/>
))}
</div>
</section>
);
}