initial commit: rocket.new export of broadcastbeat
This commit is contained in:
270
src/app/home-page/components/FeaturedBento.tsx
Normal file
270
src/app/home-page/components/FeaturedBento.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
"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.broadcastbeat.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.broadcastbeat.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.broadcastbeat.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.broadcastbeat.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.broadcastbeat.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: "https://img.rocket.new/generatedImages/rocket_gen_img_11ff3d2ff-1773287949660.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_1805d1575-1773287951103.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_10468e182-1773287953747.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_1747232a3-1773287951222.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_1c523ae5f-1773287954372.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_19f6e3fe6-1769744394519.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_188669b2b-1773287951738.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_17266f6cf-1773287953058.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_140568f68-1773287953452.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_19c4ba922-1773287953682.png",
|
||||
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: "https://img.rocket.new/generatedImages/rocket_gen_img_1ea12f02f-1768375990494.png",
|
||||
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">
|
||||
<button
|
||||
onClick={() => { goPrev(); setPaused(true); }}
|
||||
aria-label="Previous stories"
|
||||
className="w-7 h-7 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] hover:text-[#3b82f6] text-[#666] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
<svg width="12" height="12" 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
|
||||
onClick={() => { goNext(); setPaused(true); }}
|
||||
aria-label="Next stories"
|
||||
className="w-7 h-7 flex items-center justify-center bg-[#1a1a1a] border border-[#2a2a2a] hover:border-[#3b82f6] hover:text-[#3b82f6] text-[#666] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#3b82f6]">
|
||||
<svg width="12" height="12" 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-[#555] 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user