O botão das ofertas rola até o contato com o tipo marcado e não quebra mais as âncoras; ofertas com o que você recebe e primeiro passo, logo depois da vitrine; bloco de IA fora da home; cartão largo da SEV no hero do celular; separador da faixa de clientes só entre itens; favicon de 13 para 1,8 KB; pré-carregamento só no hover e nunca de /#... fora da home; X do menu e cabeçalho opaco; WhatsApp com "o case"; /ia com 3 passos e modelo de relatório ilustrativo; Sobre alinhado ao currículo; CSP Report-Only com os domínios do Ads.
173 lines
5.8 KiB
TypeScript
173 lines
5.8 KiB
TypeScript
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 com pedido pelo WhatsApp e sistemas que tiram sua operação da planilha. 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ó nomes, sem o conteúdo pesado das páginas. */
|
|
const whatsAppContexts: WhatsAppContexts = {
|
|
services: Object.fromEntries(
|
|
servicePages.map((service) => [service.slug, service.navLabel]),
|
|
),
|
|
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<RootLayoutProps>) {
|
|
const jsonLd = {
|
|
"@context": "https://schema.org",
|
|
"@graph": [buildWebSiteJsonLd(), buildPersonJsonLd()],
|
|
};
|
|
|
|
return (
|
|
<html
|
|
lang="pt-BR"
|
|
className={`${spaceGrotesk.variable} ${geistMono.variable}`}
|
|
>
|
|
<head>
|
|
{/* Set arrival policy before native restoration and client-side layout changes. */}
|
|
<style id="arrival-scroll-policy">{`html { scroll-behavior: auto !important; }`}</style>
|
|
{/* 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. */}
|
|
<noscript>
|
|
<style>{`[style*="opacity:0"],[style*="clip-path"]{opacity:1!important;transform:none!important;clip-path:none!important}.text-reveal-word{opacity:1!important;transform:none!important}.js-only{display:none!important}`}</style>
|
|
</noscript>
|
|
<script>{`
|
|
addEventListener("load", () => {
|
|
const sheet = document.getElementById("arrival-scroll-policy")?.sheet;
|
|
if (sheet) sheet.disabled = true;
|
|
}, { once: true });
|
|
${FRAGMENT_NAV_BOOT_SCRIPT}
|
|
`}</script>
|
|
</head>
|
|
<body>
|
|
<script type="application/ld+json">{safeJsonLd(jsonLd)}</script>
|
|
<a className="skip-link" href="#main-content">
|
|
Pular para o conteúdo
|
|
</a>
|
|
{/* Aviso de cookies logo depois do "pular": fica cedo na ordem do Tab, ainda que fixo no rodapé da tela. */}
|
|
<GoogleAnalytics />
|
|
<Header whatsAppContexts={whatsAppContexts} />
|
|
<CinematicOverlays />
|
|
<div id="main-content" tabIndex={-1}>
|
|
{children}
|
|
</div>
|
|
{/* 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. */}
|
|
<div id="site-footer" className="relative z-10">
|
|
<Footer serviceLinks={serviceHubLinks} />
|
|
</div>
|
|
<FloatingWhatsApp contexts={whatsAppContexts} />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|