feat(marca): atualiza logo, ícones, manifest e Open Graph para a identidade atual (#3)
- Logo em duas cores (teal + cobre), inline no cabeçalho e rodapé para seguir o tema do site; SVGs estáticos com prefers-color-scheme. - scripts/generate-favicons.mjs passa a gerar favicons, ícones de app (maskable), site.webmanifest e a imagem Open Graph 1200x630. - Manifest com fundo/tema obsidiana e lang pt-BR. Claude-Session: https://claude.ai/code/session_016W1xmizFcmXLrmabSvfSuc Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
No files matched your search
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 199 KiB |
@@ -1,4 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import Logo from "@/components/Logo";
|
||||||
import { ArrowUpRight } from "lucide-react";
|
import { ArrowUpRight } from "lucide-react";
|
||||||
import { SITE } from "@/lib/seo";
|
import { SITE } from "@/lib/seo";
|
||||||
|
|
||||||
@@ -41,8 +42,7 @@ export default function Footer() {
|
|||||||
<div className="grid gap-12 lg:grid-cols-[1.1fr_2fr]">
|
<div className="grid gap-12 lg:grid-cols-[1.1fr_2fr]">
|
||||||
<div>
|
<div>
|
||||||
<Link href="/" className="inline-flex min-h-11 items-center gap-2.5" aria-label="ESPDocs, página inicial">
|
<Link href="/" className="inline-flex min-h-11 items-center gap-2.5" aria-label="ESPDocs, página inicial">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
<Logo className="h-7 w-7" />
|
||||||
<img src="/marca/espdocs-marca.svg" alt="" aria-hidden="true" width={28} height={28} className="h-7 w-7" />
|
|
||||||
<span className="font-display text-2xl font-semibold tracking-[-0.045em] text-ink">ESPDocs</span>
|
<span className="font-display text-2xl font-semibold tracking-[-0.045em] text-ink">ESPDocs</span>
|
||||||
</Link>
|
</Link>
|
||||||
<p className="mt-5 max-w-sm text-sm leading-relaxed text-muted">
|
<p className="mt-5 max-w-sm text-sm leading-relaxed text-muted">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import Logo from "@/components/Logo";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import {
|
import {
|
||||||
@@ -121,15 +122,7 @@ export default function Header() {
|
|||||||
<div className="shell">
|
<div className="shell">
|
||||||
<div className="nav-pill flex h-16 items-center justify-between gap-3 rounded-full px-3 sm:px-4">
|
<div className="nav-pill flex h-16 items-center justify-between gap-3 rounded-full px-3 sm:px-4">
|
||||||
<Link href="/" className="flex min-h-11 shrink-0 items-center gap-2.5 rounded-full pl-1 pr-2" aria-label="ESPDocs, página inicial">
|
<Link href="/" className="flex min-h-11 shrink-0 items-center gap-2.5 rounded-full pl-1 pr-2" aria-label="ESPDocs, página inicial">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
<Logo className="h-[30px] w-[30px]" />
|
||||||
<img
|
|
||||||
src="/marca/espdocs-marca.svg"
|
|
||||||
alt=""
|
|
||||||
aria-hidden="true"
|
|
||||||
width={30}
|
|
||||||
height={30}
|
|
||||||
className="h-[30px] w-[30px]"
|
|
||||||
/>
|
|
||||||
<span className="font-display text-[1.3rem] font-semibold leading-none tracking-[-0.045em] text-ink">ESPDocs</span>
|
<span className="font-display text-[1.3rem] font-semibold leading-none tracking-[-0.045em] text-ink">ESPDocs</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Marca do ESPDocs: o encapsulamento do chip com pinos à esquerda; os pinos e
|
||||||
|
* o corpo viram linhas de texto, a documentação. Inline para seguir o tema do
|
||||||
|
* site (--accent e --copper trocam entre claro e escuro, ao contrário de uma
|
||||||
|
* imagem externa, que só enxerga a preferência do sistema).
|
||||||
|
*/
|
||||||
|
export default function Logo({ className = "h-8 w-8", title }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
className={className}
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
role={title ? "img" : undefined}
|
||||||
|
aria-label={title}
|
||||||
|
aria-hidden={title ? undefined : "true"}
|
||||||
|
>
|
||||||
|
{title && <title>{title}</title>}
|
||||||
|
<g stroke="var(--accent)" strokeWidth="3.4">
|
||||||
|
<path d="M2 21h7" />
|
||||||
|
<path d="M2 32h7" />
|
||||||
|
<path d="M2 43h7" />
|
||||||
|
</g>
|
||||||
|
<rect x="9.25" y="9.25" width="45.5" height="45.5" rx="13" stroke="var(--accent)" strokeWidth="4.5" />
|
||||||
|
<g stroke="var(--copper)" strokeWidth="4.5">
|
||||||
|
<path d="M19 21h26" />
|
||||||
|
<path d="M19 32h21" />
|
||||||
|
<path d="M19 43h15" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 686 B After Width: | Height: | Size: 554 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 8.4 KiB |
@@ -1,29 +1,17 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="ESPDocs">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="ESPDocs">
|
||||||
<title>ESPDocs</title>
|
<title>ESPDocs</title>
|
||||||
<defs>
|
<style>
|
||||||
<linearGradient id="g" x1="6" y1="58" x2="58" y2="6" gradientUnits="userSpaceOnUse">
|
.p{stroke:#0a5852}.l{stroke:#a8452f}.t{fill:#0b0d10}
|
||||||
<stop offset="0" stop-color="#3b82f6"/>
|
@media (prefers-color-scheme: dark){.p{stroke:#2dd4bf}.l{stroke:#ffb39b}.t{fill:#f4f6f8}}
|
||||||
<stop offset="0.55" stop-color="#8b5cf6"/>
|
</style>
|
||||||
<stop offset="1" stop-color="#ec4899"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<!-- Pinos entrando pela esquerda: o sinal que chega do silício. -->
|
<!-- Pinos entrando pela esquerda: o sinal que chega do silício. -->
|
||||||
<g stroke="url(#g)" stroke-width="3.4" stroke-linecap="round">
|
<g class="p" fill="none" stroke-width="3.4" stroke-linecap="round">
|
||||||
<path d="M2 21h7"/>
|
<path d="M2 21h7"/><path d="M2 32h7"/><path d="M2 43h7"/>
|
||||||
<path d="M2 32h7"/>
|
|
||||||
<path d="M2 43h7"/>
|
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Corpo do encapsulamento. -->
|
<!-- Corpo do encapsulamento. -->
|
||||||
<rect x="9.25" y="9.25" width="45.5" height="45.5" rx="13"
|
<rect class="p" x="9.25" y="9.25" width="45.5" height="45.5" rx="13" fill="none" stroke-width="4.5"/>
|
||||||
fill="none" stroke="url(#g)" stroke-width="4.5"/>
|
<!-- Os mesmos três níveis viram linhas de texto: a documentação. -->
|
||||||
|
<g class="l" fill="none" stroke-width="4.5" stroke-linecap="round">
|
||||||
<!-- Os mesmos três níveis viram linhas de texto: a documentação.
|
<path d="M19 21h26"/><path d="M19 32h21"/><path d="M19 43h15"/>
|
||||||
Comprimento decrescente para ler como parágrafo. -->
|
|
||||||
<g stroke="url(#g)" stroke-width="4.5" stroke-linecap="round">
|
|
||||||
<path d="M19 21h26"/>
|
|
||||||
<path d="M19 32h21"/>
|
|
||||||
<path d="M19 43h15"/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 869 B |
@@ -1,19 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 64" role="img" aria-label="ESPDocs">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 64" role="img" aria-label="ESPDocs">
|
||||||
<title>ESPDocs</title>
|
<title>ESPDocs</title>
|
||||||
<defs>
|
<style>
|
||||||
<linearGradient id="gl" x1="6" y1="58" x2="250" y2="6" gradientUnits="userSpaceOnUse">
|
.p{stroke:#0a5852}.l{stroke:#823626}.t{fill:#0b0d10}
|
||||||
<stop offset="0" stop-color="#24645f"/>
|
@media (prefers-color-scheme: dark){.p{stroke:#2dd4bf}.l{stroke:#ffb39b}.t{fill:#f4f6f8}}
|
||||||
<stop offset="0.55" stop-color="#8a3410"/>
|
</style>
|
||||||
<stop offset="1" stop-color="#c4744f"/>
|
<!-- Pinos entrando pela esquerda: o sinal que chega do silício. -->
|
||||||
</linearGradient>
|
<g class="p" fill="none" stroke-width="3.4" stroke-linecap="round">
|
||||||
</defs>
|
|
||||||
<g stroke="url(#gl)" stroke-width="3.4" stroke-linecap="round">
|
|
||||||
<path d="M2 21h7"/><path d="M2 32h7"/><path d="M2 43h7"/>
|
<path d="M2 21h7"/><path d="M2 32h7"/><path d="M2 43h7"/>
|
||||||
</g>
|
</g>
|
||||||
<rect x="9.25" y="9.25" width="45.5" height="45.5" rx="13" fill="none" stroke="url(#gl)" stroke-width="4.5"/>
|
<!-- Corpo do encapsulamento. -->
|
||||||
<g stroke="url(#gl)" stroke-width="4.5" stroke-linecap="round">
|
<rect class="p" x="9.25" y="9.25" width="45.5" height="45.5" rx="13" fill="none" stroke-width="4.5"/>
|
||||||
|
<!-- Os mesmos três níveis viram linhas de texto: a documentação. -->
|
||||||
|
<g class="l" fill="none" stroke-width="4.5" stroke-linecap="round">
|
||||||
<path d="M19 21h26"/><path d="M19 32h21"/><path d="M19 43h15"/>
|
<path d="M19 21h26"/><path d="M19 32h21"/><path d="M19 43h15"/>
|
||||||
</g>
|
</g>
|
||||||
<text x="72" y="43" font-family="Outfit, Inter, system-ui, sans-serif" font-size="34"
|
<text class="t" x="72" y="43" font-family="'Space Grotesk', Inter, system-ui, sans-serif" font-size="34" font-weight="600" letter-spacing="-1.5">ESPDocs</text>
|
||||||
font-weight="800" letter-spacing="-0.5" fill="url(#gl)">ESPDocs</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 954 B After Width: | Height: | Size: 1.0 KiB |
@@ -1,29 +1,17 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="ESPDocs">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="ESPDocs">
|
||||||
<title>ESPDocs</title>
|
<title>ESPDocs</title>
|
||||||
<defs>
|
<style>
|
||||||
<linearGradient id="g" x1="6" y1="58" x2="58" y2="6" gradientUnits="userSpaceOnUse">
|
.p{stroke:#0a5852}.l{stroke:#823626}.t{fill:#0b0d10}
|
||||||
<stop offset="0" stop-color="#24645f"/>
|
@media (prefers-color-scheme: dark){.p{stroke:#2dd4bf}.l{stroke:#ffb39b}.t{fill:#f4f6f8}}
|
||||||
<stop offset="0.55" stop-color="#8a3410"/>
|
</style>
|
||||||
<stop offset="1" stop-color="#c4744f"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<!-- Pinos entrando pela esquerda: o sinal que chega do silício. -->
|
<!-- Pinos entrando pela esquerda: o sinal que chega do silício. -->
|
||||||
<g stroke="url(#g)" stroke-width="3.4" stroke-linecap="round">
|
<g class="p" fill="none" stroke-width="3.4" stroke-linecap="round">
|
||||||
<path d="M2 21h7"/>
|
<path d="M2 21h7"/><path d="M2 32h7"/><path d="M2 43h7"/>
|
||||||
<path d="M2 32h7"/>
|
|
||||||
<path d="M2 43h7"/>
|
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Corpo do encapsulamento. -->
|
<!-- Corpo do encapsulamento. -->
|
||||||
<rect x="9.25" y="9.25" width="45.5" height="45.5" rx="13"
|
<rect class="p" x="9.25" y="9.25" width="45.5" height="45.5" rx="13" fill="none" stroke-width="4.5"/>
|
||||||
fill="none" stroke="url(#g)" stroke-width="4.5"/>
|
<!-- Os mesmos três níveis viram linhas de texto: a documentação. -->
|
||||||
|
<g class="l" fill="none" stroke-width="4.5" stroke-linecap="round">
|
||||||
<!-- Os mesmos três níveis viram linhas de texto: a documentação.
|
<path d="M19 21h26"/><path d="M19 32h21"/><path d="M19 43h15"/>
|
||||||
Comprimento decrescente para ler como parágrafo. -->
|
|
||||||
<g stroke="url(#g)" stroke-width="4.5" stroke-linecap="round">
|
|
||||||
<path d="M19 21h26"/>
|
|
||||||
<path d="M19 32h21"/>
|
|
||||||
<path d="M19 43h15"/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 869 B |
@@ -2,10 +2,11 @@
|
|||||||
"name": "ESPDocs",
|
"name": "ESPDocs",
|
||||||
"short_name": "ESPDocs",
|
"short_name": "ESPDocs",
|
||||||
"description": "Documentação do ecossistema ESP32 em português brasileiro",
|
"description": "Documentação do ecossistema ESP32 em português brasileiro",
|
||||||
|
"lang": "pt-BR",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#020617",
|
"background_color": "#07090d",
|
||||||
"theme_color": "#8b5cf6",
|
"theme_color": "#07090d",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/favicon-48x48.png",
|
"src": "/favicon-48x48.png",
|
||||||
@@ -15,12 +16,14 @@
|
|||||||
{
|
{
|
||||||
"src": "/icon-192.png",
|
"src": "/icon-192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/icon-512.png",
|
"src": "/icon-512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,31 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
|
|
||||||
const svgPath = fs.existsSync(path.resolve('public/icon.svg'))
|
/**
|
||||||
? path.resolve('public/icon.svg')
|
* Gera todos os ativos de marca a partir da geometria do logo:
|
||||||
: fs.existsSync(path.resolve('app/icon.svg'))
|
* favicons (transparentes), ícones de app (tile obsidiana), site.webmanifest
|
||||||
? path.resolve('app/icon.svg')
|
* e a imagem de compartilhamento (Open Graph, 1200x630).
|
||||||
: path.resolve('public/marca/espdocs-marca.svg');
|
*
|
||||||
const svgContent = fs.readFileSync(svgPath, 'utf8');
|
* Cores = tokens de app/globals.css. Uso: npm run favicons
|
||||||
|
* (usa o Chrome do Puppeteer; defina CHROME_PATH para usar outro binário)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const COR = {
|
||||||
|
obsidiana: "#07090d",
|
||||||
|
tealEscuro: "#2dd4bf",
|
||||||
|
cobreEscuro: "#ffb39b",
|
||||||
|
tealMedio: "#1a8f83", // legível em aba clara e escura
|
||||||
|
cobreMedio: "#c8694e",
|
||||||
|
texto: "#f4f6f8",
|
||||||
|
suave: "#a6afbd",
|
||||||
|
};
|
||||||
|
|
||||||
|
const marca = (teal, cobre) => `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" stroke-linecap="round">
|
||||||
|
<g stroke="${teal}" stroke-width="3.4"><path d="M2 21h7"/><path d="M2 32h7"/><path d="M2 43h7"/></g>
|
||||||
|
<rect x="9.25" y="9.25" width="45.5" height="45.5" rx="13" stroke="${teal}" stroke-width="4.5"/>
|
||||||
|
<g stroke="${cobre}" stroke-width="4.5"><path d="M19 21h26"/><path d="M19 32h21"/><path d="M19 43h15"/></g>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
function createIco(pngBuffers) {
|
function createIco(pngBuffers) {
|
||||||
// pngBuffers: array of { width, height, buffer }
|
// pngBuffers: array of { width, height, buffer }
|
||||||
@@ -39,92 +58,97 @@ function createIco(pngBuffers) {
|
|||||||
return Buffer.concat([header, ...entries, ...pngBuffers.map(b => b.buffer)]);
|
return Buffer.concat([header, ...entries, ...pngBuffers.map(b => b.buffer)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const pagina = (corpo, css = "") => `<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500&family=Space+Grotesk:wght@600&display=block" rel="stylesheet">
|
||||||
|
<style>*{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%;overflow:hidden;background:transparent}${css}</style>
|
||||||
|
</head><body>${corpo}</body></html>`;
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
|
executablePath: process.env.CHROME_PATH || undefined,
|
||||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
||||||
const html = `<!DOCTYPE html>
|
// 1. Favicons: marca sozinha, fundo transparente. O logo ocupa x 2..55; centraliza.
|
||||||
<html>
|
const favicon = pagina(`<div style="width:100%;height:100%;display:grid;place-items:center">${marca(COR.tealMedio, COR.cobreMedio).replace('<svg ', '<svg style="width:100%;height:100%;display:block" ')}</div>`);
|
||||||
<head>
|
await page.setContent(favicon);
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
html, body { width: 100%; height: 100%; overflow: hidden; background: transparent; }
|
|
||||||
svg { width: 100%; height: 100%; display: block; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>${svgContent}</body>
|
|
||||||
</html>`;
|
|
||||||
|
|
||||||
await page.setContent(html);
|
|
||||||
|
|
||||||
const sizes = [16, 32, 48, 96, 180, 192, 512];
|
|
||||||
const rendered = {};
|
const rendered = {};
|
||||||
|
for (const size of [16, 32, 48]) {
|
||||||
for (const size of sizes) {
|
|
||||||
await page.setViewport({ width: size, height: size, deviceScaleFactor: 1 });
|
await page.setViewport({ width: size, height: size, deviceScaleFactor: 1 });
|
||||||
const buffer = await page.screenshot({
|
rendered[size] = await page.screenshot({ type: 'png', omitBackground: true, clip: { x: 0, y: 0, width: size, height: size } });
|
||||||
type: 'png',
|
|
||||||
omitBackground: true,
|
|
||||||
clip: { x: 0, y: 0, width: size, height: size }
|
|
||||||
});
|
|
||||||
rendered[size] = buffer;
|
|
||||||
console.log(`Rendered ${size}x${size}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Ícones de app: tile obsidiana, marca com margem de segurança (maskable).
|
||||||
|
const tile = pagina(`<div style="width:100%;height:100%;background:${COR.obsidiana};display:grid;place-items:center">
|
||||||
|
<div style="width:58%;height:58%;transform:translateX(5%)">${marca(COR.tealEscuro, COR.cobreEscuro).replace('<svg ', '<svg style="width:100%;height:100%;display:block" ')}</div></div>`);
|
||||||
|
await page.setContent(tile);
|
||||||
|
for (const size of [180, 192, 512]) {
|
||||||
|
await page.setViewport({ width: size, height: size, deviceScaleFactor: 1 });
|
||||||
|
rendered[size] = await page.screenshot({ type: 'png', clip: { x: 0, y: 0, width: size, height: size } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Open Graph / Twitter: 1200x630.
|
||||||
|
const og = pagina(`
|
||||||
|
<main>
|
||||||
|
<div class="topo">${marca(COR.tealEscuro, COR.cobreEscuro).replace('<svg ', '<svg class="logo" ')}<span class="nome">ESPDocs</span></div>
|
||||||
|
<h1>Documentação do ESP32<br>em português</h1>
|
||||||
|
<p>Especificações, pinagem, comparação e seleção das 12 séries do ESP32.</p>
|
||||||
|
<div class="chips"><span>Séries</span><span>Seletor</span><span>Comparador</span><span>Diagnóstico</span><span>Livro</span></div>
|
||||||
|
</main>
|
||||||
|
<div class="barra"></div>`, `
|
||||||
|
body{background:${COR.obsidiana};font-family:'Manrope',system-ui,sans-serif;color:${COR.texto};position:relative}
|
||||||
|
body::before{content:"";position:absolute;inset:0;background:radial-gradient(52rem 30rem at 50% -14%,rgba(45,212,191,.16),transparent 70%),radial-gradient(40rem 26rem at 100% 30%,rgba(56,140,248,.09),transparent 70%)}
|
||||||
|
main{position:relative;height:100%;padding:76px 88px 0}
|
||||||
|
.topo{display:flex;align-items:center;gap:20px}
|
||||||
|
.logo{width:68px;height:68px}
|
||||||
|
.nome{font:600 44px 'Space Grotesk',sans-serif;letter-spacing:-0.045em}
|
||||||
|
h1{margin-top:64px;font:600 86px/1.03 'Space Grotesk',sans-serif;letter-spacing:-0.04em}
|
||||||
|
p{margin-top:30px;max-width:900px;font-size:30px;line-height:1.45;color:${COR.suave}}
|
||||||
|
.chips{position:absolute;left:88px;bottom:62px;display:flex;gap:12px}
|
||||||
|
.chips span{border:1.5px solid #2e3846;border-radius:999px;padding:9px 22px;font-size:22px;font-weight:500;color:${COR.texto}}
|
||||||
|
.barra{position:absolute;left:0;right:0;bottom:0;height:10px;background:${COR.tealEscuro}}`);
|
||||||
|
await page.setViewport({ width: 1200, height: 630, deviceScaleFactor: 1 });
|
||||||
|
await page.setContent(og, { waitUntil: 'networkidle0' });
|
||||||
|
await page.evaluate(() => document.fonts.ready);
|
||||||
|
const ogPng = await page.screenshot({ type: 'png', clip: { x: 0, y: 0, width: 1200, height: 630 } });
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
|
||||||
// Create ICO from 16, 32, 48
|
|
||||||
const icoBuffer = createIco([
|
const icoBuffer = createIco([
|
||||||
{ width: 16, height: 16, buffer: rendered[16] },
|
{ width: 16, height: 16, buffer: rendered[16] },
|
||||||
{ width: 32, height: 32, buffer: rendered[32] },
|
{ width: 32, height: 32, buffer: rendered[32] },
|
||||||
{ width: 48, height: 48, buffer: rendered[48] }
|
{ width: 48, height: 48, buffer: rendered[48] }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Write favicon.ico to public
|
|
||||||
fs.writeFileSync('public/favicon.ico', icoBuffer);
|
fs.writeFileSync('public/favicon.ico', icoBuffer);
|
||||||
console.log('Saved favicon.ico to public/');
|
|
||||||
|
|
||||||
// Write PNG favicons to public
|
|
||||||
fs.writeFileSync('public/favicon-48x48.png', rendered[48]);
|
fs.writeFileSync('public/favicon-48x48.png', rendered[48]);
|
||||||
fs.writeFileSync('public/favicon-32x32.png', rendered[32]);
|
fs.writeFileSync('public/favicon-32x32.png', rendered[32]);
|
||||||
fs.writeFileSync('public/favicon-16x16.png', rendered[16]);
|
fs.writeFileSync('public/favicon-16x16.png', rendered[16]);
|
||||||
fs.writeFileSync('public/apple-touch-icon.png', rendered[180]);
|
fs.writeFileSync('public/apple-touch-icon.png', rendered[180]);
|
||||||
fs.writeFileSync('public/icon-192.png', rendered[192]);
|
fs.writeFileSync('public/icon-192.png', rendered[192]);
|
||||||
fs.writeFileSync('public/icon-512.png', rendered[512]);
|
fs.writeFileSync('public/icon-512.png', rendered[512]);
|
||||||
console.log('Saved PNG icons to public/');
|
fs.writeFileSync('app/opengraph-image.png', ogPng);
|
||||||
|
console.log('Favicons, ícones de app e imagem Open Graph gerados.');
|
||||||
|
|
||||||
// Create site.webmanifest
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
name: "ESPDocs",
|
name: "ESPDocs",
|
||||||
short_name: "ESPDocs",
|
short_name: "ESPDocs",
|
||||||
description: "Documentação do ecossistema ESP32 em português brasileiro",
|
description: "Documentação do ecossistema ESP32 em português brasileiro",
|
||||||
|
lang: "pt-BR",
|
||||||
start_url: "/",
|
start_url: "/",
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
background_color: "#020617",
|
background_color: COR.obsidiana,
|
||||||
theme_color: "#8b5cf6",
|
theme_color: COR.obsidiana,
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{ src: "/favicon-48x48.png", sizes: "48x48", type: "image/png" },
|
||||||
src: "/favicon-48x48.png",
|
{ src: "/icon-192.png", sizes: "192x192", type: "image/png", purpose: "any maskable" },
|
||||||
sizes: "48x48",
|
{ src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "any maskable" }
|
||||||
type: "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/icon-192.png",
|
|
||||||
sizes: "192x192",
|
|
||||||
type: "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "/icon-512.png",
|
|
||||||
sizes: "512x512",
|
|
||||||
type: "image/png"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
fs.writeFileSync('public/site.webmanifest', JSON.stringify(manifest, null, 2) + '\n');
|
||||||
fs.writeFileSync('public/site.webmanifest', JSON.stringify(manifest, null, 2));
|
console.log('site.webmanifest gerado.');
|
||||||
console.log('Saved site.webmanifest to public/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run().catch(err => {
|
run().catch(err => {
|
||||||
|
|||||||