import type { Metadata, Viewport } from "next"; import { Geist_Mono, Space_Grotesk } from "next/font/google"; import type { ReactNode } from "react"; import { safeJsonLd } from "@/lib/jsonld"; import { buildPersonJsonLd, buildWebSiteJsonLd } from "@/lib/seo"; import { caseStudies, serviceHubLinks, servicePages, siteUrl, } from "@/lib/site-content"; import type { WhatsAppContexts } from "@/lib/whatsapp"; import CinematicOverlays from "./components/cinematic/CinematicOverlays"; import { FRAGMENT_NAV_BOOT_SCRIPT } from "./components/cinematic/fragment-nav"; import FloatingWhatsApp from "./components/FloatingWhatsApp"; import Footer from "./components/Footer"; import GoogleAnalytics from "./components/GoogleAnalytics"; import { Header } from "./components/Header"; import "./globals.css"; // As molduras de navegador e de celular (o hero e os cases também as usam) vêm uma vez só, aqui, depois do global. // A lista da vitrine (reel-list.css) já vem no globals.css, por @import; só a vitrine fixa (pinned-reel.css) e a janela // ampliada (lightbox.css) viajam com os componentes deles. import "./components/showcase/showcase.css"; const titleSuffix = " | Evandro.dev.br"; /** Título da home: o `title.default` não passa pelo template, então leva o sufixo. Com ele, no máximo 60 caracteres. */ const defaultTitle = `Sites e sistemas em Joinville e SC${titleSuffix}`; const seoDescription = "Sites e sistemas sob medida para o jeito que a sua empresa trabalha. Base em Garuva, SC; atendimento em Joinville e remoto em todo o Brasil."; /** * Para o WhatsApp falar do serviço ou do case da página aberta: só os assuntos e nomes, sem o conteúdo pesado das * páginas. O serviço entra pelo `whatsappSubject` (uma frase em minúsculas, que cabe em "Quero conversar sobre * {assunto}."), e não pelo nome: o nome daria "sobre Sites profissionais.". */ const whatsAppContexts: WhatsAppContexts = { services: Object.fromEntries( servicePages.map((service) => [service.slug, service.whatsappSubject]), ), cases: Object.fromEntries( caseStudies.map((caseStudy) => [caseStudy.id, caseStudy.title]), ), }; const spaceGrotesk = Space_Grotesk({ subsets: ["latin"], weight: ["400", "500", "600", "700"], display: "swap", variable: "--font-space-grotesk", }); const geistMono = Geist_Mono({ subsets: ["latin"], weight: ["400", "500", "600"], display: "swap", // só rótulos pequenos e kickers: não disputa a rede com a fonte do título, o arquivo é baixado quando o texto usa preload: false, variable: "--font-geist-mono", }); export const metadata: Metadata = { title: { default: defaultTitle, template: `%s${titleSuffix}`, }, description: seoDescription, authors: [{ name: "Evandro Fonseca Junior" }], creator: "Evandro Fonseca Junior", publisher: "Evandro Fonseca Junior", applicationName: "Evandro.dev.br", category: "Technology", metadataBase: new URL(siteUrl), formatDetection: { email: false, address: false, telephone: false, }, openGraph: { title: defaultTitle, description: seoDescription, url: siteUrl, siteName: "Evandro.dev.br", locale: "pt_BR", type: "website", images: [ { url: "/opengraph-image", width: 1200, height: 630, alt: "Evandro.dev.br | Sites e sistemas para empresas", }, ], }, twitter: { card: "summary_large_image", title: defaultTitle, description: seoDescription, creator: "@evandrodevbr", images: [ { url: "/opengraph-image", alt: "Evandro.dev.br | Sites e sistemas para empresas", }, ], }, robots: { index: true, follow: true, googleBot: { index: true, follow: true, "max-video-preview": -1, "max-image-preview": "large", "max-snippet": -1, }, }, }; export const viewport: Viewport = { themeColor: "#000000", width: "device-width", initialScale: 1, maximumScale: 5, }; interface RootLayoutProps { children: ReactNode; } export default function RootLayout({ children }: Readonly) { const jsonLd = { "@context": "https://schema.org", "@graph": [buildWebSiteJsonLd(), buildPersonJsonLd()], }; return ( // data-scroll-behavior: com `scroll-behavior: smooth` no CSS global, o Next desliga a suavização só durante a troca // de página (a página nova abre no topo, sem deslizar) e a restaura depois; as âncoras da mesma página seguem suaves. {/* Set arrival policy before native restoration and client-side layout changes. */} {/* Sem JavaScript as animações de entrada (opacity:0 inline vindo do servidor e palavras de título em CSS) não rodam: tudo precisa aparecer. */} Pular para o conteúdo {/* Aviso de cookies logo depois do "pular": fica cedo na ordem do Tab, ainda que fixo no rodapé da tela. */}
{children}
{/* Rodapé de todas as páginas; z-10 o mantém acima das cenas da home (relative z-0). O id deixa o menu do celular torná-lo inerte. */} ); }