feat: adiciona arquivos no template-04

This commit is contained in:
Marcio Bevervanso 2026-05-05 11:34:21 -03:00
parent e824be6aba
commit c947596066
42 changed files with 9904 additions and 0 deletions

BIN
.DS_Store vendored

Binary file not shown.

20
Template-04/README.md Normal file
View file

@ -0,0 +1,20 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/b9e0fe6d-c7c5-42ea-8fbe-61d65dc0914c
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

32
Template-04/index.html Normal file
View file

@ -0,0 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SEO Authority | Insights Técnicos e Estratégicos</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%232563eb'%3E%3Cpath d='M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5'/%3E%3C/svg%3E" />
<meta name="theme-color" content="#ffffff" />
<style>
/* Hide Google Translate Bar */
.goog-te-banner-frame.skiptranslate, .goog-te-gadget-icon { display: none !important; }
body { top: 0px !important; }
.goog-te-menu-value { display: none !important; }
#google_translate_element { display: none; }
.goog-tooltip { display: none !important; }
.goog-tooltip:hover { display: none !important; }
.goog-text-highlight { background-color: transparent !important; border: none !important; box-shadow: none !important; }
</style>
</head>
<body>
<div id="google_translate_element"></div>
<div id="root"></div>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({pageLanguage: 'pt', includedLanguages: 'en,es,pt', layout: google.translate.TranslateElement.InlineLayout.SIMPLE, autoDisplay: false}, 'google_translate_element');
}
</script>
<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,6 @@
{
"name": "Apex SEO",
"description": "Plataforma profissional de alta densidade para inteligência de SEO técnico e crescimento arquitetônico estratégico.",
"requestFramePermissions": [],
"majorCapabilities": []
}

6068
Template-04/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

42
Template-04/package.json Normal file
View file

@ -0,0 +1,42 @@
{
"name": "react-example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "tsx server.ts",
"build": "vite build",
"preview": "vite preview",
"clean": "rm -rf dist",
"lint": "tsc --noEmit"
},
"dependencies": {
"@google/genai": "^1.51.0",
"@google/generative-ai": "^0.24.1",
"@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4",
"clsx": "^2.1.1",
"cors": "^2.8.6",
"dotenv": "^17.2.3",
"express": "^4.22.1",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"react": "^19.0.1",
"react-dom": "^19.0.1",
"react-helmet-async": "^3.0.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.14.2",
"recharts": "^3.8.1",
"tailwind-merge": "^3.5.0",
"vite": "^6.2.3"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.14.0",
"autoprefixer": "^10.4.21",
"tailwindcss": "^4.1.14",
"tsx": "^4.21.0",
"typescript": "~5.8.2",
"vite": "^6.2.3"
}
}

View file

@ -0,0 +1,5 @@
User-agent: *
Allow: /
# Sitemaps
Sitemap: https://seu-dominio.com/sitemap.xml

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://seo-authority.digital/</loc>
<lastmod>2026-05-04</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://seo-authority.digital/sobre</loc>
<lastmod>2026-05-04</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://seo-authority.digital/contato</loc>
<lastmod>2026-05-04</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://seo-authority.digital/metodologia</loc>
<lastmod>2026-05-04</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://seo-authority.digital/artigo/futuro-do-seo-2026</loc>
<lastmod>2026-05-04</lastmod>
<priority>0.9</priority>
</url>
<url>
<loc>https://seo-authority.digital/artigo/eeat-guia-completo</loc>
<lastmod>2026-05-04</lastmod>
<priority>0.9</priority>
</url>
<url>
<loc>https://seo-authority.digital/artigo/sinais-de-usuario-ranking</loc>
<lastmod>2026-05-04</lastmod>
<priority>0.9</priority>
</url>
</urlset>

97
Template-04/server.ts Normal file
View file

@ -0,0 +1,97 @@
import express from 'express';
import { createServer as createViteServer } from 'vite';
import { GoogleGenAI } from "@google/genai";
import path from 'path';
import cors from 'cors';
async function startServer() {
const app = express();
const PORT = 3000;
app.use(express.json());
app.use(cors());
// Lazy Gemini Setup
let ai: GoogleGenAI | null = null;
const getAI = () => {
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error("GEMINI_API_KEY environment variable is required");
}
if (!ai) {
ai = new GoogleGenAI({ apiKey });
}
return ai;
};
// Translation API
app.post('/api/translate', async (req, res) => {
const { text, targetLang, context, isObject } = req.body;
try {
const client = getAI();
let prompt = "";
if (isObject) {
prompt = `Translate the following JSON object values to ${targetLang}.
Keep the keys exactly the same.
Preserve markdown formatting in values.
Return ONLY the translated JSON object.
Object:
${JSON.stringify(text)}`;
} else {
prompt = `Translate the following ${context || 'text'} to ${targetLang}.
Preserve all markdown formatting, technical terms, and HTML structures if present.
Do not add any commentary or prefix. Return only the translated string.
Content:
${text}`;
}
const response = await client.models.generateContent({
model: "gemini-3-flash-preview",
contents: prompt,
config: isObject ? { responseMimeType: "application/json" } : undefined
});
let resultText = response.text || '';
if (isObject) {
try {
res.json({ translatedText: JSON.parse(resultText) });
} catch (e) {
console.error("Parse Error:", resultText);
res.json({ translatedText: text }); // Fallback to original if parse fails
}
} else {
res.json({ translatedText: resultText });
}
} catch (error) {
console.error("Translation Error:", error);
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to translate" });
}
});
// Vite Integration
if (process.env.NODE_ENV !== "production") {
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "spa",
});
app.use(vite.middlewares);
} else {
const distPath = path.join(process.cwd(), 'dist');
app.use(express.static(distPath));
app.get('*', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
}
app.listen(PORT, "0.0.0.0", () => {
console.log(`Server running at http://0.0.0.0:${PORT}`);
});
}
startServer();

50
Template-04/src/App.tsx Normal file
View file

@ -0,0 +1,50 @@
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
import Layout from './components/Layout';
import Home from './pages/Home';
import BlogPost from './pages/BlogPost';
import CategoryPage from './pages/CategoryPage';
import About from './pages/About';
import Methodology from './pages/Methodology';
import Privacy from './pages/Privacy';
import Terms from './pages/Terms';
import Contact from './pages/Contact';
import Archive from './pages/Archive';
import NotFound from './pages/NotFound';
import Bookmarks from './pages/Bookmarks';
import { LanguageProvider } from './contexts/LanguageContext';
import { BookmarksProvider } from './contexts/BookmarksContext';
export default function App() {
return (
<HelmetProvider>
<LanguageProvider>
<BookmarksProvider>
<Router>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/artigo/:slug" element={<BlogPost />} />
<Route path="/categoria/:categorySlug" element={<CategoryPage />} />
<Route path="/sobre" element={<About />} />
<Route path="/metodologia" element={<Methodology />} />
<Route path="/privacidade" element={<Privacy />} />
<Route path="/termos" element={<Terms />} />
<Route path="/contato" element={<Contact />} />
<Route path="/arquivo" element={<Archive />} />
<Route path="/leituras-salvas" element={<Bookmarks />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Layout>
</Router>
</BookmarksProvider>
</LanguageProvider>
</HelmetProvider>
);
}

View file

@ -0,0 +1,143 @@
import { Link } from 'react-router-dom';
import { Article } from '../types';
import { motion } from 'motion/react';
import { Bookmark, Clock, ArrowRight, BarChart2 } from 'lucide-react';
import { useBookmarks } from '../contexts/BookmarksContext';
import { cn } from '../lib/utils';
interface ArticleCardProps {
article: Article;
featured?: boolean;
}
export default function ArticleCard({ article, featured = false }: ArticleCardProps) {
const { toggleBookmark, isBookmarked } = useBookmarks();
const bookmarked = isBookmarked(article.id);
if (featured) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="group relative flex flex-col md:flex-row gap-0 glass-card glass-card-hover overflow-hidden"
>
<div className="md:w-1/2 relative aspect-video md:aspect-auto overflow-hidden">
<Link to={`/artigo/${article.slug}`} className="block h-full">
<img
src={article.image}
alt={article.title}
className="h-full w-full object-cover transition-transform duration-700 group-hover:scale-110"
referrerPolicy="no-referrer"
/>
</Link>
<div className="absolute inset-0 bg-gradient-to-r from-black/60 to-transparent" />
<div className="absolute top-6 left-6">
<div className="px-3 py-1 bg-brand text-white text-[10px] font-bold rounded-lg shadow-lg">Resultado em Destaque</div>
</div>
</div>
<div className="md:w-1/2 p-8 lg:p-12 flex flex-col justify-center">
<div className="flex items-center gap-3 mb-6">
<span className="text-xs font-bold uppercase tracking-widest text-brand">{article.category}</span>
<div className="h-px w-8 bg-white/10" />
</div>
<Link to={`/artigo/${article.slug}`}>
<h2 className="text-2xl sm:text-4xl font-bold text-white leading-tight mb-6 group-hover:text-brand transition-colors tracking-tight">
{article.title}
</h2>
</Link>
<p className="text-slate-400 text-sm leading-relaxed mb-8 line-clamp-3">
{article.excerpt}
</p>
<div className="mt-auto flex items-center justify-between">
<div className="flex items-center gap-3">
<img src={article.author.avatar} alt={article.author.name} className="h-10 w-10 rounded-full border border-white/10 grayscale hover:grayscale-0 transition-all" />
<div>
<p className="text-xs font-bold text-white">{article.author.name}</p>
<p className="text-[10px] text-slate-500 uppercase tracking-widest">Lead Strategist</p>
</div>
</div>
<div className="flex gap-2">
<button
onClick={() => toggleBookmark(article.id)}
className={cn(
"h-10 w-10 rounded-xl border flex items-center justify-center transition-all",
bookmarked ? "bg-brand border-brand text-white" : "border-white/10 text-white hover:border-brand hover:text-brand"
)}
>
<Bookmark size={16} fill={bookmarked ? "currentColor" : "none"} />
</button>
<Link to={`/artigo/${article.slug}`} className="h-10 w-12 bg-white/5 hover:bg-brand text-white hover:text-white rounded-xl flex items-center justify-center transition-all">
<ArrowRight size={18} />
</Link>
</div>
</div>
</div>
</motion.div>
);
}
return (
<motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="group relative h-full flex flex-col glass-card glass-card-hover p-4"
>
<div className="relative aspect-[16/10] bg-black rounded-xl overflow-hidden mb-6">
<Link to={`/artigo/${article.slug}`} className="block h-full">
<img
src={article.image}
alt={article.title}
className="h-full w-full object-cover transition-transform duration-700 group-hover:scale-110"
referrerPolicy="no-referrer"
/>
</Link>
<div className="absolute top-4 left-4">
<div className="px-2 py-1 bg-black/60 backdrop-blur-md rounded-lg text-white text-[10px] font-bold uppercase tracking-widest">{article.category}</div>
</div>
<button
onClick={() => toggleBookmark(article.id)}
className={cn(
"absolute top-4 right-4 h-8 w-8 rounded-lg border backdrop-blur-md flex items-center justify-center transition-all",
bookmarked ? "bg-brand border-brand text-white opacity-100" : "bg-black/60 border-white/10 text-white hover:bg-brand hover:text-white opacity-0 group-hover:opacity-100"
)}
>
<Bookmark size={14} fill={bookmarked ? "currentColor" : "none"} />
</button>
</div>
<div className="px-2 pb-4 flex flex-col flex-grow">
<div className="flex items-center gap-2 mb-4 text-brand">
<BarChart2 size={12} />
<span className="text-[10px] font-bold uppercase tracking-widest">Análise de Crescimento</span>
</div>
<Link to={`/artigo/${article.slug}`} className="flex-grow group/link">
<h3 className="text-xl font-bold text-white leading-tight mb-4 group-hover/link:text-brand transition-colors tracking-tight">
{article.title}
</h3>
<p className="text-slate-500 text-sm leading-relaxed line-clamp-2 mb-8">
{article.excerpt}
</p>
</Link>
<div className="mt-auto pt-6 border-t border-white/5 flex items-center justify-between">
<div className="flex items-center gap-3">
<img src={article.author.avatar} alt={article.author.name} className="h-8 w-8 rounded-full border border-white/10" />
<p className="text-xs font-bold text-slate-400">{article.author.name}</p>
</div>
<div className="flex items-center gap-2 text-slate-600 text-[10px] font-bold">
<Clock size={12} />
<span>{article.readTime}</span>
</div>
</div>
</div>
</motion.div>
);
}

View file

@ -0,0 +1,31 @@
import { Link } from 'react-router-dom';
import { Category } from '../types';
import { cn } from '../lib/utils';
import { CATEGORY_MAP } from '../constants';
interface CategoryBadgeProps {
category: Category;
variant?: 'solid' | 'outline' | 'glass';
className?: string;
}
export default function CategoryBadge({ category, variant = 'solid', className }: CategoryBadgeProps) {
const mapping = CATEGORY_MAP[category] || { name: category, slug: category.toLowerCase().replace(/\s+/g, '-') };
const baseStyles = "inline-flex items-center rounded-full px-3 py-1 text-[10px] font-bold uppercase tracking-wider transition-all hover:scale-105 active:scale-95";
const variants = {
solid: "bg-brand text-white hover:bg-brand-dark",
outline: "border border-brand/10 text-brand hover:bg-brand/5",
glass: "bg-white/80 backdrop-blur-sm text-slate-900 shadow-sm hover:bg-white"
};
return (
<Link
to={`/categoria/${mapping.slug}`}
className={cn(baseStyles, variants[variant], className)}
onClick={(e) => e.stopPropagation()}
>
{mapping.name}
</Link>
);
}

View file

@ -0,0 +1,91 @@
import { Link } from 'react-router-dom';
import { Twitter, Linkedin, Github, TrendingUp, ArrowRight } from 'lucide-react';
export default function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="bg-black border-t border-white/5 pt-24 pb-12 relative overflow-hidden">
<div className="section-container relative z-10">
<div className="grid grid-cols-1 md:grid-cols-12 gap-16 mb-20">
<div className="md:col-span-12 lg:col-span-4">
<Link to="/" className="flex items-center gap-2 mb-8 group">
<div className="h-8 w-8 bg-brand rounded-lg flex items-center justify-center text-white shadow-lg shadow-brand/20 group-hover:scale-110 transition-transform">
<TrendingUp size={18} />
</div>
<span className="font-bold text-xl tracking-tight text-white">
Apex<span className="text-brand">SEO</span>
</span>
</Link>
<p className="text-slate-500 text-sm leading-relaxed max-w-sm mb-8">
Engenharia de precisão para o horizonte da busca orgânica. Ajudamos empresas de elite e scale-ups a escalar receita através de autoridade técnica e arquitetura estratégica de conteúdo.
</p>
<div className="flex gap-4">
{[Twitter, Linkedin, Github].map((Icon, i) => (
<a
key={i}
href="#"
className="h-10 w-10 flex items-center justify-center rounded-xl border border-white/10 text-slate-400 hover:text-brand hover:border-brand/40 hover:bg-brand/5 transition-all"
>
<Icon size={18} />
</a>
))}
</div>
</div>
<div className="md:col-span-4 lg:col-span-2">
<h4 className="text-white text-xs font-bold uppercase tracking-widest mb-8">Plataforma</h4>
<ul className="space-y-4 text-xs font-medium text-slate-500">
<li><Link to="/#servicos" className="hover:text-brand transition-colors">Serviços</Link></li>
<li><Link to="/arquivo" className="hover:text-brand transition-colors">Estudos de Caso</Link></li>
<li><Link to="/metodologia" className="hover:text-brand transition-colors">Metodologia</Link></li>
<li><Link to="/arquivo" className="hover:text-brand transition-colors">Insights</Link></li>
</ul>
</div>
<div className="md:col-span-4 lg:col-span-2">
<h4 className="text-white text-xs font-bold uppercase tracking-widest mb-8">Empresa</h4>
<ul className="space-y-4 text-xs font-medium text-slate-500">
<li><Link to="/sobre" className="hover:text-brand transition-colors">Sobre Nós</Link></li>
<li><Link to="/contato" className="hover:text-brand transition-colors">Contato</Link></li>
<li><Link to="/privacidade" className="hover:text-brand transition-colors">Privacidade</Link></li>
<li><Link to="/termos" className="hover:text-brand transition-colors">Termos de Uso</Link></li>
</ul>
</div>
<div className="md:col-span-4 lg:col-span-4">
<div className="glass-card p-8">
<h5 className="text-white font-bold text-sm mb-4">Escale sua autoridade</h5>
<p className="text-slate-500 text-xs mb-6 leading-relaxed">
Receba nosso briefing técnico mensal sobre o estado da busca orgânica e algoritmos.
</p>
<form className="flex gap-2" onSubmit={(e) => e.preventDefault()}>
<input
type="email"
placeholder="Seu melhor e-mail"
className="flex-grow h-11 bg-black/50 border border-white/10 rounded-xl px-4 text-xs text-white focus:border-brand outline-none transition-all"
/>
<button className="h-11 px-6 bg-brand text-white font-bold text-xs rounded-xl hover:bg-brand-light transition-all flex items-center gap-2">
Assinar <ArrowRight size={14} />
</button>
</form>
</div>
</div>
</div>
<div className="pt-10 border-t border-white/5 flex flex-col md:flex-row justify-between items-center gap-6 text-[10px] font-bold uppercase tracking-widest text-slate-600">
<p>
© {currentYear} Apex SEO Agency. Todos os direitos reservados.
</p>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
<span>Sistema Operacional</span>
</div>
<span className="text-slate-800">Apex_Core_v4.2</span>
</div>
</div>
</div>
</footer>
);
}

View file

@ -0,0 +1,194 @@
import { Link } from 'react-router-dom';
import { Search, Menu, X, Globe, Bookmark, TrendingUp } from 'lucide-react';
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { useLanguage } from '../contexts/LanguageContext';
import { useBookmarks } from '../contexts/BookmarksContext';
import { cn } from '../lib/utils';
interface HeaderProps {
onSearchOpen: () => void;
}
export default function Header({ onSearchOpen }: HeaderProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const { lang, setLang } = useLanguage();
const [isLangOpen, setIsLangOpen] = useState(false);
const { bookmarks } = useBookmarks();
const languages = [
{ code: 'pt-br', label: 'PT', flag: 'BR' },
{ code: 'en', label: 'EN', flag: 'US' },
{ code: 'es', label: 'ES', flag: 'ES' }
] as const;
useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 20);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const navItems = [
{ name: 'Serviços', path: '/#servicos' },
{ name: 'Cases', path: '/arquivo' },
{ name: 'Sobre', path: '/sobre' },
{ name: 'Metodologia', path: '/metodologia' },
{ name: 'Insights', path: '/arquivo' }
];
return (
<header className={cn(
"fixed top-0 z-50 w-full transition-all duration-500",
scrolled ? "bg-black/80 backdrop-blur-xl border-b border-white/5 py-4" : "bg-transparent py-6"
)}>
<div className="section-container">
<div className="flex items-center justify-between">
<div className="flex items-center gap-12">
<Link to="/" className="flex items-center gap-2 group">
<div className="h-8 w-8 bg-brand rounded-lg flex items-center justify-center text-white shadow-lg shadow-brand/20 group-hover:scale-110 transition-transform">
<TrendingUp size={18} />
</div>
<span className="font-bold text-xl tracking-tight text-white">
Apex<span className="text-brand">SEO</span>
</span>
</Link>
<nav className="hidden lg:flex items-center gap-8">
{navItems.map((item) => (
<Link
key={item.name}
to={item.path}
className="text-sm font-medium text-slate-400 hover:text-white transition-colors py-2"
>
{item.name}
</Link>
))}
</nav>
</div>
<div className="flex items-center gap-2">
<div className="hidden sm:flex items-center gap-4 px-4 relative">
<button
onClick={() => setIsLangOpen(!isLangOpen)}
className="flex items-center gap-1.5 text-xs font-semibold text-slate-500 hover:text-white transition-colors py-2"
>
<Globe size={14} />
<span>{lang === 'pt-br' ? 'PT' : lang.toUpperCase()}</span>
</button>
<AnimatePresence>
{isLangOpen && (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute top-full right-0 mt-1 bg-slate-900 border border-white/10 rounded-lg p-1 min-w-[80px] shadow-xl"
>
{languages.map((l) => (
<button
key={l.code}
onClick={() => {
setLang(l.code);
setIsLangOpen(false);
}}
className={cn(
"w-full text-left px-3 py-1.5 rounded-md text-[10px] font-bold transition-colors",
lang === l.code ? "bg-brand text-white" : "text-slate-400 hover:bg-white/5 hover:text-white"
)}
>
{l.label}
</button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
<button
className="h-10 w-10 flex items-center justify-center text-white/70 hover:text-brand transition-colors"
onClick={onSearchOpen}
>
<Search size={20} />
</button>
<Link
to="/bookmarks"
className="relative h-10 w-10 flex items-center justify-center text-white/70 hover:text-brand transition-colors"
>
<Bookmark size={20} />
{bookmarks.length > 0 && (
<span className="absolute top-2 right-2 h-2 w-2 bg-brand rounded-full border border-black" />
)}
</Link>
<Link to="/contato" className="hidden md:flex btn-primary !h-10 !px-6 !text-xs ml-4 flex items-center justify-center">
Auditoria Grátis
</Link>
<button
className="lg:hidden h-10 w-10 flex items-center justify-center text-white ml-2"
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
</div>
</div>
{/* Mobile Menu */}
<AnimatePresence>
{isMenuOpen && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="lg:hidden absolute top-full left-0 w-full bg-black border-t border-white/5 py-8"
>
<div className="section-container flex flex-col gap-6">
{navItems.map((item) => (
<Link
key={item.name}
to={item.path}
className="text-2xl font-bold text-white hover:text-brand transition-colors"
onClick={() => setIsMenuOpen(false)}
>
{item.name}
</Link>
))}
<div className="h-px w-full bg-white/5 my-4" />
<div className="flex gap-4">
{languages.map((l) => (
<button
key={l.code}
onClick={() => {
setLang(l.code);
setIsMenuOpen(false);
}}
className={cn(
"flex-grow py-3 px-4 rounded-xl border text-sm font-bold transition-all",
lang === l.code
? "bg-brand border-brand text-white"
: "bg-white/[0.03] border-white/10 text-slate-400"
)}
>
{l.label}
</button>
))}
</div>
<Link
to="/contato"
className="btn-primary w-full mt-4 flex items-center justify-center h-12"
onClick={() => setIsMenuOpen(false)}
>
Solicitar Auditoria
</Link>
</div>
</motion.div>
)}
</AnimatePresence>
</header>
);
}

View file

@ -0,0 +1,53 @@
import { ReactNode, useEffect, useState } from 'react';
import Header from './Header';
import Footer from './Footer';
import LeadMagnet from './LeadMagnet';
import SearchOverlay from './SearchOverlay';
import { useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'motion/react';
interface LayoutProps {
children: ReactNode;
}
export default function Layout({ children }: LayoutProps) {
const { pathname, hash } = useLocation();
const [isSearchOpen, setIsSearchOpen] = useState(false);
// Scroll to top or to hash on route change
useEffect(() => {
if (hash) {
setTimeout(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 100);
} else {
window.scrollTo(0, 0);
}
}, [pathname, hash]);
return (
<div className="min-h-screen flex flex-col bg-[#020203] selection:bg-brand/10 selection:text-brand text-slate-300">
<Header onSearchOpen={() => setIsSearchOpen(true)} />
<SearchOverlay isOpen={isSearchOpen} onClose={() => setIsSearchOpen(false)} />
<main className="flex-grow">
<AnimatePresence mode="wait">
<motion.div
key={pathname}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
{children}
</motion.div>
</AnimatePresence>
</main>
<LeadMagnet />
<Footer />
</div>
);
}

View file

@ -0,0 +1,108 @@
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { X, Download, ShieldCheck, ArrowRight, Mail } from 'lucide-react';
export default function LeadMagnet() {
const [isOpen, setIsOpen] = useState(false);
const [dismissed, setDismissed] = useState(false);
useEffect(() => {
// Check if user already dismissed or converted
const hasSeen = localStorage.getItem('seo_lead_magnet_dismissed');
if (hasSeen) return;
const handleMouseLeave = (e: MouseEvent) => {
if (e.clientY <= 0 && !dismissed) {
setIsOpen(true);
}
};
const handleScroll = () => {
const scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
if (scrollPercent > 50 && !dismissed) {
setIsOpen(true);
}
};
document.addEventListener('mouseleave', handleMouseLeave);
window.addEventListener('scroll', handleScroll);
return () => {
document.removeEventListener('mouseleave', handleMouseLeave);
window.removeEventListener('scroll', handleScroll);
};
}, [dismissed]);
const handleClose = () => {
setIsOpen(false);
setDismissed(true);
localStorage.setItem('seo_lead_magnet_dismissed', 'true');
};
return (
<AnimatePresence>
{isOpen && (
<div className="fixed inset-0 z-[200] flex items-center justify-center p-6 bg-slate-900/60 backdrop-blur-sm">
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="bg-[#08080a] border border-white/10 shadow-2xl max-w-2xl w-full overflow-hidden flex flex-col relative"
>
<button
onClick={handleClose}
className="absolute top-4 right-4 h-8 w-8 bg-white/5 hover:bg-brand hover:text-black text-slate-500 flex items-center justify-center z-20 transition-all"
aria-label="Fechar"
>
<X size={16} />
</button>
<div className="p-8 md:p-12">
<div className="flex items-center gap-3 mb-6">
<div className="h-10 w-10 bg-brand/10 flex items-center justify-center text-brand skew-x-[-6deg]">
<ShieldCheck size={20} className="skew-x-[6deg]" />
</div>
<span className="text-[9px] font-mono font-black text-brand uppercase tracking-[0.4em]">Resource_Found</span>
</div>
<h2 className="text-3xl sm:text-4xl font-display font-black text-white italic tracking-tight leading-none mb-6">
Dominate the<br /><span className="text-brand">Neural_Index 2026.</span>
</h2>
<p className="text-slate-500 font-mono text-[11px] leading-relaxed uppercase tracking-widest mb-10 max-w-lg">
Download our exclusive blueprint for technical systems and semantic authority protocols.
</p>
<form className="space-y-4" onSubmit={(e) => { e.preventDefault(); handleClose(); }}>
<div className="relative">
<Mail className="absolute left-0 top-1/2 -translate-y-1/2 text-brand" size={16} />
<input
type="email"
placeholder="ENTER_ENDPOINT_ID..."
required
className="w-full h-12 pl-8 border-b border-white/10 bg-transparent text-white placeholder:text-slate-800 focus:outline-none focus:border-brand transition-all font-mono text-xs uppercase tracking-widest"
/>
</div>
<button
type="submit"
className="btn-primary w-full"
>
Sync_Now <Download size={16} />
</button>
</form>
<div className="mt-8 pt-8 border-t border-white/5 flex flex-wrap gap-x-8 gap-y-4">
<div className="flex items-center gap-2 text-[8px] font-black text-slate-700 uppercase tracking-widest">
<ArrowRight size={10} className="text-brand" /> Interactive Protocol
</div>
<div className="flex items-center gap-2 text-[8px] font-black text-slate-700 uppercase tracking-widest">
<ArrowRight size={10} className="text-brand" /> Audit Framework
</div>
</div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
);
}

View file

@ -0,0 +1,62 @@
import { Helmet } from 'react-helmet-async';
interface SEOProps {
title: string;
description: string;
image?: string;
url?: string;
type?: string;
keywords?: string[];
articleData?: {
publishedTime?: string;
author?: string;
authorAvatar?: string;
tags?: string[];
section?: string;
};
}
export default function SEO({
title,
description,
image,
url = 'https://apexseo.agency',
type = 'website',
keywords,
articleData
}: SEOProps) {
const siteTitle = `${title} | Apex SEO Agency`;
const metaImage = image || 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1200&h=630&fit=crop';
return (
<Helmet>
{/* Base */}
<title>{siteTitle}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
{/* Open Graph / Facebook */}
<meta property="og:type" content={type} />
<meta property="og:url" content={url} />
<meta property="og:title" content={siteTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={metaImage} />
{articleData?.publishedTime && <meta property="article:published_time" content={articleData.publishedTime} />}
{articleData?.author && <meta property="article:author" content={articleData.author} />}
{articleData?.section && <meta property="article:section" content={articleData.section} />}
{articleData?.tags && articleData.tags.map(tag => <meta key={tag} property="article:tag" content={tag} />)}
{/* Twitter */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content={url} />
<meta name="twitter:title" content={siteTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={metaImage} />
{keywords && <meta name="keywords" content={keywords.join(', ')} />}
{/* Theme */}
<meta name="theme-color" content="#030303" />
</Helmet>
);
}

View file

@ -0,0 +1,133 @@
import { motion, AnimatePresence } from 'motion/react';
import { X, Search as SearchIcon, ArrowRight, TrendingUp } from 'lucide-react';
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { articles } from '../data/articles';
import { Article } from '../types';
interface SearchOverlayProps {
isOpen: boolean;
onClose: () => void;
}
export default function SearchOverlay({ isOpen, onClose }: SearchOverlayProps) {
const [query, setQuery] = useState('');
const [results, setResults] = useState<Article[]>([]);
useEffect(() => {
if (query.trim().length > 1) {
const filtered = articles.filter(a =>
a.title.toLowerCase().includes(query.toLowerCase()) ||
a.category.toLowerCase().includes(query.toLowerCase()) ||
a.excerpt.toLowerCase().includes(query.toLowerCase())
).slice(0, 6);
setResults(filtered);
} else {
setResults([]);
}
}, [query]);
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleEsc);
return () => window.removeEventListener('keydown', handleEsc);
}, [onClose]);
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
return () => {
document.body.style.overflow = 'unset';
};
}, [isOpen]);
if (!isOpen) return null;
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[9999] bg-black/95 backdrop-blur-2xl flex flex-col pt-32"
>
<div className="section-container max-w-4xl relative z-10">
<div className="flex items-center justify-between mb-8">
<h2 className="text-xl font-bold text-white flex items-center gap-3">
<div className="h-8 w-8 bg-brand rounded-lg flex items-center justify-center text-white">
<TrendingUp size={16} />
</div>
Buscar Insights
</h2>
<button
onClick={onClose}
className="h-10 w-10 flex items-center justify-center text-slate-500 hover:text-white rounded-full bg-white/5 transition-all"
>
<X size={20} />
</button>
</div>
<div className="relative mb-16 group">
<SearchIcon size={24} className="absolute left-6 top-1/2 -translate-y-1/2 text-slate-600 group-focus-within:text-brand transition-colors" />
<input
autoFocus
type="text"
placeholder="Buscar cases, protocolos, insights..."
className="w-full h-18 bg-white/[0.03] border border-white/10 focus:border-brand rounded-2xl px-16 text-white font-medium outline-none transition-all placeholder:text-slate-700"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</div>
<div className="grid grid-cols-1 gap-3 max-h-[60vh] overflow-y-auto pr-4 custom-scrollbar">
{results.length > 0 ? (
results.map((article) => (
<Link
key={article.id}
to={`/artigo/${article.slug}`}
onClick={onClose}
className="block p-6 glass-card glass-card-hover group"
>
<div className="flex justify-between items-center">
<div>
<div className="flex items-center gap-3 mb-2">
<span className="text-[10px] font-bold text-brand uppercase tracking-widest">{article.category}</span>
<div className="h-1 w-1 rounded-full bg-white/10" />
<span className="text-[10px] text-slate-600 font-bold uppercase">{article.readTime}</span>
</div>
<h3 className="text-lg font-bold text-white group-hover:text-brand transition-colors leading-tight">{article.title}</h3>
</div>
<div className="h-10 w-10 rounded-full border border-white/5 flex items-center justify-center text-slate-600 group-hover:text-brand group-hover:border-brand/40 transition-all">
<ArrowRight size={18} />
</div>
</div>
</Link>
))
) : query.trim() ? (
<div className="text-center py-20 p-8 glass-card">
<p className="text-slate-500 font-medium">Nenhum resultado encontrado para "{query}"</p>
</div>
) : (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
{['SEO', 'Técnico', 'Conteúdo', 'Crescimento'].map((tag) => (
<button
key={tag}
onClick={() => setQuery(tag)}
className="p-4 glass-card text-slate-500 font-bold text-xs uppercase tracking-widest hover:border-brand hover:text-brand transition-all text-center"
>
{tag}
</button>
))}
</div>
)}
</div>
</div>
</motion.div>
</AnimatePresence>
);
}

View file

@ -0,0 +1,107 @@
import { useEffect, useState } from 'react';
import { cn } from '../lib/utils';
import { List } from 'lucide-react';
import { motion } from 'motion/react';
interface TOCItem {
id: string;
text: string;
level: number;
}
export default function TableOfContents({ content }: { content: string }) {
const [activeId, setActiveId] = useState<string>('');
const [headings, setHeadings] = useState<TOCItem[]>([]);
useEffect(() => {
// Extract headings (H2 and H3 only for clarity)
const headingLines = content.split('\n').filter(line =>
line.startsWith('## ') || line.startsWith('### ')
);
const extractedHeadings = headingLines.map(line => {
const level = line.startsWith('###') ? 3 : 2;
const text = line.replace(/^#+\s+/, '').trim();
const id = text.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s+/g, '-')
.replace(/[^\w-]/g, '');
return { id, text, level };
});
setHeadings(extractedHeadings);
// Initial check (delay slightly for DOM to be ready)
setTimeout(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveId(entry.target.id);
}
});
},
{ rootMargin: '-10% 0% -80% 0%' }
);
extractedHeadings.forEach((h) => {
const el = document.getElementById(h.id);
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, 500);
}, [content]);
if (headings.length === 0) return null;
return (
<div className="h-fit">
<div className="mb-10 flex items-center justify-between">
<h5 className="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em] flex items-center gap-2">
<div className="h-1 w-4 bg-brand" />
Roteiro
</h5>
<span className="text-[9px] font-bold text-slate-200">
{headings.length} SEÇÕES
</span>
</div>
<nav className="flex flex-col">
{headings.map((h, i) => (
<a
key={h.id}
href={`#${h.id}`}
onClick={(e) => {
e.preventDefault();
document.getElementById(h.id)?.scrollIntoView({ behavior: 'smooth' });
}}
className={cn(
"group py-3 border-b border-slate-50 flex items-baseline gap-4 transition-all relative",
activeId === h.id
? "text-brand"
: "text-slate-400 hover:text-slate-900",
h.level === 3 && "pl-8"
)}
>
<span className={cn(
"text-[9px] font-mono transition-colors",
activeId === h.id ? "text-brand" : "text-slate-200 group-hover:text-slate-400"
)}>
{i + 1 < 10 ? `0${i + 1}` : i + 1}
</span>
<span className="text-[11px] font-bold leading-tight">
{h.text}
</span>
{activeId === h.id && (
<motion.div
layoutId="active-toc"
className="absolute left-0 top-0 bottom-0 w-[2px] bg-brand"
/>
)}
</a>
))}
</nav>
</div>
);
}

View file

@ -0,0 +1,126 @@
import { Category } from './types';
export const CATEGORY_MAP: Record<Category, { name: string; slug: string }> = {
'Estratégia': { name: 'Estratégia', slug: 'estrategia' },
'Técnico': { name: 'Técnico', slug: 'tecnico' },
'Autoridade': { name: 'Autoridade', slug: 'autoridade' },
'Negócios': { name: 'Negócios', slug: 'negocios' },
'SEO para Iniciantes': { name: 'SEO para Iniciantes', slug: 'iniciantes' },
'SEO Técnico': { name: 'SEO Técnico', slug: 'seo-tecnico' },
'SEO On Page': { name: 'SEO On Page', slug: 'on-page' },
'SEO Off Page': { name: 'SEO Off Page', slug: 'off-page' },
'Link Building': { name: 'Link Building', slug: 'link-building' },
'Ferramentas de SEO': { name: 'Ferramentas de SEO', slug: 'ferramentas' },
'SEO para E-commerce': { name: 'SEO para E-commerce', slug: 'e-commerce' },
'SEO para Blogs': { name: 'SEO para Blogs', slug: 'blogs' },
'Atualizações do Google': { name: 'Atualizações do Google', slug: 'google-updates' }
};
export const getCategoryBySlug = (slug: string) => {
return Object.entries(CATEGORY_MAP).find(([_, value]) => value.slug === slug);
};
export const translations: Record<string, any> = {
'pt-br': {
newsletter: 'Newsletter',
subscribe: 'Assinar Agora',
readMore: 'Ler artigo',
latest: 'Últimas',
allArticles: 'Todos os Artigos',
sections: 'Seções',
publishing: 'Publicação',
contact: 'Contato',
about: 'Sobre',
privacy: 'Privacidade',
terms: 'Termos',
featured: 'Destaque',
recent: 'Recentes',
explore: 'Explorar Insights',
search: 'Buscar',
viewAll: 'Ver Todos',
manifesto: 'Manifesto',
methodology: 'Metodologia',
heroTitle: 'A Nova Ordem da Busca Semântica.',
heroSubtitle: 'EDITORIAL DE SEO 2026',
heroDescription: 'O recurso definitivo para profissionais que buscam excelência técnica e autoridade duradoura no ecossistema de busca.',
readManifesto: 'Ler Manifesto',
publishedIn: 'Publicado em',
newsletterTitle: 'Newsletter Semanal',
newsletterDescription: 'Inscreva-se para receber as últimas novidades.',
emailPlaceholder: 'seu@email.com',
subscribeButton: 'Inscrever',
trendsTitle: 'TENDÊNCIAS',
footerNewsletterTitle: 'Entre no Círculo de Autoridade',
footerNewsletterSubtitle: 'Assine para receber análises profundas sobre mudanças algorítmicas, IA e estratégias de busca que o resto do mercado ainda não viu.',
footerNewsletterInput: 'seu@email.com',
footerNewsletterDisclaimer: 'Nós respeitamos sua caixa de entrada. Zero spam, apenas inteligência de busca.'
},
'en': {
newsletter: 'Newsletter',
subscribe: 'Subscribe Now',
readMore: 'Read more',
latest: 'Latest',
allArticles: 'All Articles',
sections: 'Sections',
publishing: 'Publishing',
contact: 'Contact',
about: 'About',
privacy: 'Privacy',
terms: 'Terms',
featured: 'Featured',
recent: 'Recent',
explore: 'Explore Insights',
search: 'Search',
viewAll: 'View All',
manifesto: 'Manifesto',
methodology: 'Methodology',
heroTitle: 'The New Order of Semantic Search.',
heroSubtitle: 'SEO EDITORIAL 2026',
heroDescription: 'The definitive resource for professionals seeking technical excellence and lasting authority in the search ecosystem.',
readManifesto: 'Read Manifesto',
publishedIn: 'Published in',
newsletterTitle: 'Weekly Newsletter',
newsletterDescription: 'Subscribe to get the latest news.',
emailPlaceholder: 'your@email.com',
subscribeButton: 'Subscribe',
trendsTitle: 'TRENDS',
footerNewsletterTitle: 'Join the Circle of Authority',
footerNewsletterSubtitle: 'Subscribe to receive deep insights into algorithmic changes, AI, and search strategies that the rest of the market hasn\'t seen yet.',
footerNewsletterInput: 'your@email.com',
footerNewsletterDisclaimer: 'We respect your inbox. Zero spam, only search intelligence.'
},
'es': {
newsletter: 'Boletín',
subscribe: 'Suscríbete Ahora',
readMore: 'Leer más',
latest: 'Últimos',
allArticles: 'Todos los Artículos',
sections: 'Secciones',
publishing: 'Publicación',
contact: 'Contacto',
about: 'Sobre',
privacy: 'Privacidad',
terms: 'Términos',
featured: 'Destacado',
recent: 'Recientes',
explore: 'Explorar Insights',
search: 'Buscar',
viewAll: 'Ver Todo',
manifesto: 'Manifiesto',
methodology: 'Metodología',
heroTitle: 'El Nuevo Orden de la Búsqueda Semántica.',
heroSubtitle: 'EDITORIAL SEO 2026',
heroDescription: 'El recurso definitivo para profesionales que buscan excelencia técnica y autoridad duradera en el ecosistema de búsqueda.',
readManifesto: 'Leer Manifiesto',
publishedIn: 'Publicado en',
newsletterTitle: 'Boletín Semanal',
newsletterDescription: 'Suscríbete para recibir las últimas noticias.',
emailPlaceholder: 'tu@email.com',
subscribeButton: 'Suscríbete',
trendsTitle: 'TENDENCIAS',
footerNewsletterTitle: 'Únete al Círculo de Autoridad',
footerNewsletterSubtitle: 'Suscríbete para recibir análisis profundos sobre cambios algorítmicos, IA y estrategias de búsqueda que el resto del mercado aún no ha visto.',
footerNewsletterInput: 'tu@email.com',
footerNewsletterDisclaimer: 'Respetamos tu bandeja de entrada. Cero spam, solo inteligencia de búsqueda.'
}
};

View file

@ -0,0 +1,52 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
interface BookmarksContextType {
bookmarks: string[];
toggleBookmark: (id: string) => void;
isBookmarked: (id: string) => boolean;
}
const BookmarksContext = createContext<BookmarksContextType | undefined>(undefined);
export function BookmarksProvider({ children }: { children: React.ReactNode }) {
const [bookmarks, setBookmarks] = useState<string[]>([]);
// Load from localStorage on mount
useEffect(() => {
const saved = localStorage.getItem('seo_bookmarks');
if (saved) {
try {
setBookmarks(JSON.parse(saved));
} catch (e) {
console.error('Failed to parse bookmarks', e);
}
}
}, []);
// Sync with localStorage
const toggleBookmark = (id: string) => {
setBookmarks(prev => {
const next = prev.includes(id)
? prev.filter(item => item !== id)
: [...prev, id];
localStorage.setItem('seo_bookmarks', JSON.stringify(next));
return next;
});
};
const isBookmarked = (id: string) => bookmarks.includes(id);
return (
<BookmarksContext.Provider value={{ bookmarks, toggleBookmark, isBookmarked }}>
{children}
</BookmarksContext.Provider>
);
}
export function useBookmarks() {
const context = useContext(BookmarksContext);
if (context === undefined) {
throw new Error('useBookmarks must be used within a BookmarksProvider');
}
return context;
}

View file

@ -0,0 +1,64 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
export type Language = 'pt-br' | 'en' | 'es';
interface LanguageContextType {
lang: Language;
setLang: (lang: Language) => void;
translate: (text: any, context?: string, isObject?: boolean) => Promise<any>;
isTranslating: boolean;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [lang, setLang] = useState<Language>(() => {
const saved = localStorage.getItem('app-lang');
return (saved as Language) || 'pt-br';
});
useEffect(() => {
localStorage.setItem('app-lang', lang);
document.documentElement.lang = lang;
// Trigger Google Translate Widget
const triggerGoogleTranslate = () => {
const select = document.querySelector('.goog-te-combo') as HTMLSelectElement;
if (select) {
const googleLangCode = lang === 'pt-br' ? 'pt' : lang;
if (select.value !== googleLangCode) {
select.value = googleLangCode;
select.dispatchEvent(new Event('change'));
}
return true;
}
return false;
};
// Try multiple times as the widget loads asynchronously
let attempts = 0;
const interval = setInterval(() => {
attempts++;
const success = triggerGoogleTranslate();
if (success || attempts > 10) {
clearInterval(interval);
}
}, 1000);
return () => clearInterval(interval);
}, [lang]);
return (
<LanguageContext.Provider value={{ lang, setLang, translate: async (t) => t, isTranslating: false }}>
{children}
</LanguageContext.Provider>
);
};
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (context === undefined) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};

View file

@ -0,0 +1,654 @@
import { Article } from '../types';
export const articles: Article[] = [
{
id: '1',
slug: 'o-impacto-da-ia-generativa-na-busca-organica-2026',
title: 'O Impacto da IA Generativa na Busca Orgânica em 2026',
excerpt: 'Como as Search Generative Experiences (SGE) estão moldando o comportamento do usuário e o que isso significa para o CTR.',
category: 'Estratégia',
publishedAt: '2026-05-01',
image: 'https://images.unsplash.com/photo-1673191898498-9bac443b2407?auto=format&fit=crop&q=80&w=1600',
readTime: '12 min',
author: {
name: 'Dr. Lucas Silva',
role: 'Lead Strategist',
avatar: 'https://i.pravatar.cc/150?u=lucas'
},
metaTitle: 'IA Generativa e SEO em 2026 | Análise Técnica',
metaDescription: "SEO em 2026: Entenda como a IA Generativa e o SGE transformaram o tráfego orgânico. Conheça as táticas de 'Conteúdo de Valor Incrementado' para vencer o Zero-Click.",
tags: ['IA', 'SGE', 'Algoritmo', 'Google'],
lang: 'pt-br',
content: `
# O Impacto da IA Generativa na Busca Orgânica em 2026
A busca não é mais o que costumava ser. Em 2026, a integração total da IA nas páginas de resultados (SERPs) criou um novo paradigma: a busca não apenas por links, mas por respostas diretas e inteligíveis.
## O Desafio do Zero-Click
Com a IA resolvendo dúvidas complexas diretamente no topo da busca, o volume de cliques para sites informativos superficiais despencou. A estratégia vencedora agora foca em **Conteúdo de Valor Incrementado** informações que a IA ainda não consegue sintetizar sem falhas.
### O que são Insights de Propriedade?
Insights de propriedade são dados, opiniões de especialistas e experiências reais que não existem em Large Language Models (LLMs). É a sua "fórmula secreta" que obriga o usuário a clicar para entender a profundidade do assunto.
## Estratégias de Sobrevivência
1. **Foco na Cauda Longa Hiper-Específica:** Consultas onde a opinião humana é necessária.
2. **Autoridade de Tópico (Topic Clusters):** Ser o dono do assunto, não apenas da palavra-chave.
3. **Optimizing for SGE Sources:** Estruturar dados para que seu site seja a fonte citada pela IA.
## Conclusão
A IA não matou o SEO; ela elevou o nível do jogo. Quem produz conteúdo medíocre está fora. Quem produz autoridade técnica está mais forte do que nunca.
`
},
{
id: '2',
slug: 'eeat-a-biblia-da-autoridade-digital',
title: 'E-E-A-T: A Bíblia da Autoridade Digital Moderna',
excerpt: 'Análise profunda sobre Experiência, Especialidade, Autoridade e Confiança no novo algoritmo.',
category: 'Estratégia',
publishedAt: '2026-04-28',
image: 'https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&q=80&w=1600',
readTime: '15 min',
author: {
name: 'Beatriz Costa',
role: 'Head of Content',
avatar: 'https://i.pravatar.cc/150?u=beatriz'
},
metaTitle: 'Guia Definitivo E-E-A-T para SEO em 2026',
metaDescription: "Domine o E-E-A-T em 2026. Saiba por que a experiência humana é o diferencial contra a IA e como construir autoridade real para dominar os rankings do Google.",
tags: ['EEAT', 'Autoridade', 'Confiança', 'Especialidade'],
lang: 'pt-br',
content: `
# E-E-A-T: A Bíblia da Autoridade Digital Moderna
Se você quer rankear em 2026, você precisa entender o E-E-A-T. Não é um fator de ranking direto, mas uma diretriz que permeia todo o algoritmo do Google.
## O "E" de Experiência
Adicionado alguns anos, o fator experiência tornou-se crucial. O Google quer saber: você realmente usou este produto? Você realmente viveu essa situação?
<blockquote>
"A experiência humana é o único dado que a IA não consegue alucinar com perfeição em 2026."
</blockquote>
## Como demonstrar experiência?
- Use fotos originais, não apenas de bancos de imagem.
- Narre estudos de caso reais.
- Evite generalismos. Use "Eu descobri que..." em vez de "É geralmente aceito que...".
## Especialidade e Autoridade
Estes pilares são construídos através da consistência. Não adianta falar de SEO um dia e de culinária no outro se você quer ser visto como um especialista em tecnologia.
`
},
{
id: '3',
slug: 'auditoria-tecnica-seo-infraestrutura-performance',
title: 'Auditoria Técnica: Garantindo uma Infraestrutura de Elite',
excerpt: 'O guia definitivo para Core Web Vitals, Rendering e Crawl Budget em sites de larga escala.',
category: 'Técnico',
publishedAt: '2026-04-25',
image: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&q=80&w=1600',
readTime: '18 min',
author: {
name: 'Marcos Tech',
role: 'SEO Engineer',
avatar: 'https://i.pravatar.cc/150?u=marcos'
},
metaTitle: 'Auditoria Técnica de SEO 2026 | Guia para Desenvolvedores',
metaDescription: "Guia definitivo de Auditoria Técnica SEO 2026. Otimize INP, LCP e Rendering para garantir uma infraestrutura de elite e performance máxima em sites de larga escala.",
tags: ['Technical SEO', 'Performance', 'Web Vitals', 'Infrastructure'],
lang: 'pt-br',
content: `
# Auditoria Técnica: Garantindo uma Infraestrutura de Elite
Em 2026, a performance técnica não é um diferencial é o requisito mínimo para entrar na arena.
## O novo Core Web Vital: Interaction to Next Paint (INP)
O INP substituiu oficialmente o FID como a métrica de interatividade. Ele mede quão rápido seu site responde a qualquer interação do usuário.
### Otimizando para INP
- Reduza o tempo de execução do JavaScript principal.
- Use Web Workers para tarefas pesadas.
- Evite layouts complexos que causem reflow excessivo.
## Indexação e Crawl Budget
Para sites com milhares de páginas, cada segundo do bot do Google no seu servidor conta.
- Use Server-Side Rendering (SSR) de forma inteligente.
- Mantenha um Sitemap dinâmico e limpo.
- Monitore erros de 404 e cadeias de redirect.
`
},
{
id: '4',
slug: 'link-building-atraves-de-dados-proprietarios',
title: 'Link Building através de Dados Proprietários',
excerpt: 'Esqueça o outreach em massa. Aprenda a criar pesquisas que atraem links de forma orgânica.',
category: 'Autoridade',
publishedAt: '2026-04-20',
image: 'https://images.unsplash.com/photo-1557838923-2985c318be48?auto=format&fit=crop&q=80&w=1600',
readTime: '10 min',
author: {
name: 'João Links',
role: 'PR Specialist',
avatar: 'https://i.pravatar.cc/150?u=joao'
},
metaTitle: 'Estratégias de Link Building com Dados | SEO Authority',
metaDescription: "Transforme dados em imãs de links. Aprenda a criar pesquisas proprietárias e reports de mercado que geram backlinks editoriais espontâneos de alta autoridade.",
tags: ['Link Building', 'Backlinks', 'Content Marketing', 'PR'],
lang: 'pt-br',
content: `
# Link Building através de Dados Proprietários
O link building clássico de "troca de favores" está cada vez menos eficiente. O Google valoriza links editoriais espontâneos. A melhor forma de conseguir isso? Ser a fonte da notícia.
## Criando Imãs de Links
Quando você produz um infográfico com dados originais do seu setor, outros blogs e portais de notícias vão citar você como referência.
### Tipos de Conteúdo que Atraem Links:
1. **Pesquisas de Mercado:** Tendências para o próximo ano.
2. **Calculadoras e Ferramentas:** Soluções para problemas práticos.
3. **Análises de Impacto:** Como uma mudança econômica afetou seu nicho.
## A Importância do Texto Âncora
Links com âncoras variadas e naturais para páginas de autoridade (não apenas para a home) são o sinal mais forte de um perfil de links saudável.
`
},
{
id: '5',
slug: 'arquitetura-de-informacao-pillar-pages-e-clusters',
title: 'Arquitetura de Informação: Pillar Pages e Topic Clusters',
excerpt: 'Como estruturar seu blog para que o Google entenda sua autoridade em cada pilar de conteúdo.',
category: 'Estratégia',
publishedAt: '2026-04-15',
image: 'https://images.unsplash.com/photo-1542744094-3a31f272c490?auto=format&fit=crop&q=80&w=1600',
readTime: '14 min',
author: {
name: 'Beatriz Costa',
role: 'Head of Content',
avatar: 'https://i.pravatar.cc/150?u=beatriz'
},
metaTitle: 'Arquitetura de Conteúdo: Pillar Pages e Clusters em 2026',
metaDescription: "Topic Clusters e Pillar Pages: Descubra como estruturar seu site para dominar a autoridade semântica em 2026. O guia completo para organizar conteúdo estrategicamente.",
tags: ['Architecture', 'Content Strategy', 'Topic Clusters'],
lang: 'pt-br',
content: `
# Arquitetura de Informação: Pillar Pages e Topic Clusters
Organizar o conteúdo do seu blog é tão importante quanto escrevê-lo. Uma estrutura de **Topic Clusters** ajuda o Google a mapear sua expertise.
## O Que é uma Pillar Page?
É um guia abrangente sobre um tema central. Por exemplo, "SEO para Iniciantes".
## O Que são Clusters?
São artigos específicos que aprofundam tópicos mencionados na Pillar Page. Por exemplo, "O que é Robots.txt?".
### O Link Mágico
Todos os artigos satélites (clusters) devem apontar de volta para a Pillar Page. Isso cria uma teia de autoridade.
`
},
{
id: '6',
slug: 'seo-para-e-commerce-otimizando-paginas-de-categoria',
title: 'SEO para E-commerce: O Segredo das Páginas de Categoria',
excerpt: 'Por que as páginas de categoria trazem mais tráfego que as páginas de produto e como otimizá-las.',
category: 'Negócios',
publishedAt: '2026-04-10',
image: 'https://images.unsplash.com/photo-1556742049-045a728bbd66?auto=format&fit=crop&q=80&w=1600',
readTime: '11 min',
author: {
name: 'Ricardo Vendas',
role: 'E-commerce Expert',
avatar: 'https://i.pravatar.cc/150?u=ricardo'
},
metaTitle: 'SEO para E-commerce: Guias de Categoria em 2026',
metaDescription: "SEO para E-commerce: Por que as categorias são sua maior fonte de tráfego? Aprenda táticas avançadas para otimizar páginas de categoria e converter mais no Google.",
tags: ['E-commerce', 'Category Pages', 'Product SEO'],
lang: 'pt-br',
content: `
# SEO para E-commerce: O Segredo das Páginas de Categoria
Muitos donos de loja focam nas páginas de produto. Mas o volume real de busca está nas **Categorias**.
## Por que focar em Categorias?
Um usuário que busca "tênis de corrida" está em uma fase de consideração muito mais valiosa do que quem busca o modelo específico "Nike Pegasus 41 Azul".
### Otimização On-Page para Categorias:
- **Texto de Apoio:** Não enterre o texto no final da página. Use introduções úteis.
- **Interlinkagem:** Linke para seus produtos mais populares diretamente da categoria.
`
},
{
id: '7',
slug: 'schema-markup-avancado-e-dados-estruturados',
title: 'Schema Markup Avançado: Indo além do básico',
excerpt: 'Utilizando JSON-LD para fornecer contexto profundo aos mecanismos de busca.',
category: 'Técnico',
publishedAt: '2026-04-05',
image: 'https://images.unsplash.com/photo-1555066931-4365d14bab8c?auto=format&fit=crop&q=80&w=1600',
readTime: '13 min',
author: {
name: 'Marcos Tech',
role: 'SEO Engineer',
avatar: 'https://i.pravatar.cc/150?u=marcos'
},
metaTitle: 'Schema Markup Avançado JSON-LD | Guia Técnico 2026',
metaDescription: "Schema Markup 2026: Vá além do básico com JSON-LD. Aprenda a fornecer contexto profundo à IA do Google e conquistar Rich Snippets poderosos para seu site.",
tags: ['Schema', 'Structured Data', 'JSON-LD', 'Rich Snippets'],
lang: 'pt-br',
content: `
# Schema Markup Avançado: Indo além do básico
Dados estruturados são o vocabulário que o Google usa para entender o seu conteúdo. Em 2026, ignorar o Schema é como falar uma língua diferente do buscador.
## O Poder do JSON-LD
Prefira sempre a implementação via JSON-LD no cabeçalho ou rodapé do HTML.
### Esquemas Indispensáveis:
- **Article/BlogPosting:** Para conteúdo editorial.
- **FAQPage:** Para ocupar mais espaço na SERP.
- **Person/Organization:** Para reforçar o E-E-A-T do autor.
`
},
{
id: '8',
slug: 'seo-off-page-alem-dos-backlinks',
title: 'SEO Off-Page: A Autoridade além dos Backlinks',
excerpt: 'Menções de marca, sinais sociais e a construção de reputação no ambiente digital.',
category: 'Autoridade',
publishedAt: '2026-04-01',
image: 'https://images.unsplash.com/photo-1557838923-2985c318be48?auto=format&fit=crop&q=80&w=1600',
readTime: '12 min',
author: {
name: 'João Links',
role: 'PR Specialist',
avatar: 'https://i.pravatar.cc/150?u=joao'
},
metaTitle: 'SEO Off-Page e Reputação de Marca em 2026',
metaDescription: "SEO Off-Page em 2026: Autoridade é mais que backlinks. Entenda o peso das menções de marca, sinais sociais e co-ocorrência na reputação digital moderna.",
tags: ['Off Page', 'Reputation', 'Branding', 'Signals'],
lang: 'pt-br',
content: `
# SEO Off-Page: A Autoridade além dos Backlinks
O Google está cada vez melhor em entender menções de marca, mesmo quando elas não vêm acompanhadas de um link (Unlinked Brand Mentions).
## Sinais de Co-ocorrência
Se o nome da sua marca aparece frequentemente perto de palavras-chave como "melhor agência de SEO", o Google começa a associar sua entidade a esse tópico.
`
},
{
id: '9',
slug: 'psicologia-de-busca-entendendo-a-intencao',
title: 'Psicologia de Busca: Entendendo a Intenção Real',
excerpt: 'Por que o volume de busca é uma métrica vaidosa e como focar no que realmente converte.',
category: 'Estratégia',
publishedAt: '2026-03-25',
image: 'https://images.unsplash.com/photo-1518349619113-03114f06ac3a?auto=format&fit=crop&q=80&w=1600',
readTime: '14 min',
author: {
name: 'Dr. Lucas Silva',
role: 'Lead Strategist',
avatar: 'https://i.pravatar.cc/150?u=lucas'
},
metaTitle: 'Psicologia e Intenção de Busca em SEO (2026)',
metaDescription: "Pare de perseguir volume e entenda a Intenção de Busca. Guia de Psicologia de Busca para mapear a jornada do usuário e converter tráfego em resultados reais.",
tags: ['Psychology', 'User Intent', 'Keywords', 'Funnel'],
lang: 'pt-br',
content: `
# Psicologia de Busca: Entendendo a Intenção Real
Nem toda busca é criada igual. O usuário pode estar procurando informação, navegando para um site específico ou pronto para comprar.
## Otimizando para a Intenção
Se o seu artigo é um guia ensinando "como fazer", mas tenta vender um produto agressivamente no primeiro parágrafo, você está quebrando a intenção do usuário.
`
},
{
id: '10',
slug: 'otimizacao-para-busca-por-voz-e-assistentes',
title: 'Otimização para Busca por Voz e Assistentes em 2026',
excerpt: 'Como estruturar seu conteúdo para ser a resposta única da Alexa, Siri e Google Assistant.',
category: 'Técnico',
publishedAt: '2026-03-20',
image: 'https://images.unsplash.com/photo-1589254065878-42c9da997008?auto=format&fit=crop&q=80&w=1600',
readTime: '9 min',
author: {
name: 'Marcos Tech',
role: 'SEO Engineer',
avatar: 'https://i.pravatar.cc/150?u=marcos'
},
metaTitle: 'SEO para Busca por Voz 2026 | Dicas Práticas',
metaDescription: "SEO para Busca por Voz: Como ser a resposta única da Alexa, Siri e Google Assistant em 2026. Otimize seu conteúdo para NLP e linguagem natural hoje.",
tags: ['Voice Search', 'NLP', 'Conversational SEO'],
lang: 'pt-br',
content: `
# Otimização para Busca por Voz e Assistentes em 2026
Com o avanço do Processamento de Linguagem Natural (NLP), a busca por voz tornou-se extremamente precisa.
## Linguagem Natural
As pessoas não falam como escrevem. Em vez de "melhor SEO curso", elas perguntam "Ei Google, qual é o melhor curso de SEO disponível hoje?".
`
},
{
id: '11',
slug: 'seo-local-para-profissionais-liberais',
title: 'SEO Local: Dominando sua Região em 2026',
excerpt: 'Como profissionais liberais podem dominar o Google Maps e a busca local.',
category: 'Negócios',
publishedAt: '2026-03-15',
image: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?auto=format&fit=crop&q=80&w=1600',
readTime: '8 min',
author: {
name: 'Dr. Lucas Silva',
role: 'Lead Strategist',
avatar: 'https://i.pravatar.cc/150?u=lucas'
},
metaTitle: 'Guia SEO Local 2026 | Google My Business',
metaDescription: "SEO Local 2026: Como dominar o Map Pack e Google My Business. Atraia clientes qualificados em sua região com estratégias de autoridade geográfica e IA local.",
tags: ['Local SEO', 'Google Maps'],
lang: 'pt-br',
content: `
# SEO Local: Dominando sua Região em 2026
O SEO Local tornou-se a espinha dorsal de pequenas e médias empresas. Em 2026, a presença no Google Maps não é apenas uma conveniência, é o destino final da jornada do consumidor local.
## Otimização do Perfil da Empresa
Manter suas informações atualizadas é o passo zero. Mas a autoridade local vai além disso: as avaliações com fotos e palavras-chave específicas do local agora pesam mais do que nunca no "Map Pack".
### O que mudou?
- **Respostas de IA Locais:** O Google Assistant agora usa os dados do seu perfil para responder perguntas complexas sobre disponibilidade e serviços.
- **Links Locais:** Citações em portais de notícias do seu bairro ou cidade transferem uma autoridade geográfica impossível de ignorar.
`
},
{
id: '12',
slug: 'erros-comuns-migracao-de-site',
title: 'Migração de Site: Como não perder tráfego',
excerpt: 'O checklist de sobrevivência para mudar seu domínio ou plataforma sem desastres.',
category: 'Técnico',
publishedAt: '2026-03-10',
image: 'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&q=80&w=1600',
readTime: '20 min',
author: {
name: 'Marcos Tech',
role: 'SEO Engineer',
avatar: 'https://i.pravatar.cc/150?u=marcos_dev'
},
metaTitle: 'Migração de Site 2026 | Checklist de SEO',
metaDescription: "Checklist Definitivo de Migração de Site em 2026. Aprenda a gerenciar redirects 301, monitorar GSC e evitar quedas de tráfego traumáticas durante mudanças.",
tags: ['Migration', 'Redirects'],
lang: 'pt-br',
content: `
# Migração de Site: Como não perder tráfego
Migrar um site é um dos processos mais arriscados em SEO. Um erro no mapeamento de URLs pode destruir anos de autoridade acumulada em poucos dias.
## O Checklist de Sobrevivência
1. **Mapeamento de 1 para 1:** Garanta que cada URL antiga tenha um destino lógico na nova estrutura.
2. **Teste em Staging:** Nunca faça o deploy sem testar os redirects em um ambiente controlado.
3. **Monitoramento Granular:** Após a virada, acompanhe o Google Search Console diariamente para identificar erros 404 inesperados.
`
},
{
id: '13',
slug: 'futuro-da-busca-por-video-youtube-seo',
title: 'O Futuro da Busca por Vídeo e YouTube SEO',
excerpt: 'Como os vídeos estão sendo indexados diretamente nas SERPs principais.',
category: 'Estratégia',
publishedAt: '2026-03-05',
image: 'https://images.unsplash.com/photo-1492724441997-5dc865305da7?auto=format&fit=crop&q=80&w=1600',
readTime: '10 min',
author: {
name: 'Beatriz Costa',
role: 'Head of Content',
avatar: 'https://i.pravatar.cc/150?u=beatriz_c'
},
metaTitle: 'Vertical Search: YouTube e Vídeo SEO em 2026',
metaDescription: "Google Video SEO & YouTube 2026: Como dominar a busca multimodal. Aprenda a otimizar momentos chave, transcrições e capítulos para visibilidade máxima.",
tags: ['Video SEO', 'YouTube'],
lang: 'pt-br',
content: `
# O Futuro da Busca por Vídeo e YouTube SEO
Em 2026, a busca é multimodal. Vídeos curtos e tutoriais aprofundados estão ocupando espaços premium na página principal do Google.
## Indexação de Momentos Chave
O Google não indexa o vídeo, mas também os capítulos e as transcrições, permitindo que o usuário "caia" exatamente no segundo que resolve sua dúvida.
`
},
{
id: '14',
slug: 'ferramentas-gratuitas-seo-indispensaveis',
title: '15 Ferramentas Gratuitas de SEO Indispensáveis',
excerpt: 'O arsenal que você precisa para gerenciar seus projetos sem gastar um centavo.',
category: 'Estratégia',
publishedAt: '2026-03-01',
image: 'https://images.unsplash.com/photo-1551288049-bbdac8a28a1e?auto=format&fit=crop&q=80&w=1600',
readTime: '12 min',
author: {
name: 'Dr. Lucas Silva',
role: 'Lead Strategist',
avatar: 'https://i.pravatar.cc/150?u=lucas_s'
},
metaTitle: 'Ferramentas Gratuitas de SEO 2026',
metaDescription: "As 15 Melhores Ferramentas Gratuitas de SEO para 2026. Tenha o arsenal técnico completo sem gastar um centavo: de Search Console a monitoramento de tendências.",
tags: ['Tools', 'Free'],
lang: 'pt-br',
content: `
# 15 Ferramentas Gratuitas de SEO Indispensáveis
Você não precisa de assinaturas caras de mil dólares por mês para começar a rankear. Aqui está o arsenal básico para 2026:
1. **Google Search Console:** A fonte da verdade direta do buscador.
2. **AnswerThePublic:** Para entender a intenção de busca.
3. **Screaming Frog (Versão Free):** Para auditorias técnicas básicas.
4. **Google Trends:** Para capturar tendências sazonais antes da concorrência.
`
},
{
id: '15',
slug: 'seo-para-saas-estratatégias-de-growth',
title: 'SEO para SaaS: Estratégias de Growth Através do Orgânico',
excerpt: 'Como empresas de software escalam aquisição de usuários usando conteúdo técnico.',
category: 'Negócios',
publishedAt: '2026-02-25',
image: 'https://images.unsplash.com/photo-1526628953301-3e589a6a8b74?auto=format&fit=crop&q=80&w=1600',
readTime: '15 min',
author: {
name: 'Beatriz Costa',
role: 'Head of Content',
avatar: 'https://i.pravatar.cc/150?u=beatriz'
},
metaTitle: 'SaaS SEO Strategy 2026 | Growth Marketing',
metaDescription: "Growth SEO para SaaS: Como escalar a aquisição de usuários no orgânico. Aprenda a estruturar o funil de conteúdo para converter buscas em usuários ativos de software.",
tags: ['SaaS', 'Growth'],
lang: 'pt-br',
content: `
# SEO para SaaS: Estratégias de Growth
O custo de aquisição (CAC) via anúncios subiu 400% nos últimos anos. Para empresas SaaS, o orgânico tornou-se o canal de crescimento mais sustentável.
## O Funil de Conteúdo SaaS
- **TOFU:** Resolvendo problemas genéricos (ex: "Como gerenciar projetos").
- **MOFU:** Comparativos (ex: "Software X vs Software Y").
- **BOFU:** Intenção direta (ex: "Melhor CRM para imobiliárias").
`
},
{
id: '16',
slug: 'analise-de-concorrencia-descobrindo-gaps',
title: 'Análise de Concorrência: Descobrindo Gaps de Conteúdo',
excerpt: 'Como encontrar o que seus concorrentes não estão cobrindo e dominar essas buscas.',
category: 'Estratégia',
publishedAt: '2026-02-20',
image: 'https://images.unsplash.com/photo-1542744173-8e7e53415bb0?auto=format&fit=crop&q=80&w=1600',
readTime: '9 min',
author: {
name: 'Dr. Lucas Silva',
role: 'Lead Strategist',
avatar: 'https://i.pravatar.cc/150?u=lucas'
},
metaTitle: 'Análise de Concorrentes SEO 2026',
metaDescription: "Descubra os pontos fracos dos seus concorrentes. Guia de Análise de Concorrência SEO para identificar gaps de conteúdo e dominar nichos subexplorados.",
tags: ['Competition', 'Strategy'],
lang: 'pt-br',
content: `
# Análise de Concorrência: Gaps de Conteúdo
Não tente ganhar dos gigantes nas palavras-chave mais difíceis. Procure os **Gaps**. Onde a resposta deles é incompleta? Onde eles estão desatualizados?
## Ferramentas de Gap
Use relatórios de "Content Gap" para ver onde seus dois maiores concorrentes rankeiam mas você não. Esse é o seu roteiro de conteúdo para o próximo trimestre.
`
},
{
id: '17',
slug: 'otimizacao-de-imagens-para-google-discover',
title: 'Otimização de Imagens para o Google Discover',
excerpt: 'Como aparecer no feed de notícias do Google com imagens de impacto e tags corretas.',
category: 'Técnico',
publishedAt: '2026-02-15',
image: 'https://images.unsplash.com/photo-1493612276216-ee3925520721?auto=format&fit=crop&q=80&w=1600',
readTime: '7 min',
author: {
name: 'Marcos Tech',
role: 'SEO Engineer',
avatar: 'https://i.pravatar.cc/150?u=marcos'
},
metaTitle: 'Google Discover SEO 2026',
metaDescription: "Google Discover SEO: O segredo para atrair milhões de acessos via imagens de impacto. Requisitos técnicos e táticas visuais para dominar o feed em 2026.",
tags: ['Images', 'Discover'],
lang: 'pt-br',
content: `
# Imagens no Google Discover
O Discover é visual. Se a sua imagem não "parar o polegar" do usuário, seu CTR será nulo.
## Requisitos Técnicos
- **Tamanho:** Mínimo de 1200px de largura.
- **Alt Text:** Descritivo, mas focado no contexto visual.
- **Qualidade:** Evite fotos de estúdio genéricas; fotos reais com impacto emocional performam 3x melhor.
`
},
{
id: '18',
slug: 'seo-para-news-otimizando-artigos-de-noticias',
title: 'SEO para News: Otimizando Artigos em Tempo Real',
excerpt: 'Técnicas para aparecer no Google News e Top Stories de forma instantânea.',
category: 'Estratégia',
publishedAt: '2026-02-10',
image: 'https://images.unsplash.com/photo-1495020689067-958852a7765e?auto=format&fit=crop&q=80&w=1600',
readTime: '11 min',
author: {
name: 'Beatriz Costa',
role: 'Head of Content',
avatar: 'https://i.pravatar.cc/150?u=beatriz'
},
metaTitle: 'Google News SEO 2026',
metaDescription: "Domine o Top Stories e Google News. Aprenda técnicas de SEO em tempo real para portais de notícias, focando em velocidade de indexação e autoridade do autor.",
tags: ['News', 'Real Time'],
lang: 'pt-br',
content: `
# SEO para News
Notícia é o ápice da temporariedade. Rankear no "Top Stories" exige uma combinação de autoridade de marca e velocidade de indexação técnica.
## Entidades e Jornalismo
O Google agora identifica a entidade do jornalista. Quem escreveu? Esse autor tem histórico no assunto? Isso é o E-E-A-T aplicado ao jornalismo digital.
`
},
{
id: '19',
slug: 'impacto-dos-links-nofollow-e-ugc',
title: 'O Impacto Real dos Links Nofollow, Sponsored e UGC',
excerpt: 'Analisando como os hints de atributo de link influenciam o algoritmo moderno.',
category: 'Autoridade',
publishedAt: '2026-02-05',
image: 'https://images.unsplash.com/photo-1544197150-b99a580bb7a8?auto=format&fit=crop&q=80&w=1600',
readTime: '13 min',
author: {
name: 'João Links',
role: 'PR Specialist',
avatar: 'https://i.pravatar.cc/150?u=joao'
},
metaTitle: 'Link Attributes 2026 | Nofollow vs Sponsored',
metaDescription: "Nofollow, Sponsored ou UGC? Entenda o impacto real dos atributos de link no algoritmo do Google em 2026 e quando usar cada um para sua autoridade.",
tags: ['Attributes', 'Links'],
lang: 'pt-br',
content: `
# Links Nofollow e UGC em 2026
Desde que se tornaram "hints", os links nofollow pararam de ser ignorados. O Google agora decide se quer ou não passar autoridade por eles.
## Quando usar cada um?
- **UGC:** Para links em comentários de blog ou fóruns.
- **Sponsored:** Para qualquer tipo de publieditorial or link pago.
- **Nofollow:** Para links que você não quer necessariamente endossar.
`
},
{
id: '20',
slug: 'monetizacao-blog-seo-autoridade',
title: 'Monetização de Blog através de SEO de Autoridade',
excerpt: 'Como transformar seu tráfego orgânico em uma máquina de receita sustentável.',
category: 'Negócios',
publishedAt: '2026-02-01',
image: 'https://images.unsplash.com/photo-1553729459-efe14ef6055d?auto=format&fit=crop&q=80&w=1600',
readTime: '16 min',
author: {
name: 'Ricardo Vendas',
role: 'E-commerce Expert',
avatar: 'https://i.pravatar.cc/150?u=ricardo'
},
metaTitle: 'Monetização SEO 2026 | Guia de Lucratividade',
metaDescription: "Como lucrar com seu blog de autoridade em 2026. Saia das métricas de vaidade e transforme seu tráfego orgânico em uma máquina de receita sustentável.",
tags: ['Monetization', 'Business'],
lang: 'pt-br',
content: `
# Monetização de Blogs de Autoridade
Tráfego é vaidade, lucro é sanidade. Em 2026, os blogs de nicho mais lucrativos saíram do AdSense e foram para modelos de afiliados de alto ticket e produtos digitais próprios.
## Estrutura de Conversão
Cada artigo deve ter um objetivo de conversão claro. Se o usuário veio por uma dúvida técnica, ofereça uma ferramenta ou curso que resolva essa dor.
`
},
{
id: '21',
slug: 'the-future-of-organic-search-2026',
title: 'The Future of Organic Search in 2026',
excerpt: 'How Search Generative Experiences are redefining digital landscapes.',
category: 'Estratégia',
publishedAt: '2026-05-04',
image: 'https://images.unsplash.com/photo-1451187580459-43490279c0fa?auto=format&fit=crop&q=80&w=1600',
readTime: '10 min',
author: {
name: 'Dr. Lucas Silva',
role: 'Lead Strategist',
avatar: 'https://i.pravatar.cc/150?u=lucas'
},
metaTitle: 'The Future of Search | English Edition',
metaDescription: "Organic Search 2026: Deep dive into the future of SEO, SGE, and AI-driven user behavior. Learn how to stay ahead in the evolving digital landscape.",
tags: ['AI', 'SEO', 'Future'],
lang: 'en',
content: `
# The Future of Organic Search in 2026
The search landscape is evolving rapidly. Artificial intelligence is no longer just a backend tool; it's the interface itself.
## SGE and User Behavior
Search Generative Experience (SGE) provides direct answers at the top of the SERP, reducing the need for clicks on simple informational queries. Content creators must adapt by offering unique insights that go beyond simple data aggregation.
`
}
];

76
Template-04/src/index.css Normal file
View file

@ -0,0 +1,76 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap');
@import "tailwindcss";
@theme {
--font-sans: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", monospace;
--color-brand: #4f46e5; /* Premium Indigo */
--color-brand-light: #818cf8;
--color-brand-dark: #3730a3;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
--radius-3xl: 2rem;
--spacing-section: 8rem;
}
/* Hide Google Translate Bar */
.goog-te-banner-frame.skiptranslate, .goog-te-banner-frame, #goog-gt-tt, .goog-te-balloon-frame {
display: none !important;
}
body { top: 0 !important; }
.goog-tooltip { display: none !important; }
.goog-text-highlight { background-color: transparent !important; border: none !important; box-shadow: none !important; }
@layer base {
body {
@apply bg-[#030303] text-slate-400 font-sans antialiased selection:bg-brand/30 selection:text-white;
}
h1, h2, h3, h4, h5, h6 {
@apply font-sans tracking-tight text-white font-bold;
}
}
@layer components {
.glass-card {
@apply bg-white/[0.03] border border-white/10 backdrop-blur-md rounded-2xl transition-all duration-300;
}
.glass-card-hover {
@apply hover:bg-white/[0.05] hover:border-white/20 hover:-translate-y-1 hover:shadow-2xl hover:shadow-brand/5;
}
.btn-primary {
@apply h-10 px-5 bg-brand text-white font-bold text-[11px] uppercase tracking-wider rounded-lg transition-all hover:bg-brand-light active:scale-95 flex items-center justify-center gap-2 shadow-lg shadow-brand/10;
}
.btn-outline {
@apply h-10 px-5 border border-white/10 text-white font-bold text-[11px] uppercase tracking-wider rounded-lg transition-all hover:bg-white/5 flex items-center justify-center gap-2;
}
.section-container {
@apply mx-auto max-w-7xl w-full px-6 lg:px-8;
}
/* Typography Utilities */
.heading-hero {
@apply text-5xl sm:text-7xl lg:text-8xl font-extrabold tracking-tighter leading-[1.1] text-white;
}
.heading-section {
@apply text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight text-white;
}
/* Decorative elements */
.brand-glow {
@apply absolute blur-[120px] opacity-20 pointer-events-none;
}
.grid-pattern {
background-image: radial-gradient(rgba(255,255,255,0.05) 1px, transparent 1px);
background-size: 40px 40px;
}
}

View file

@ -0,0 +1,14 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function formatDate(date: string) {
return new Intl.DateTimeFormat('pt-BR', {
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(new Date(date));
}

10
Template-04/src/main.tsx Normal file
View file

@ -0,0 +1,10 @@
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);

View file

@ -0,0 +1,67 @@
import { motion } from 'motion/react';
import SEO from '../components/SEO';
import { Target, Zap, ShieldCheck, ArrowRight, Activity } from 'lucide-react';
import { Link } from 'react-router-dom';
export default function About() {
return (
<>
<SEO
title="Sobre Nossa Agência | Apex SEO"
description="Pioneirismo na próxima geração da busca orgânica. Unimos complexidade técnica ao crescimento real de receita."
/>
<section className="relative pt-32 pb-32 overflow-hidden bg-black">
<div className="brand-glow top-0 left-1/2 -translate-x-1/2 h-[500px] w-[500px] bg-brand/10" />
<div className="section-container relative z-10">
<div className="max-w-4xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<h1 className="text-5xl sm:text-7xl lg:text-8xl font-bold text-white mb-10 tracking-tight">
Escalando a <br/><span className="text-brand">Elite Global.</span>
</h1>
<p className="text-xl text-slate-400 leading-relaxed max-w-2xl mb-16">
Na Apex, não acreditamos em métricas de vaidade ou volume por volume. Acreditamos em autoridade arquitetônica, relevância de alta intenção e no fim do SEO genérico.
</p>
</motion.div>
</div>
<div className="grid lg:grid-cols-3 gap-8 py-16">
{[
{ icon: Target, title: 'Intenção Estratégica', desc: 'Focamos nos nós da jornada do usuário que realmente geram receita, não apenas picos de tráfego.' },
{ icon: Zap, title: 'Velocidade Técnica', desc: 'Performance é nossa base. Construímos ecossistemas de busca rápidos e resilientes para crawlers modernos.' },
{ icon: ShieldCheck, title: 'Prova de Autoridade', desc: 'Modelagem de E-E-A-T calculada que constrói confiança autêntica e estabilidade de ranking sustentável.' }
].map((item, i) => (
<div key={i} className="glass-card p-10 group hover:border-brand/30 transition-all">
<div className="h-12 w-12 rounded-xl bg-white/[0.03] border border-white/10 flex items-center justify-center text-brand mb-8 group-hover:bg-brand group-hover:text-white transition-all">
<item.icon size={22} />
</div>
<h3 className="text-2xl font-bold text-white mb-4">{item.title}</h3>
<p className="text-slate-500 text-sm leading-relaxed">
{item.desc}
</p>
</div>
))}
</div>
<div className="py-24 flex flex-col items-center border-t border-white/5 mt-16">
<blockquote className="max-w-4xl text-center font-bold text-white text-3xl sm:text-5xl tracking-tight leading-tight px-6 italic">
"O SEO moderno não é mais sobre palavras-chave; <span className="text-brand">é sobre se tornar a fonte definitiva</span> de verdade no seu setor."
</blockquote>
<div className="mt-16">
<Link to="/contato" className="btn-primary px-12 group">
Escale sua Empresa
<ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
</Link>
</div>
</div>
</div>
</section>
</>
);
}

View file

@ -0,0 +1,57 @@
import { motion } from 'motion/react';
import { articles } from '../data/articles';
import ArticleCard from '../components/ArticleCard';
import SEO from '../components/SEO';
import { useLanguage } from '../contexts/LanguageContext';
import { translations } from '../constants';
export default function Archive() {
const { lang } = useLanguage();
const t = translations[lang];
return (
<>
<SEO
title="Arquivo de Insights | Apex SEO"
description="Explore nossa coleção completa de protocolos, guias e estudos de caso sobre autoridade orgânica."
/>
<div className="pt-24 pb-20 min-h-screen bg-[#050505]">
<div className="max-w-7xl mx-auto px-6">
<header className="mb-16 relative">
<div className="absolute top-0 right-0 w-[400px] h-[400px] bg-brand/5 blur-[100px] -z-10 rounded-full" />
<div className="flex items-center gap-3 mb-8">
<div className="h-px w-8 bg-brand" />
<span className="text-[9px] font-black uppercase tracking-[0.4em] text-brand">Arquivo_Central_Apex</span>
</div>
<h1 className="text-5xl sm:text-7xl font-display font-black text-white tracking-tight leading-[0.9] mb-8 uppercase italic">
Insights<br /><span className="text-brand">Arquivados.</span>
</h1>
<p className="text-xs font-mono text-slate-600 max-w-xl font-black leading-relaxed uppercase tracking-widest pl-6 border-l border-white/10">
Uma sequência cronológica de inteligência de busca e autoridade estratégica. [APEX_DATA_v4.2]
</p>
</header>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
{articles.map((article, index) => (
<motion.div
key={article.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }}
>
<ArticleCard article={article} />
</motion.div>
))}
</div>
{articles.length === 0 && (
<div className="text-center py-48 glass rounded-[3rem] border border-white/5">
<p className="text-slate-500 font-black uppercase tracking-[0.4em] text-[10px]">Nenhum_Registro_Encontrado [404]</p>
</div>
)}
</div>
</div>
</>
);
}

View file

@ -0,0 +1,354 @@
import { useParams, Link, useNavigate } from 'react-router-dom';
import { articles } from '../data/articles';
import SEO from '../components/SEO';
import ReactMarkdown from 'react-markdown';
import { Clock, Calendar, Share2, Bookmark, ArrowRight, MessageCircle, Loader2, Sparkles, Twitter, Linkedin, Facebook, Link2, CheckCircle2, TrendingUp } from 'lucide-react';
import { motion, AnimatePresence, useScroll, useSpring } from 'motion/react';
import { formatDate, cn } from '../lib/utils';
import { useLanguage } from '../contexts/LanguageContext';
import { translations } from '../constants';
import { useState, useEffect } from 'react';
import { Article } from '../types';
import { useBookmarks } from '../contexts/BookmarksContext';
import TableOfContents from '../components/TableOfContents';
export default function BlogPost() {
const { slug } = useParams();
const navigate = useNavigate();
const { lang, translate } = useLanguage();
const { toggleBookmark, isBookmarked } = useBookmarks();
const { scrollYProgress } = useScroll();
const scaleX = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001
});
const [translatedArticle, setTranslatedArticle] = useState<Article | undefined>();
const [loading, setLoading] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const [showToast, setShowToast] = useState(false);
const originalArticle = articles.find(a => a.slug === slug);
useEffect(() => {
if (!originalArticle) return;
const translateFullArticle = async () => {
// If language is Portuguese, just use the original
if (lang === 'pt-br') {
setTranslatedArticle(originalArticle);
return;
}
setLoading(true);
try {
// Use Gemini to translate all parts in one shot
const translated = await translate({
title: originalArticle.title,
excerpt: originalArticle.excerpt,
content: originalArticle.content,
metaTitle: originalArticle.metaTitle,
metaDescription: originalArticle.metaDescription
}, 'full article content', true);
setTranslatedArticle({
...originalArticle,
...translated
});
} catch (error) {
console.error("Auto-translation error:", error);
setTranslatedArticle(originalArticle);
} finally {
setLoading(false);
}
};
translateFullArticle();
}, [lang, originalArticle, slug, translate]);
const copyToClipboard = () => {
navigator.clipboard.writeText(window.location.href);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
};
const article = translatedArticle || originalArticle;
if (!article) {
return (
<div className="min-h-screen flex items-center justify-center p-4">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4">Artigo não encontrado</h1>
<button onClick={() => navigate('/')} className="btn-primary">Voltar para a Home</button>
</div>
</div>
);
}
const relatedArticles = articles.filter(a => a.category === article.category && a.id !== article.id).slice(0, 2);
return (
<>
<SEO
title={article.metaTitle}
description={article.metaDescription}
type="article"
image={article.image}
keywords={article.tags}
articleData={{
publishedTime: article.publishedAt,
author: article.author.name,
authorAvatar: article.author.avatar,
tags: article.tags,
section: article.category
}}
/>
<article className="pb-32 bg-black relative">
<motion.div
className="fixed top-20 left-0 right-0 h-1 bg-brand origin-left z-[60]"
style={{ scaleX }}
/>
<AnimatePresence>
{loading && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100] bg-black/95 backdrop-blur-xl flex items-center justify-center p-6 text-center"
>
<div className="max-w-md w-full">
<div className="relative mb-8 inline-block">
<div className="absolute inset-0 bg-brand/20 animate-ping rounded-full" />
<div className="relative h-16 w-16 bg-brand/10 border border-brand/30 rounded-2xl flex items-center justify-center">
<Loader2 className="w-8 h-8 text-brand animate-spin" />
</div>
</div>
<h3 className="text-xl font-bold text-white mb-2 tracking-tight">
Otimizando Perspectiva
</h3>
<p className="text-slate-500 text-xs font-mono uppercase tracking-[0.2em]">
[ Aplicando Algoritmos de Crescimento ]
</p>
</div>
</motion.div>
)}
</AnimatePresence>
{/* Hero Header */}
<header className="pt-24 pb-12 bg-black relative border-b border-white/5">
<div className="brand-glow top-0 left-1/4 h-[300px] w-[300px] bg-brand/20" />
<div className="section-container relative z-10">
<div className="max-w-4xl">
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="inline-flex items-center gap-3 px-3 py-1 bg-brand/5 border border-brand/20 rounded-full text-brand text-[10px] font-bold uppercase tracking-widest mb-8"
>
Protocolo de Estratégia // {article.category}
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="text-4xl sm:text-6xl lg:text-7xl font-bold text-white leading-[1.1] mb-12 tracking-tight"
>
{article.title}
</motion.h1>
<div className="flex flex-wrap items-center gap-12">
<div className="flex items-center gap-4">
<img src={article.author.avatar} alt={article.author.name} className="h-12 w-12 rounded-full border border-white/10" />
<div>
<p className="text-sm font-bold text-white">{article.author.name}</p>
<p className="text-[10px] uppercase tracking-widest text-slate-500 font-bold">Lead Strategist</p>
</div>
</div>
<div className="h-10 w-px bg-white/10" />
<div className="flex gap-10">
<div className="flex flex-col gap-1">
<span className="text-slate-600 text-[10px] font-bold uppercase tracking-widest">Publicado</span>
<span className="text-white text-xs font-medium">{formatDate(article.publishedAt)}</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-slate-600 text-[10px] font-bold uppercase tracking-widest">Tempo de Leitura</span>
<span className="text-white text-xs font-medium">{article.readTime}</span>
</div>
</div>
</div>
</div>
</div>
</header>
{/* Featured Image */}
<div className="bg-black py-12">
<div className="section-container">
<motion.div
initial={{ opacity: 0, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
className="w-full relative h-[40vh] lg:h-[60vh] rounded-3xl overflow-hidden shadow-2xl"
>
<img
src={article.image}
alt={article.title}
className="h-full w-full object-cover transition-transform duration-[3s]"
referrerPolicy="no-referrer"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
</motion.div>
</div>
</div>
{/* Content Layout */}
<div className="section-container">
<div className="grid lg:grid-cols-12 gap-16 relative">
{/* Main Content */}
<div className="lg:col-span-8 py-10">
<div className="markdown-body text-slate-400">
<ReactMarkdown
components={{
h2: ({ children }) => {
const id = String(children).toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s+/g, '-')
.replace(/[^\w-]/g, '');
return (
<h2 id={id} className="group relative scroll-mt-32 font-bold text-white border-brand">
{children}
</h2>
);
}
}}
>
{article.content}
</ReactMarkdown>
</div>
{/* Tags */}
<div className="mt-20 flex flex-wrap gap-2 pt-12 border-t border-white/5">
{article.tags.map(tag => (
<span key={tag} className="px-4 py-2 bg-white/[0.03] border border-white/10 rounded-xl text-slate-500 text-[10px] font-bold uppercase tracking-widest hover:border-brand hover:text-brand transition-all cursor-default">
{tag}
</span>
))}
</div>
{/* Author Detail */}
<div className="mt-24 p-8 glass-card border-brand/10">
<div className="flex flex-col sm:flex-row items-center gap-8">
<img src={article.author.avatar} alt={article.author.name} className="h-20 w-20 rounded-2xl border border-white/10 shadow-xl" />
<div className="text-left">
<h4 className="text-xl font-bold text-white mb-2">{article.author.name}</h4>
<p className="text-slate-500 text-sm leading-relaxed mb-4">
{article.author.role}, especializado em SEO arquitetônico e estratégias de conversão de alto ticket.
</p>
<div className="flex gap-4">
<button className="text-xs font-bold text-brand uppercase tracking-widest hover:text-white transition-colors">LinkedIn</button>
<button className="text-xs font-bold text-brand uppercase tracking-widest hover:text-white transition-colors">Twitter</button>
</div>
</div>
</div>
</div>
</div>
{/* Sidebar */}
<aside className="lg:col-span-4 py-10">
<div className="sticky top-32 space-y-16">
<TableOfContents content={article.content} />
{/* Social Share */}
<div className="pt-12 border-t border-white/5">
<h5 className="text-[10px] font-bold text-white uppercase tracking-widest mb-8">Compartilhar Insight</h5>
<div className="grid grid-cols-4 gap-3">
{[Twitter, Linkedin, Facebook, Link2].map((Icon, i) => (
<button
key={i}
onClick={i === 3 ? copyToClipboard : undefined}
className="h-12 flex items-center justify-center rounded-xl border border-white/10 bg-white/[0.02] text-slate-500 hover:text-brand hover:border-brand/40 transition-all group"
>
<Icon size={18} className="group-hover:scale-110 transition-transform" />
</button>
))}
</div>
</div>
{/* Related Outcomes */}
{relatedArticles.length > 0 && (
<div className="pt-12 border-t border-white/5">
<h5 className="text-[10px] font-bold text-white uppercase tracking-widest mb-8">Resultados Relacionados</h5>
<div className="space-y-8">
{relatedArticles.map(rel => (
<Link key={rel.id} to={`/artigo/${rel.slug}`} className="group block">
<div className="flex gap-4 items-start">
<div className="w-20 h-20 rounded-xl overflow-hidden shrink-0 border border-white/5">
<img src={rel.image} className="w-full h-full object-cover transition-transform group-hover:scale-110" referrerPolicy="no-referrer" />
</div>
<div>
<span className="text-[10px] font-bold text-brand uppercase mb-2 block">{rel.category}</span>
<h6 className="font-bold text-white text-sm leading-snug group-hover:text-brand transition-colors">
{rel.title}
</h6>
</div>
</div>
</Link>
))}
</div>
</div>
)}
{/* Newsletter Box */}
<div className="p-8 glass-card border-brand/20 bg-gradient-to-br from-brand/10 to-transparent">
<div className="h-12 w-12 bg-brand rounded-2xl flex items-center justify-center text-white mb-6 shadow-xl shadow-brand/20">
<Sparkles size={20} />
</div>
<h5 className="text-xl font-bold text-white mb-2">Trimestral de Crescimento</h5>
<p className="text-slate-500 text-xs mb-8 leading-relaxed">Junte-se a mais de 5.000 profissionais de growth recebendo nossos insights técnicos de SEO.</p>
<button className="btn-primary w-full shadow-lg shadow-brand/20">
Assinar Agora
</button>
</div>
</div>
</aside>
</div>
</div>
</article>
{/* Toast Notification */}
<AnimatePresence>
{isCopied && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
className="fixed bottom-12 left-1/2 -translate-x-1/2 z-[100] px-6 py-3 bg-white text-black text-xs font-bold rounded-full shadow-2xl flex items-center gap-3"
>
<CheckCircle2 size={16} className="text-emerald-500" />
Link copiado para a área de transferência
</motion.div>
)}
</AnimatePresence>
<section className="bg-black py-24 border-t border-white/5 relative overflow-hidden">
<div className="brand-glow bottom-0 left-1/2 -translate-x-1/2 h-[400px] w-[400px] bg-brand/10" />
<div className="section-container text-center relative z-10">
<div className="w-16 h-16 glass-card rounded-2xl flex items-center justify-center mx-auto mb-10 group">
<TrendingUp size={28} className="text-brand group-hover:scale-110 transition-transform" />
</div>
<h2 className="text-3xl sm:text-5xl font-bold text-white mb-6">Gere Autoridade Real.</h2>
<p className="text-lg text-slate-500 mb-12 font-medium max-w-2xl mx-auto leading-relaxed">Nossa metodologia transforma a busca orgânica padrão em uma máquina de receita de alta performance.</p>
<div className="flex flex-wrap justify-center gap-6">
<Link to="/" className="btn-primary px-10">Voltar para Estratégia</Link>
<Link to="/contato" className="btn-outline px-10">Consultar nosso Time</Link>
</div>
</div>
</section>
</>
);
}

View file

@ -0,0 +1,100 @@
import { motion } from 'motion/react';
import { Bookmark as BookmarkIcon, ArrowRight, Trash2 } from 'lucide-react';
import SEO from '../components/SEO';
import { useBookmarks } from '../contexts/BookmarksContext';
import { articles } from '../data/articles';
import { Link } from 'react-router-dom';
import CategoryBadge from '../components/CategoryBadge';
export default function Bookmarks() {
const { bookmarks, toggleBookmark } = useBookmarks();
const savedArticles = articles.filter(a => bookmarks.includes(a.id));
return (
<div className="pt-36 pb-20 min-h-screen bg-[#050505]">
<SEO
title="Insights Salvos | Apex SEO"
description="Sua coleção personalizada de protocolos e insights para ler depois."
/>
<div className="max-w-7xl mx-auto px-6">
<header className="mb-16 relative">
<div className="absolute top-0 right-0 w-[400px] h-[400px] bg-brand/5 blur-[100px] -z-10 rounded-full" />
<div className="flex items-center gap-4 mb-8">
<div className="h-0.5 w-8 bg-brand" />
<span className="text-[9px] font-black uppercase tracking-[0.4em] text-brand">Biblioteca_Pessoal_Apex</span>
</div>
<h1 className="text-5xl sm:text-7xl font-display font-black text-white tracking-tight leading-[0.9] mb-8 uppercase italic">
Insights<br /><span className="text-brand">Salvos.</span>
</h1>
<p className="text-xs font-mono text-slate-600 max-w-xl font-black leading-relaxed uppercase tracking-widest pl-8 border-l border-white/10">
Revise seus padrões neurais extraídos e aplique-os ao seu crescimento arquitetônico.
</p>
</header>
{savedArticles.length > 0 ? (
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-16">
{savedArticles.map((article, i) => (
<motion.div
key={article.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
className="group glass-dark rounded-[3rem] border border-white/10 p-10 flex flex-col hover:border-brand/40 transition-all h-full shadow-2xl relative overflow-hidden"
>
<div className="absolute top-0 right-0 w-24 h-24 bg-brand/5 blur-2xl rounded-full translate-x-1/2 -translate-y-1/2" />
<div className="flex justify-between items-start mb-10 relative z-10">
<CategoryBadge category={article.category} variant="outline" />
<button
onClick={() => toggleBookmark(article.id)}
className="p-3 glass rounded-xl text-slate-500 hover:text-red-500 hover:border-red-500/20 transition-all"
title="Remover"
>
<Trash2 size={18} />
</button>
</div>
<Link to={`/artigo/${article.slug}`} className="flex-grow relative z-10">
<h3 className="text-2xl font-display font-black text-white group-hover:text-brand transition-colors mb-8 leading-tight italic uppercase tracking-tight">
{article.title}
</h3>
<p className="text-slate-500 text-sm leading-relaxed mb-10 line-clamp-3 font-medium uppercase tracking-widest text-[10px]">
{article.excerpt}
</p>
</Link>
<Link
to={`/artigo/${article.slug}`}
className="inline-flex items-center gap-4 text-brand font-black uppercase text-[10px] tracking-[0.3em] group/link relative z-10"
>
Continuar_Leitura
<div className="h-10 w-10 rounded-xl glass flex items-center justify-center text-brand group-hover/link:bg-brand group-hover/link:text-slate-950 transition-all shadow-[0_0_15px_rgba(0,242,255,0.1)]">
<ArrowRight size={16} />
</div>
</Link>
</motion.div>
))}
</div>
) : (
<div className="glass-dark rounded-[4rem] p-32 text-center border border-white/5 relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,rgba(0,242,255,0.02),transparent_70%)]" />
<div className="h-24 w-24 glass rounded-[2rem] flex items-center justify-center text-slate-700 mx-auto mb-12 border border-white/10 group shadow-2xl relative z-10">
<BookmarkIcon size={40} className="group-hover:text-brand transition-colors" />
</div>
<h2 className="text-4xl font-display font-black text-white mb-6 uppercase italic tracking-tighter relative z-10">Biblioteca_Vazia</h2>
<p className="text-slate-500 mb-16 max-w-md mx-auto font-medium uppercase tracking-[0.2em] text-xs relative z-10">
Explore nossos bancos de dados editoriais e salve insights para escalonamento futuro.
</p>
<Link
to="/"
className="btn-primary"
>
Iniciar_Busca
</Link>
</div>
)}
</div>
</div>
);
}

View file

@ -0,0 +1,84 @@
import { useParams, Link } from 'react-router-dom';
import { articles } from '../data/articles';
import ArticleCard from '../components/ArticleCard';
import SEO from '../components/SEO';
import { ChevronRight } from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
import { CATEGORY_MAP } from '../constants';
export default function CategoryPage() {
const { categorySlug } = useParams();
const { lang } = useLanguage();
const getCategoryData = (slug: string) => {
const entry = Object.entries(CATEGORY_MAP).find(([_, value]) => value.slug === slug);
if (entry) {
const [originalName, data] = entry;
return {
name: data.name,
originalName: originalName,
description: `Análises profundas, estudos de caso e táticas avançadas sobre ${data.name.toLowerCase()}.`
};
}
return {
name: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, ' '),
originalName: slug,
description: 'Artigos e análises sobre o ecossistema digital.'
};
};
const { name: categoryName, originalName, description: categoryDescription } = getCategoryData(categorySlug || '');
const filteredArticles = articles.filter(a => {
// If we have a map entry, match by original category name
if (originalName) {
return a.category === originalName;
}
// Fallback strategy
const slugfiedCategory = a.category.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\s+/g, '-');
return slugfiedCategory === categorySlug;
});
return (
<>
<SEO
title={`Artigos sobre ${categoryName}`}
description={`Confira guias e dicas exclusivas sobre ${categoryName} em nosso blog de autoridade SEO.`}
/>
<div className="bg-[#020204] pt-24 pb-16 border-b border-white/5 relative overflow-hidden">
<div className="absolute inset-0 bg-grid opacity-5" />
<div className="mx-auto max-w-7xl px-6 relative z-10">
<div className="flex items-center gap-4 text-brand font-black uppercase tracking-[0.4em] text-[9px] mb-8 italic">
<div className="h-[1px] w-8 bg-brand" />
<span>CLUSTER_EDITORIAL</span>
</div>
<h1 className="text-5xl sm:text-7xl font-display font-black text-white tracking-tight leading-[0.9] uppercase italic">{categoryName}</h1>
<p className="text-xs font-mono text-slate-500 mt-8 max-w-2xl font-black leading-relaxed uppercase tracking-widest border-l border-white/10 pl-6">{categoryDescription}</p>
</div>
</div>
<div className="py-24 bg-[#020204]">
<div className="mx-auto max-w-7xl px-6">
{filteredArticles.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12">
{filteredArticles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</div>
) : (
<div className="text-center py-48 bg-white/5 border border-white/5">
<h2 className="text-xs font-mono font-black text-slate-700 uppercase tracking-[0.4em] mb-8 italic">NENHUM_INSIGHT_INDEXADO</h2>
<Link to="/" className="btn-primary">
REINICIAR_HOME
</Link>
</div>
)}
</div>
</div>
</>
);
}

View file

@ -0,0 +1,152 @@
import { motion } from 'motion/react';
import { Mail, MessageCircle, Send, ArrowRight } from 'lucide-react';
import { useState } from 'react';
import SEO from '../components/SEO';
import Layout from '../components/Layout';
export default function Contact() {
const [submitted, setSubmitted] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setSubmitted(true);
};
return (
<>
<SEO
title="Contato | Apex SEO Growth"
description="Conecte-se com nossos estrategistas para auditorias enterprise, estratégia de conteúdo de alta intenção ou dúvidas gerais."
/>
<div className="pt-32 pb-24 bg-black relative overflow-hidden">
<div className="brand-glow top-0 right-1/4 h-[500px] w-[500px] bg-brand/10" />
<div className="section-container relative z-10">
<div className="grid lg:grid-cols-2 gap-20">
{/* Left Column: Info */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="space-y-16"
>
<div>
<div className="flex items-center gap-3 mb-8">
<div className="h-1.5 w-1.5 rounded-full bg-brand" />
<span className="text-xs font-bold text-brand uppercase tracking-widest">Fale com nosso time</span>
</div>
<h1 className="text-5xl sm:text-7xl font-bold text-white mb-8 tracking-tight">
Inicie sua <br /><span className="text-brand">Fase de Crescimento.</span>
</h1>
<p className="text-lg text-slate-400 max-w-md leading-relaxed">
Pronto para implantar estratégias orgânicas de alta autoridade? Nossos estrategistas seniores estão à disposição para uma sincronização técnica.
</p>
</div>
<div className="space-y-6">
<div className="flex gap-6 group">
<div className="h-14 w-14 rounded-2xl border border-white/10 bg-white/[0.03] flex items-center justify-center text-brand transition-all group-hover:bg-brand group-hover:text-white group-hover:scale-110">
<Mail size={22} />
</div>
<div>
<h4 className="font-bold text-white uppercase tracking-widest text-[10px] mb-2">Central de E-mail</h4>
<p className="text-slate-500 font-medium text-sm">hello@apexseo.agency</p>
</div>
</div>
<a
href="https://wa.me/5511999999999"
target="_blank"
rel="noopener noreferrer"
className="flex gap-6 group"
>
<div className="h-14 w-14 rounded-2xl border border-white/10 bg-white/[0.03] flex items-center justify-center text-brand transition-all group-hover:bg-brand group-hover:text-white group-hover:scale-110">
<MessageCircle size={22} />
</div>
<div>
<h4 className="font-bold text-white uppercase tracking-widest text-[10px] mb-2">Canal Direto</h4>
<p className="text-slate-500 font-medium text-sm group-hover:text-brand transition-colors">+55 (11) 99999-9999</p>
</div>
</a>
</div>
</motion.div>
{/* Right Column: Form */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="glass-card p-10 md:p-14 relative"
>
<div className="absolute top-0 right-0 w-32 h-32 bg-brand/5 blur-[80px]" />
{submitted ? (
<div className="h-full flex flex-col items-center justify-center text-center space-y-8 py-20 relative z-10">
<div className="h-20 w-20 bg-brand rounded-3xl flex items-center justify-center text-white shadow-xl shadow-brand/20">
<Send size={32} />
</div>
<div>
<h2 className="text-3xl font-bold text-white mb-4">Briefing Recebido</h2>
<p className="text-slate-500 text-sm leading-relaxed">Seus dados foram processados. Um estrategista entrará em contato em até 24 horas úteis.</p>
</div>
<button
onClick={() => setSubmitted(false)}
className="text-brand font-bold uppercase text-[10px] tracking-widest hover:text-white transition-colors border-b border-brand/20 pb-1"
>
Enviar nova solicitação
</button>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-8 relative z-10">
<div className="grid md:grid-cols-2 gap-8">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-slate-500">Nome Completo</label>
<input
required
type="text"
placeholder="Ex: Marcus Thorne"
className="w-full bg-white/[0.03] border border-white/10 rounded-xl px-4 py-3 text-white text-sm focus:outline-none focus:border-brand transition-all"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-slate-500">E-mail Corporativo</label>
<input
required
type="email"
placeholder="Ex: marcus@cloudscale.com"
className="w-full bg-white/[0.03] border border-white/10 rounded-xl px-4 py-3 text-white text-sm focus:outline-none focus:border-brand transition-all"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-slate-500">Tipo de Projeto</label>
<select className="w-full bg-white/[0.03] border border-white/10 rounded-xl px-4 py-3 text-white text-sm focus:outline-none focus:border-brand transition-all appearance-none">
<option className="bg-slate-900">Auditoria Técnica</option>
<option className="bg-slate-900">Arquitetura de Conteúdo</option>
<option className="bg-slate-900">Crescimento Enterprise</option>
<option className="bg-slate-900">Outras Consultas</option>
</select>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-slate-500">Como podemos ajudar?</label>
<textarea
required
placeholder="Descreva seus desafios orgânicos atuais..."
rows={4}
className="w-full bg-white/[0.03] border border-white/10 rounded-xl px-4 py-3 text-white text-sm focus:outline-none focus:border-brand transition-all resize-none"
></textarea>
</div>
<button className="btn-primary w-full h-12 group">
Enviar Solicitação Estratégica
<ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
</button>
</form>
)}
</motion.div>
</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,338 @@
import { Link } from 'react-router-dom';
import { articles } from '../data/articles';
import ArticleCard from '../components/ArticleCard';
import SEO from '../components/SEO';
import { ArrowRight, TrendingUp, Search, BarChart3, Globe, Zap, CheckCircle2, Star, Quote, Activity } from 'lucide-react';
import { motion } from 'motion/react';
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
const chartData = [
{ name: 'Jan', traffic: 4000 },
{ name: 'Feb', traffic: 7000 },
{ name: 'Mar', traffic: 12000 },
{ name: 'Apr', traffic: 18000 },
{ name: 'May', traffic: 32000 },
{ name: 'Jun', traffic: 54000 },
{ name: 'Jul', traffic: 89000 },
];
const logos = [
{ name: 'TechFlow', color: 'text-slate-500' },
{ name: 'CloudScale', color: 'text-slate-500' },
{ name: 'FutureNode', color: 'text-slate-500' },
{ name: 'DataCore', color: 'text-slate-500' },
{ name: 'PulseAI', color: 'text-slate-500' },
];
const services = [
{
title: 'SEO de Precisão',
description: 'Estratégias de otimização orientadas por dados que comandam autoridade nos rankings e crescimento orgânico sustentável.',
icon: Search,
color: 'text-brand'
},
{
title: 'Estratégia de Conteúdo',
description: 'Conteúdo arquitetônico de alta intenção projetado para converter tráfego de elite em clientes leais.',
icon: Zap,
color: 'text-blue-500'
},
{
title: 'Análise de Performance',
description: 'Rastreamento multi-nó em tempo real e modelagem de atribuição para total transparência de resultados.',
icon: BarChart3,
color: 'text-emerald-500'
},
{
title: 'Infraestrutura Técnica',
description: 'Otimizações avançadas de backend que maximizam a eficiência de rastreamento e a velocidade do site.',
icon: Globe,
color: 'text-purple-500'
}
];
export default function Home() {
const featuredArticles = articles.slice(0, 3);
return (
<div className="relative">
<SEO title="Apex SEO | Agência de Crescimento Premium" description="Escale sua autoridade com estratégias de SEO de alto nível projetadas para resultados exponenciais." />
{/* Hero Section */}
<section className="relative pt-32 pb-24 lg:pt-48 lg:pb-40 overflow-hidden">
<div className="brand-glow top-0 left-1/4 h-[400px] w-[400px] bg-brand/30" />
<div className="brand-glow bottom-1/4 right-0 h-[500px] w-[500px] bg-blue-500/20" />
<div className="absolute inset-0 grid-pattern opacity-10 pointer-events-none" />
<div className="section-container relative z-10">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<motion.div
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }}
>
<div className="inline-flex items-center gap-2 px-3 py-1 bg-white/[0.03] border border-white/10 rounded-full text-xs font-semibold text-brand mb-8">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-brand opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-brand"></span>
</span>
Escalando empresas de elite e scale-ups
</div>
<h1 className="heading-hero mb-8">
Domine a <span className="text-transparent bg-clip-text bg-gradient-to-r from-brand to-brand-light">Autoridade Estrutural</span> da Busca.
</h1>
<p className="text-lg sm:text-xl text-slate-400 mb-12 max-w-xl leading-relaxed">
SEO estratégico e arquitetura de conteúdo para empresas de alto crescimento. Transformamos a busca orgânica em seu canal de receita mais consistente.
</p>
<div className="flex flex-col sm:flex-row gap-3">
<Link to="/contato" className="btn-primary group">
Agendar Chamada Estratégica
<ArrowRight size={16} className="group-hover:translate-x-1 transition-transform" />
</Link>
<Link to="/arquivo" className="btn-outline">
Ver Estudos de Caso
</Link>
</div>
<div className="mt-12 flex items-center gap-10">
<div className="flex flex-col">
<span className="text-3xl font-bold text-white tracking-tighter">480%</span>
<span className="text-[10px] uppercase tracking-[0.2em] font-bold text-slate-500">Crescimento Médio</span>
</div>
<div className="h-10 w-px bg-white/10" />
<div className="flex flex-col">
<span className="text-3xl font-bold text-white tracking-tighter">1.2M+</span>
<span className="text-[10px] uppercase tracking-[0.2em] font-bold text-slate-500">Tráfego Escalado</span>
</div>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 1, delay: 0.2 }}
className="glass-card p-4 lg:p-8 relative"
>
<div className="absolute top-0 right-0 p-6">
<div className="flex items-center gap-2 text-brand font-bold text-xs uppercase tracking-widest">
<Activity size={14} /> Monitoramento ROI Ativo
</div>
</div>
<h3 className="text-lg font-bold mb-8 px-2 flex items-center gap-3">
<BarChart3 size={20} className="text-brand" />
Momentum de Tráfego Orgânico
</h3>
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={chartData}>
<defs>
<linearGradient id="colorTraffic" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#4f46e5" stopOpacity={0.3}/>
<stop offset="95%" stopColor="#4f46e5" stopOpacity={0}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="rgba(255,255,255,0.05)" />
<XAxis
dataKey="name"
axisLine={false}
tickLine={false}
tick={{fill: 'rgba(255,255,255,0.3)', fontSize: 10}}
dy={10}
/>
<YAxis hide />
<Tooltip
contentStyle={{ backgroundColor: '#0f172a', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', fontSize: '10px' }}
itemStyle={{ color: '#818cf8', fontWeight: 'bold' }}
/>
<Area
type="monotone"
dataKey="traffic"
stroke="#4f46e5"
strokeWidth={3}
fillOpacity={1}
fill="url(#colorTraffic)"
/>
</AreaChart>
</ResponsiveContainer>
</div>
<div className="mt-8 grid grid-cols-3 gap-4 pt-8 border-t border-white/5">
{[
{ label: 'Visibilidade SERP', value: '+142%', color: 'text-emerald-500' },
{ label: 'Conversão', value: '+24%', color: 'text-blue-500' },
{ label: 'Eficiência Crawl', value: '99.8%', color: 'text-brand' }
].map((stat, i) => (
<div key={i} className="text-center">
<p className="text-[10px] uppercase font-bold text-slate-600 mb-1">{stat.label}</p>
<p className={`text-sm font-bold ${stat.color}`}>{stat.value}</p>
</div>
))}
</div>
</motion.div>
</div>
</div>
</section>
{/* Social Proof */}
<section className="py-12 border-y border-white/5 bg-white/[0.01]">
<div className="section-container">
<div className="flex flex-wrap items-center justify-center gap-12 lg:justify-between opacity-50 grayscale hover:grayscale-0 transition-all">
{logos.map((logo, i) => (
<span key={i} className="text-2xl font-black tracking-tighter text-slate-500 hover:text-white cursor-default">
{logo.name}
</span>
))}
</div>
</div>
</section>
{/* Services Section */}
<section id="servicos" className="py-spacing-section">
<div className="section-container">
<div className="text-center max-w-3xl mx-auto mb-24">
<h2 className="heading-section mb-6">Nossa Metodologia Central</h2>
<p className="text-slate-400 text-lg leading-relaxed">
Não apenas otimizamos meta tags. Reconstrutímos a autoridade estratégica do seu site usando modelagem de dados proprietária e arquitetura de conteúdo de alta densidade.
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
{services.map((service, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 }}
className="glass-card glass-card-hover p-8 group"
>
<div className={`h-12 w-12 rounded-xl bg-white/[0.03] border border-white/10 flex items-center justify-center mb-6 group-hover:scale-110 group-hover:bg-brand group-hover:text-white transition-all`}>
<service.icon size={22} className={service.color + " group-hover:text-white transition-colors"} />
</div>
<h3 className="text-xl font-bold mb-4">{service.title}</h3>
<p className="text-slate-500 text-sm leading-relaxed mb-6">
{service.description}
</p>
<Link to="/arquivo" className="flex items-center gap-2 text-xs font-bold text-brand uppercase tracking-widest">
Ver Protocolo <ArrowRight size={14} />
</Link>
</motion.div>
))}
</div>
</div>
</section>
{/* Case Studies */}
<section className="py-spacing-section bg-white/[0.01] border-y border-white/5 relative overflow-hidden">
<div className="brand-glow top-0 right-0 h-[600px] w-[600px] bg-brand/10" />
<div className="section-container relative z-10">
<div className="flex flex-col md:flex-row justify-between items-end gap-10 mb-20">
<div>
<h2 className="heading-section mb-4">Resultados Estratégicos</h2>
<p className="text-slate-500 max-w-xl">
Resultados reais dos protocolos de otimização deep-tech que implementamos para clientes SaaS e Enterprise de elite.
</p>
</div>
<Link to="/arquivo" className="btn-outline">
Explorar Todos os Cases
</Link>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{featuredArticles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</div>
</div>
</section>
{/* Process Section */}
<section className="py-spacing-section">
<div className="section-container">
<div className="grid lg:grid-cols-2 gap-20 items-center">
<div className="relative">
<div className="absolute -left-10 top-1/2 -translate-y-1/2 w-px h-full bg-white/10" />
<div className="space-y-16">
{[
{ step: '01', title: 'Auditoria Deep-Tech', desc: 'Análise abrangente de indexação e mapeamento de vulnerabilidades estruturais.' },
{ step: '02', title: 'Arquitetura de Autoridade', desc: 'Modelagem estratégica de tópicos e extração semântica de alta intenção.' },
{ step: '03', title: 'Execução de Precisão', desc: 'Implantação de infraestrutura otimizada e módulos de conteúdo engenheirados.' },
{ step: '04', title: 'Escalabilidade Exponencial', desc: 'Iteração contínua baseada em atribuição de performance em tempo real.' }
].map((item, i) => (
<div key={i} className="flex gap-8 group">
<span className="text-4xl font-black text-white/10 group-hover:text-brand transition-colors duration-500">{item.step}</span>
<div>
<h4 className="text-xl font-bold mb-2 group-hover:text-white transition-colors">{item.title}</h4>
<p className="text-slate-500 max-w-md">{item.desc}</p>
</div>
</div>
))}
</div>
</div>
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="p-12 glass-card relative overflow-hidden"
>
<div className="absolute top-0 right-0 w-32 h-32 bg-brand/20 blur-[80px]" />
<Quote size={40} className="text-brand mb-8 opacity-50" />
<p className="text-2xl font-semibold text-white leading-relaxed mb-12 italic">
"A transição de uma agência genérica para a abordagem técnica da Apex foi o catalisador para o nosso maior trimestre de receita até hoje."
</p>
<div className="flex items-center gap-4">
<div className="h-14 w-14 rounded-full bg-brand/20 border border-brand/40 overflow-hidden">
<img src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop" alt="Founder" />
</div>
<div>
<p className="font-bold text-white uppercase tracking-tight">Marcus Thorne</p>
<p className="text-xs text-brand uppercase font-bold tracking-widest">CMO // CloudScale Analytics</p>
</div>
</div>
<div className="mt-12 flex gap-4 pt-10 border-t border-white/5">
<div className="flex flex-col">
<span className="text-2xl font-bold text-white tracking-tighter">300%</span>
<span className="text-[10px] uppercase font-bold text-slate-600">Crescimento</span>
</div>
<div className="flex flex-col ml-12">
<span className="text-2xl font-bold text-white tracking-tighter">180K</span>
<span className="text-[10px] uppercase font-bold text-slate-600">Novos Leads</span>
</div>
</div>
</motion.div>
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-spacing-section pb-24">
<div className="section-container">
<div className="glass-card p-12 lg:p-24 relative overflow-hidden text-center bg-gradient-to-br from-brand/10 via-transparent to-blue-500/10">
<div className="brand-glow top-0 left-0 h-full w-full bg-brand/5 blur-[120px]" />
<div className="relative z-10 max-w-3xl mx-auto">
<h2 className="text-4xl lg:text-6xl font-bold tracking-tight text-white mb-8">
Pronto para escalar sua <span className="italic block mt-2 text-brand-light">Autoridade Orgânica?</span>
</h2>
<p className="text-slate-400 text-lg mb-12 opacity-80">
Junte-se a um coletivo de elite formado por empresas de alto crescimento que escalam receita através de SEO de precisão.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-6">
<Link to="/contato" className="btn-primary min-w-[240px] !h-14 !text-sm">
Solicitar Briefing Estratégico
</Link>
<p className="text-xs font-mono uppercase tracking-[0.2em] text-slate-600">
Vagas limitadas para estratégia Q3.
</p>
</div>
</div>
</div>
</div>
</section>
</div>
);
}

View file

@ -0,0 +1,97 @@
import { motion } from 'motion/react';
import { Link } from 'react-router-dom';
import SEO from '../components/SEO';
import { BarChart3, Database, Search, FileCheck, ArrowRight, TrendingUp } from 'lucide-react';
export default function Methodology() {
const steps = [
{
icon: Database,
title: "Inteligência de Dados",
desc: "Analisamos mais de 1 milhão de SERPs mensais para identificar padrões sutis de mudança nos algoritmos do Google, Anthropic e OpenAI."
},
{
icon: Search,
title: "Análise Semântica",
desc: "Nossos modelos proprietários identificam quais entidades e contextos estão gerando os maiores ganhos de autoridade orgânica."
},
{
icon: BarChart3,
title: "Teste Empírico de Campo",
desc: "Implementamos hipóteses em redes e ambientes controlados antes de documentá-las como protocolos recomendados."
},
{
icon: FileCheck,
title: "Validação Estratégica",
desc: "Cada insight é revisado por estrategistas com mais de uma década de experiência em busca de alto ticket."
}
];
return (
<>
<SEO
title="Nossa Metodologia | Apex SEO"
description="Saiba como a Apex cria relatórios de SEO de alta performance e análises técnicas. Dados reais, testes empíricos e transparência."
/>
<section className="pt-32 pb-32 bg-black relative overflow-hidden">
<div className="brand-glow top-0 right-1/4 h-[400px] w-[400px] bg-brand/10" />
<div className="section-container relative z-10">
<div className="max-w-4xl mb-24">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<span className="text-brand font-bold uppercase tracking-widest text-xs mb-6 block">Protocolo de Qualidade</span>
<h1 className="text-5xl sm:text-7xl lg:text-8xl font-bold text-white mb-10 tracking-tight">
Nossa<br />Metodologia.
</h1>
<p className="text-xl text-slate-400 leading-relaxed font-medium border-l-2 border-brand/30 pl-6 max-w-2xl">
O SEO deixou de ser uma ciência exata para se tornar uma mistura de ciência de dados e psicologia humana. Aqui está como deciframos o algoritmo.
</p>
</motion.div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{steps.map((step, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 }}
className="glass-card p-12 group hover:border-brand/30 transition-all"
>
<div className="h-14 w-14 rounded-2xl bg-brand/20 text-brand flex items-center justify-center mb-10 group-hover:bg-brand group-hover:text-white transition-all shadow-xl shadow-brand/10">
<step.icon size={26} />
</div>
<div>
<span className="text-white/5 font-bold text-8xl block mb-2 tracking-tighter leading-none select-none">0{i+1}</span>
<h3 className="text-2xl font-bold text-white mb-4 tracking-tight">{step.title}</h3>
<p className="text-slate-500 font-medium leading-relaxed">{step.desc}</p>
</div>
</motion.div>
))}
</div>
<div className="mt-24 p-12 glass-card bg-gradient-to-br from-brand/10 to-transparent border-brand/20 text-center max-w-3xl mx-auto">
<div className="h-12 w-12 bg-brand rounded-xl flex items-center justify-center text-white mx-auto mb-8 shadow-xl shadow-brand/20">
<TrendingUp size={24} />
</div>
<h4 className="text-2xl font-bold text-white mb-6 tracking-tight">Uplink de Dados Estratégicos</h4>
<p className="text-slate-500 font-medium mb-10 leading-relaxed">
Parceiros de agência e assinantes elite ganham acesso aos nossos datasets proprietários de flutuação de SERP e blueprints de auditoria estrutural.
</p>
<Link
to="/contato"
className="btn-primary group"
>
Acessar Briefing Premium
<ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
</Link>
</div>
</div>
</section>
</>
);
}

View file

@ -0,0 +1,59 @@
import { motion } from 'motion/react';
import { Link } from 'react-router-dom';
import { Home, ArrowLeft, Search } from 'lucide-react';
import SEO from '../components/SEO';
import Layout from '../components/Layout';
export default function NotFound() {
return (
<div className="min-h-[80vh] flex items-center justify-center pt-20">
<SEO
title="404 - Página Não Encontrada"
description="A página que você está procurando não existe ou foi movida."
/>
<div className="max-w-xl w-full px-6 text-center">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="mb-8"
>
<div className="inline-flex h-24 w-24 items-center justify-center rounded-3xl bg-brand/10 text-brand mb-8 rotate-12">
<Search size={48} />
</div>
<h1 className="text-8xl sm:text-9xl font-display font-black text-white mb-8 tracking-[-0.04em] leading-[0.9]">404</h1>
<h2 className="text-2xl font-display font-black text-white uppercase tracking-widest mb-6">Página Não Encontrada</h2>
<p className="text-slate-500 text-lg leading-relaxed mb-10 font-medium max-w-md mx-auto border-l-2 border-white/5 pl-6 text-left">
O conteúdo que você busca parece ter sido reindexado ou movido para uma nova dimensão semântica. Vamos te levar de volta para um lugar seguro.
</p>
</motion.div>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<Link
to="/"
className="btn-primary w-full sm:w-auto inline-flex items-center gap-3 group"
>
<Home size={18} />
Voltar para Home
</Link>
<button
onClick={() => window.history.back()}
className="btn-outline w-full sm:w-auto inline-flex items-center gap-3 group"
>
<ArrowLeft size={18} />
Página Anterior
</button>
</div>
<div className="mt-20 pt-10 border-t border-white/5">
<p className="text-[10px] font-bold text-slate-600 uppercase tracking-[0.3em] mb-8">Navegue pelas categorias</p>
<div className="flex flex-wrap justify-center gap-2">
{['Estratégia', 'IA', 'Autoridade', 'Técnico'].map((cat) => (
<span key={cat} className="px-4 py-2 bg-white/[0.03] border border-white/10 text-slate-500 rounded-lg text-xs font-bold uppercase tracking-widest">{cat}</span>
))}
</div>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,40 @@
import { motion } from 'motion/react';
import SEO from '../components/SEO';
export default function Privacy() {
return (
<>
<SEO
title="Privacy Policy | Apex SEO"
description="Learn how we handle your data and our commitment to architectural transparency."
/>
<section className="pt-36 pb-24 bg-black">
<div className="section-container max-w-4xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-20 border-l border-brand pl-8 py-2"
>
<span className="text-brand text-[10px] font-bold uppercase tracking-widest mb-4 block italic">Legal Protocol // v1.2</span>
<h1 className="text-4xl sm:text-6xl font-bold text-white mb-6 tracking-tight">Privacy <br/><span className="text-brand">Protocols.</span></h1>
<p className="text-[10px] font-mono text-slate-600 uppercase tracking-widest font-bold">Last Updated: May 2026</p>
</motion.div>
<div className="markdown-body text-slate-400">
<p>Your privacy is paramount at Apex. It is our policy to respect your privacy regarding any information we may collect through our website and other platforms we own and operate.</p>
<h2>1. Information Gathering</h2>
<p>We only ask for personal information when it is vital to provide you with a specific service, such as strategy briefs or newsletter subscriptions. We collect it by fair and lawful means, with your knowledge and consent.</p>
<h2>2. Data Utilization</h2>
<p>We do not share any personally identifying information publicly or with third-parties, except when required by law. Cookie usage is strictly limited to analytical traffic monitoring (Google Analytics) to refine your consultative experience.</p>
<h2>3. Security Standards</h2>
<p>We only retain collected information for as long as necessary to provide you with your requested service. What data we store, we protect within commercially acceptable means to prevent loss and theft, as well as unauthorized access, disclosure, copying, use, or modification.</p>
</div>
</div>
</section>
</>
);
}

View file

@ -0,0 +1,38 @@
import { motion } from 'motion/react';
import SEO from '../components/SEO';
export default function Terms() {
return (
<>
<SEO
title="Terms of Service | Apex SEO"
description="Review the terms of use for our growth platforms and digital strategy briefings."
/>
<section className="pt-36 pb-24 bg-black">
<div className="section-container max-w-4xl">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-20 border-l border-brand pl-8 py-2"
>
<span className="text-brand text-[10px] font-bold uppercase tracking-widest mb-4 block italic">Structure Logs // v2.1</span>
<h1 className="text-4xl sm:text-6xl font-bold text-white mb-6 tracking-tight">Terms of <br/><span className="text-brand">Service.</span></h1>
<p className="text-[10px] font-mono text-slate-600 uppercase tracking-widest font-bold">Last Stamp: May 2026</p>
</motion.div>
<div className="markdown-body text-slate-400">
<h2>1. Agreement</h2>
<p>By accessing the Apex SEO website, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws.</p>
<h2>2. Content License</h2>
<p>All published content is protected by international copyright laws. Citation of brief excerpts is permitted provided a direct link of attribution to the original source is included. Commercial utilization of our datasets without prior explicit authorization is strictly prohibited.</p>
<h2>3. Disclaimer</h2>
<p>The materials on Apex SEO's website are provided on an 'as is' basis. Apex SEO makes no warranties, expressed or implied, and hereby disclaims and negates all other warranties including, without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.</p>
</div>
</div>
</section>
</>
);
}

View file

@ -0,0 +1,57 @@
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: (process.env.GEMINI_API_KEY as string) });
export async function translateText(text: string, targetLang: string) {
if (!text) return text;
try {
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Translate the following text to ${targetLang}. Keep the markdown formatting and don't add any extra commentary. Just the translation: \n\n${text}`,
});
return response.text || text;
} catch (error) {
console.error("Translation error:", error);
return text;
}
}
export async function translateArticle(article: any, targetLang: string) {
try {
const prompt = `Translate the following article object to ${targetLang}.
Return a JSON object with the same structure.
Only translate the 'title', 'excerpt', 'content', 'metaTitle', and 'metaDescription' fields.
Keep the 'id', 'slug', 'category', 'publishedAt', 'image', 'readTime', 'author', 'tags', and 'lang' (set lang to ${targetLang}) fields exactly as they are.
Article to translate:
${JSON.stringify({
title: article.title,
excerpt: article.excerpt,
content: article.content,
metaTitle: article.metaTitle,
metaDescription: article.metaDescription
})}
`;
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: prompt,
config: {
responseMimeType: "application/json",
}
});
const translatedParts = JSON.parse(response.text || "{}");
return {
...article,
...translatedParts,
lang: targetLang
};
} catch (error) {
console.error("Article translation error:", error);
return article;
}
}

View file

@ -0,0 +1,54 @@
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' });
export async function generateSEOArticle(topic: string) {
try {
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Crie um artigo de blog profissional e altamente otimizado para SEO sobre o seguinte tópico: ${topic}.
O artigo deve estar em Português do Brasil.
Retorne APENAS um JSON no seguinte formato:
{
"title": "Título otimizado com palavra-chave",
"excerpt": "Um resumo atraente do artigo",
"content": "Conteúdo completo em Markdown, com H2, H3, bullets e parágrafos curtos",
"category": "SEO para Iniciantes",
"metaTitle": "Meta title SEO",
"metaDescription": "Meta description persuasiva",
"tags": ["tag1", "tag2"],
"readTime": "X min"
}
Certifique-se de que o conteúdo seja profundo, profissional e focado em autoridade.`,
config: {
responseMimeType: "application/json"
}
});
return JSON.parse(response.text || '{}');
} catch (error) {
console.error("Erro ao gerar artigo:", error);
throw error;
}
}
export async function summarizeSearchResults(query: string, results: any[]) {
try {
const titles = results.map(r => r.title).join(', ');
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `O usuário pesquisou por "${query}" em um blog de autoridade em SEO.
Encontramos os seguintes artigos relacionados: ${titles}.
Crie um resumo muito curto (máximo 200 caracteres) explicando por que esses tópicos são importantes para a estratégia de SEO do usuário.
Seja direto, profissional e encorajador. Use Português do Brasil.`,
});
return response.text;
} catch (error) {
console.error("Erro ao resumir busca:", error);
return null;
}
}

77
Template-04/src/types.ts Normal file
View file

@ -0,0 +1,77 @@
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
export type Category =
| 'Estratégia'
| 'Técnico'
| 'Autoridade'
| 'Negócios'
| 'SEO para Iniciantes'
| 'SEO Técnico'
| 'SEO On Page'
| 'SEO Off Page'
| 'Link Building'
| 'Ferramentas de SEO'
| 'SEO para E-commerce'
| 'SEO para Blogs'
| 'Atualizações do Google';
export interface Author {
name: string;
role: string;
avatar: string;
}
export interface Article {
id: string;
slug: string;
title: string;
excerpt: string;
content: string;
category: Category;
author: Author;
publishedAt: string;
image: string;
readTime: string;
metaTitle: string;
metaDescription: string;
tags: string[];
lang: 'pt-br' | 'en' | 'es';
}
export interface Translations {
newsletter: string;
subscribe: string;
readMore: string;
latest: string;
allArticles: string;
sections: string;
publishing: string;
contact: string;
about: string;
privacy: string;
terms: string;
featured: string;
recent: string;
explore: string;
search: string;
viewAll: string;
manifesto: string;
methodology: string;
heroTitle: string;
heroSubtitle: string;
heroDescription: string;
readManifesto: string;
publishedIn: string;
newsletterTitle: string;
newsletterDescription: string;
emailPlaceholder: string;
subscribeButton: string;
trendsTitle: string;
footerNewsletterTitle: string;
footerNewsletterSubtitle: string;
footerNewsletterInput: string;
footerNewsletterDisclaimer: string;
}

26
Template-04/tsconfig.json Normal file
View file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}

View file

@ -0,0 +1,24 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import {defineConfig, loadEnv} from 'vite';
export default defineConfig(({mode}) => {
const env = loadEnv(mode, '.', '');
return {
plugins: [react(), tailwindcss()],
define: {
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
server: {
// HMR is disabled in AI Studio via DISABLE_HMR env var.
// Do not modify—file watching is disabled to prevent flickering during agent edits.
hmr: process.env.DISABLE_HMR !== 'true',
},
};
});