/** * Fonte única das informações de SEO do ESPDocs. * * Centralizar aqui evita o problema clássico de metadata espalhado: a URL do * site aparecendo com valores diferentes em cada arquivo, ou uma página nova * nascendo sem título próprio. */ import { ENABLED_LOCALES, getLocalizedHref } from "@/i18n/links"; export const SITE = { url: "https://espdocs.cienciaembarcada.com.br", nome: "ESPDocs", autor: "Lucas Rayan Guerra", autorUrl: "https://lucasrguerra.dev.br", autorLinkedin: "https://linkedin.com/in/lucasrguerra", autorEmail: "contato@lucasrguerra.dev.br", repositorio: "https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs", blog: "https://cienciaembarcada.com.br", livroTitulo: "Microcontroladores ESP32", livroSubtitulo: "Conectividade, Eficiência e Desempenho", livroIsbn: "978-65-02-43432-1", livroCapa: "/livro/capa.jpg", livroAmazon: "https://a.co/d/0fdCvzlg", livroUiclap: "https://loja.uiclap.com/titulo/ua213708", }; /** * Monta o objeto `metadata` de uma página, já com canonical e Open Graph. * Usar isto em vez de escrever o objeto à mão garante que nenhuma página fique * sem canonical ou sem og:title. */ export function paginaMeta({ titulo, descricao, caminho, keywords = [], tipo = "website", locale = "pt" }) { const currentLocale = locale || "pt"; const basePath = caminho.startsWith("/") ? caminho : `/${caminho}`; const canonicalPath = getLocalizedHref(`/pt${basePath}`, currentLocale); const url = `${SITE.url}${canonicalPath}`; const languages = {}; for (const loc of ENABLED_LOCALES) { if (basePath === "/livro" && loc !== "pt") continue; const locHref = getLocalizedHref(`/pt${basePath}`, loc); const langKey = loc === "pt" ? "pt-BR" : loc; languages[langKey] = `${SITE.url}${locHref}`; } if (ENABLED_LOCALES.includes("en") && basePath !== "/livro") { languages["x-default"] = `${SITE.url}${getLocalizedHref(`/pt${basePath}`, "en")}`; } const ogLocaleMap = { pt: "pt_BR", en: "en_US", es: "es_419", fr: "fr_FR", de: "de_DE", "zh-CN": "zh_CN", }; const alternateLocales = ENABLED_LOCALES .filter((l) => l !== currentLocale && (basePath !== "/livro" || l === "pt")) .map((l) => ogLocaleMap[l]) .filter(Boolean); return { title: titulo, description: descricao, keywords, alternates: { canonical: canonicalPath, languages: Object.keys(languages).length > 1 ? languages : undefined, }, openGraph: { type: tipo, locale: ogLocaleMap[currentLocale] || "pt_BR", alternateLocale: alternateLocales.length > 0 ? alternateLocales : undefined, url, siteName: SITE.nome, title: `${titulo} · ${SITE.nome}`, description: descricao, }, twitter: { card: "summary_large_image", title: `${titulo} · ${SITE.nome}`, description: descricao, }, }; } /* ------------------------------------------------------------------------ Dados estruturados (JSON-LD). São eles que rendem os resultados ricos: trilha de navegação, box de busca e reconhecimento de autoria. ------------------------------------------------------------------------ */ export const jsonLd = { siteESociedade(locale = "pt") { const isEn = locale === "en"; return { "@context": "https://schema.org", "@graph": [ { "@type": "WebSite", "@id": `${SITE.url}/#site`, url: `${SITE.url}/${locale}`, name: SITE.nome, inLanguage: isEn ? "en" : "pt-BR", description: isEn ? "ESP32 ecosystem documentation: specifications, pinouts, comparison, and series selection." : "Documentação do ecossistema ESP32 em português brasileiro: especificações, pinagem, comparação e seleção de séries.", publisher: { "@id": `${SITE.url}/#${isEn ? "organization" : "organizacao"}` }, }, { "@type": "Organization", "@id": `${SITE.url}/#${isEn ? "organization" : "organizacao"}`, name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, image: `${SITE.url}/icon-512.png`, sameAs: [SITE.autorLinkedin, SITE.repositorio, SITE.blog], }, { "@type": "Person", "@id": `${SITE.url}/#${isEn ? "author" : "autor"}`, name: SITE.autor, url: SITE.autorUrl, email: SITE.autorEmail, sameAs: [SITE.autorLinkedin, SITE.repositorio, SITE.blog], }, ], }; }, /** Trilha de navegação: aparece acima do título no resultado de busca. */ trilha(itens) { return { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: itens.map((it, i) => ({ "@type": "ListItem", position: i + 1, name: it.nome, item: `${SITE.url}${it.caminho}`, })), }; }, /** Documentação e ficha técnica da série como TechArticle para o Google. */ serie(chave, serie, locale = "pt") { const isEn = locale === "en"; const prop = (nome, valor) => valor ? { "@type": "PropertyValue", name: nome, value: String(valor) } : null; const propriedades = [ prop(isEn ? "Manufacturer" : "Fabricante", "Espressif Systems"), prop(isEn ? "Architecture" : "Arquitetura", serie.arquitetura), prop(isEn ? "Cores" : "Núcleos", serie.nucleos), prop(isEn ? "Max frequency" : "Frequência máxima", serie.frequencia), prop("SRAM", serie.memoria_sram), prop("RTC SRAM", serie.memoria_sram_rtc), prop(isEn ? "Supported Flash" : "Flash suportada", serie.flash_suportada), prop(isEn ? "Supported PSRAM" : "PSRAM suportada", serie.psram_suportada), prop("GPIO", serie.gpio), prop("Wi-Fi", serie.wifi), prop("Bluetooth", serie.bluetooth), prop("Zigbee / Thread / 802.15.4", serie.zigbee_thread), prop(isEn ? "Package" : "Encapsulamento", serie.encapsulamento), prop(isEn ? "Minimum ESP-IDF" : "ESP-IDF mínimo", serie.esp_idf_minimo), ].filter(Boolean); const imagem = serie.placa ? `${SITE.url}${serie.placa}` : `${SITE.url}/opengraph-image.png`; const basePath = isEn ? `/en/series/${chave}` : `/series/${chave}`; return { "@context": "https://schema.org", "@type": "TechArticle", "@id": `${SITE.url}${basePath}#${isEn ? "article" : "artigo"}`, headline: isEn ? `${serie.nome_completo || chave}: specifications and pinout` : `${serie.nome_completo || chave}: especificações e pinagem`, name: isEn ? `${serie.nome_completo || chave} - Technical Documentation and Specifications` : `${serie.nome_completo || chave} - Documentação Técnica e Especificações`, description: serie.descricao, image: [imagem], inLanguage: isEn ? "en" : "pt-BR", datePublished: "2026-08-25T00:00:00.000Z", dateModified: new Date().toISOString(), mainEntityOfPage: `${SITE.url}${basePath}`, url: `${SITE.url}${basePath}`, author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, }, about: { "@type": "Thing", name: serie.nome_completo || chave, description: serie.descricao, additionalProperty: propriedades, }, }; }, /** Documentação técnica de framework. */ framework(chave, framework, locale = "pt") { const isEn = locale === "en"; const basePath = isEn ? `/en/frameworks/${encodeURIComponent(chave)}` : `/frameworks/${encodeURIComponent(chave)}`; return { "@context": "https://schema.org", "@type": "TechArticle", "@id": `${SITE.url}${basePath}#${isEn ? "article" : "artigo"}`, headline: isEn ? `${framework.nome} for ESP32: Guide and Documentation` : `${framework.nome} para ESP32: Guia e Documentação`, name: isEn ? `${framework.nome_completo || framework.nome} for ESP32` : `${framework.nome_completo || framework.nome} para ESP32`, description: framework.descricao, image: [`${SITE.url}/opengraph-image.png`], inLanguage: isEn ? "en" : "pt-BR", datePublished: "2026-08-25T00:00:00.000Z", dateModified: new Date().toISOString(), mainEntityOfPage: `${SITE.url}${basePath}`, url: `${SITE.url}${basePath}`, author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, }, about: { "@type": "SoftwareApplication", name: framework.nome_completo || framework.nome, applicationCategory: "DeveloperApplication", operatingSystem: "Embedded", description: framework.descricao, url: framework.documentacao || `${SITE.url}${basePath}`, }, }; }, /** Glossário: marca cada verbete como termo definido. */ glossario(categorias, locale = "pt") { const isEn = locale === "en"; const basePath = isEn ? "/en/glossary" : "/glossario"; return { "@context": "https://schema.org", "@type": "DefinedTermSet", "@id": `${SITE.url}${basePath}#${isEn ? "glossary" : "glossario"}`, name: isEn ? "ESP32 Technical Glossary" : "Glossário Técnico do ESP32", inLanguage: isEn ? "en" : "pt-BR", hasDefinedTerm: categorias.flatMap((cat) => cat.termos.map((t) => ({ "@type": "DefinedTerm", name: t.termo, description: t.definicao, inDefinedTermSet: `${SITE.url}${basePath}#${isEn ? "glossary" : "glossario"}`, })) ), }; }, /** Perguntas frequentes: pode render o bloco expansível no Google. */ perguntas(pares) { return { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: pares.map(({ pergunta, resposta }) => ({ "@type": "Question", name: pergunta, acceptedAnswer: { "@type": "Answer", text: resposta }, })), }; }, /** Aplicação web / ferramenta interativa (Seletor, Comparador, Catálogo). */ aplicacaoWeb({ nome, descricao, caminho, locale = "pt" }) { const isEn = locale === "en"; return { "@context": "https://schema.org", "@type": "WebApplication", "@id": `${SITE.url}${caminho}#app`, name: nome, description: descricao, url: `${SITE.url}${caminho}`, applicationCategory: "DeveloperApplication", operatingSystem: "All", browserRequirements: "Requires JavaScript", inLanguage: isEn ? "en" : "pt-BR", author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, }, offers: { "@type": "Offer", price: "0", priceCurrency: "BRL", }, }; }, /** Livro do autor: dados de obra para mecanismos de busca. */ livro() { return { "@context": "https://schema.org", "@type": "Book", "@id": `${SITE.url}/livro#livro`, name: SITE.livroTitulo, alternateName: `${SITE.livroTitulo}: ${SITE.livroSubtitulo}`, description: "Livro em português sobre o hardware e o firmware do ESP32: arquitetura, energia, rádio, periféricos e projeto de produto, em 12 partes e 78 capítulos.", inLanguage: "pt-BR", url: `${SITE.url}/livro`, isbn: SITE.livroIsbn, image: `${SITE.url}${SITE.livroCapa}`, author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, }, about: [ { "@type": "Thing", name: "ESP32" }, { "@type": "Thing", name: "Sistemas embarcados" }, { "@type": "Thing", name: "Internet das Coisas" }, ], workExample: [ { "@type": "Book", "@id": `${SITE.url}/livro#digital`, bookFormat: "https://schema.org/EBook", url: SITE.livroAmazon, potentialAction: { "@type": "BuyAction", target: SITE.livroAmazon, }, }, { "@type": "Book", "@id": `${SITE.url}/livro#fisico`, bookFormat: "https://schema.org/Paperback", url: SITE.livroUiclap, potentialAction: { "@type": "BuyAction", target: SITE.livroUiclap, }, }, ], }; }, /** Página Sobre com metadados E-E-A-T de autoria e reputação. */ paginaSobre(locale = "pt") { const isEn = locale === "en"; const basePath = isEn ? "/en/about" : "/sobre"; return { "@context": "https://schema.org", "@type": "AboutPage", "@id": `${SITE.url}${basePath}#${isEn ? "about" : "sobre"}`, name: isEn ? "About ESPDocs" : "Sobre o ESPDocs", description: isEn ? "ESPDocs is an independent platform organizing technical documentation of the ESP32 ecosystem." : "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro.", url: `${SITE.url}${basePath}`, inLanguage: isEn ? "en" : "pt-BR", mainEntity: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, email: SITE.autorEmail, sameAs: [ SITE.autorLinkedin, SITE.repositorio, SITE.blog, ], }, }; }, };