import React, { Suspense } from 'react'; import type { Metadata, Viewport } from 'next'; import { Lora, Inter } from 'next/font/google'; import '../styles/tailwind.css'; // Self-hosted at build time by next/font — no runtime CDN fetch, no FOIT, // no GDPR concern. The two variables are consumed by --font-heading and // --font-body in src/styles/tailwind.css with Georgia/Arial fallbacks. const lora = Lora({ subsets: ['latin'], weight: ['400', '500', '600', '700'], style: ['normal', 'italic'], variable: '--font-serif', display: 'swap', }); const inter = Inter({ subsets: ['latin'], weight: ['300', '400', '500', '600', '700', '800'], variable: '--font-sans', display: 'swap', }); // SystemStatusBar removed — the "articles indexed / events tracked / [v2.0.0]" // strip was eating vertical space without serving readers. import AskBBAI from '@/components/AskBBAI'; import GoogleAnalytics from '@/components/GoogleAnalytics'; import CookieConsent from '@/components/CookieConsent'; import NewsletterModal from '@/components/NewsletterModal'; export const viewport: Viewport = { width: 'device-width', initialScale: 1 }; export const metadata: Metadata = { metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'), title: 'AV Beat — Pro AV, Live Production & Display Tech News', description: 'The digital platform for pro AV, live production, and display tech professionals. Breaking news, deep-dive features, and industry spotlights from InfoComm to ISE and beyond.', icons: { icon: [ { url: '/favicon.ico', type: 'image/x-icon', sizes: 'any' }, { url: '/static/favicons/favicon.svg', type: 'image/svg+xml' }, { url: '/static/favicons/favicon-32.png', type: 'image/png', sizes: '32x32' }, { url: '/static/favicons/favicon-16.png', type: 'image/png', sizes: '16x16' }, ], apple: [ { url: '/static/favicons/favicon-192.png', sizes: '180x180' }, { url: '/static/favicons/favicon-256.png', sizes: '256x256' }, ], }, alternates: { canonical: '/', // hreflang entries removed — we don't currently serve translated content, // and pointing Google at /en, /es, /pt, etc. (which 404) was poisoning // the SEO signal. Re-add when the i18n routes actually exist. }, openGraph: { type: 'website', locale: 'en_US', url: '/', siteName: 'AV Beat', title: 'AV Beat — Pro AV, Live Production & Display Tech', description: 'Breaking news and insights for pro AV, live production, and display tech professionals.', images: [ { url: '/assets/images/og-image.png', width: 1200, height: 630, alt: 'AV Beat — Pro AV, Live Production & Display Tech News', type: 'image/png' }] }, twitter: { card: 'summary_large_image', site: '@AV Beat', creator: '@AV Beat', title: 'AV Beat — Pro AV, Live Production & Display Tech', description: 'Breaking news and insights for pro AV, live production, and display tech professionals.', images: ['/assets/images/og-image.png'] }, robots: { // Open to indexing as of 2026-05-20. Phase B (Ollama rewriter) is // live; sitemap + robots.ts exist; the PR scrubber + AI featured // images are running in the background to clean up the catalog. index: true, follow: true, googleBot: { index: true, follow: true, 'max-video-preview': -1, 'max-image-preview': 'large', 'max-snippet': -1 } } }; export default function RootLayout({ children }: Readonly<{children: React.ReactNode;}>) { return (
{/* hreflang entries removed pending real i18n routes (Phase D). */} {/* Tell browsers + Google not to auto-offer translation. The site IS English (lang="en"); the language pulldown opt-in uses Google Translate's hosted proxy (*.translate.goog) so users who want another language get one without polluting the canonical URL. Without these, Chrome's content-language detector sometimes fires on imported posts that contain Portuguese/Spanish company or person names and offers a translate popup unprompted. */} {/* robots tag now driven entirely by `export const metadata.robots` above — having both produced conflicting noindex/index tags. */} {/* Skip to main content — WCAG 2.4.1 */} Skip to main content