Primeira versão templates tecnologia
This commit is contained in:
commit
14dc6d0fca
85 changed files with 26670 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
14
Template-01/README.md
Normal file
14
Template-01/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Nexus Tech Blog
|
||||
Very professional static blog.
|
||||
|
||||
## Setup
|
||||
Install dependencies:
|
||||
`npm install`
|
||||
|
||||
## Technologies
|
||||
- React 18
|
||||
- Vite
|
||||
- Tailwind CSS
|
||||
- Motion (framer-motion)
|
||||
- Lucide React
|
||||
- React Helmet Async (SEO)
|
||||
13
Template-01/index.html
Normal file
13
Template-01/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><rect width=%22100%22 height=%22100%22 rx=%2220%22 fill=%22%230070F3%22/><text y=%22.9em%22 font-size=%2280%22 x=%2250%%22 text-anchor=%22middle%22 fill=%22white%22 font-family=%22monospace%22 font-weight=%22bold%22>N</text></svg>" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
Template-01/metadata.json
Normal file
6
Template-01/metadata.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Nexus Tech Blog",
|
||||
"description": "A premium, multi-language static technology blog optimized for performance and SEO.",
|
||||
"requestFramePermissions": [],
|
||||
"majorCapabilities": []
|
||||
}
|
||||
5633
Template-01/package-lock.json
generated
Normal file
5633
Template-01/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
39
Template-01/package.json
Normal file
39
Template-01/package.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port=3000 --host=0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.29.0",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.2",
|
||||
"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.15.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
4
Template-01/public/robots.txt
Normal file
4
Template-01/public/robots.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://nexus-blog.tech/sitemap.xml
|
||||
62
Template-01/public/sitemap.xml
Normal file
62
Template-01/public/sitemap.xml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<!-- Main Pages -->
|
||||
<url><loc>https://nexus-blog.tech/pt</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/es</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog</loc><priority>0.8</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog</loc><priority>0.8</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/es/blog</loc><priority>0.8</priority></url>
|
||||
|
||||
<!-- Categories -->
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/ai</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/code</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/startups</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/tools</loc><priority>0.6</priority></url>
|
||||
|
||||
<!-- PT Posts -->
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/futuro-ia-generativa-2024</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/typescript-moderno-guia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/escalando-startups-bootstrap</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/ferramentas-indispensaveis-dev</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/web-components-nativos</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/clean-architecture-js</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/seguranca-ciber-ia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/trabalho-remoto-fatos</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/design-system-flexivel</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/webgpu-ias-locais</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/precificacao-saas</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/nextjs-14-enterprise</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/etica-algoritmica</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/figma-dev-mode</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/rust-backends</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/growth-hacking-real</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/documentacao-agil</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/medicina-ia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/testes-frontend</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/nomadismo-dev</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/sustentabilidade-tech</loc><priority>0.7</priority></url>
|
||||
|
||||
<!-- EN Posts -->
|
||||
<url><loc>https://nexus-blog.tech/en/blog/future-generative-ai-2024</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/modern-typescript-guide</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/scaling-startups-bootstrap</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/essential-dev-tools</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/native-web-components</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/clean-architecture-js</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/ai-cybersecurity</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/remote-work-facts</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/flexible-design-system</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/webgpu-local-ai</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/saas-pricing</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/nextjs-14-enterprise</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/algorithmic-ethics</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/figma-dev-mode</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/rust-backends</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/real-growth-hacking</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/agile-documentation</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/medicine-ai</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/frontend-testing</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/dev-nomadism</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/tech-sustainability</loc><priority>0.7</priority></url>
|
||||
</urlset>
|
||||
51
Template-01/src/App.tsx
Normal file
51
Template-01/src/App.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React, { lazy, Suspense } from 'react';
|
||||
import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { Layout } from './components/Layout';
|
||||
import Home from './pages/Home';
|
||||
import BlogList from './pages/BlogList';
|
||||
import PostDetail from './pages/PostDetail';
|
||||
|
||||
// Wrapper to handle language validation and layout injection
|
||||
const LangWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { lang } = useParams<{ lang: string }>();
|
||||
const validLangs = ['en', 'pt', 'es'];
|
||||
|
||||
if (!lang || !validLangs.includes(lang)) {
|
||||
return <Navigate to="/pt" replace />;
|
||||
}
|
||||
|
||||
return <Layout>{children}</Layout>;
|
||||
};
|
||||
|
||||
const Contact = lazy(() => import('./pages/Contact'));
|
||||
const Legal = lazy(() => import('./pages/Legal'));
|
||||
|
||||
import { ScrollToTop } from './components/ScrollToTop';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<BrowserRouter>
|
||||
<ScrollToTop />
|
||||
<Suspense fallback={<div className="min-h-screen bg-white flex items-center justify-center font-mono text-[10px] uppercase tracking-widest text-tech-muted animate-pulse">loading_module...</div>}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/pt" replace />} />
|
||||
|
||||
<Route path="/:lang" element={<LangWrapper><Home /></LangWrapper>} />
|
||||
<Route path="/:lang/blog" element={<LangWrapper><BlogList /></LangWrapper>} />
|
||||
<Route path="/:lang/blog/category/:category" element={<LangWrapper><BlogList /></LangWrapper>} />
|
||||
<Route path="/:lang/blog/:slug" element={<LangWrapper><PostDetail /></LangWrapper>} />
|
||||
|
||||
<Route path="/:lang/contact" element={<LangWrapper><Contact /></LangWrapper>} />
|
||||
<Route path="/:lang/privacy" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
<Route path="/:lang/terms" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
<Route path="/:lang/ethics" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
|
||||
<Route path="*" element={<Navigate to="/pt" replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
</HelmetProvider>
|
||||
);
|
||||
}
|
||||
186
Template-01/src/components/Layout.tsx
Normal file
186
Template-01/src/components/Layout.tsx
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import React from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Language } from '../types';
|
||||
import { Globe, Menu, X, Github, Twitter, Linkedin, Terminal, Command } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { lang, t, changeLanguage } = useI18n();
|
||||
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
||||
const [isScrolled, setIsScrolled] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleScroll = () => setIsScrolled(window.scrollY > 10);
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const languages: { code: Language; label: string }[] = [
|
||||
{ code: 'pt', label: 'PT' },
|
||||
{ code: 'en', label: 'EN' },
|
||||
{ code: 'es', label: 'ES' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col font-sans bg-white">
|
||||
{/* Engineering Navigation */}
|
||||
<header
|
||||
className={cn(
|
||||
"fixed top-0 left-0 right-0 z-50 transition-all duration-300 border-b",
|
||||
isScrolled ? "bg-white/95 backdrop-blur-md border-tech-border py-3 shadow-xs" : "bg-transparent border-transparent py-5"
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto px-6 lg:px-12 flex items-center justify-between">
|
||||
<Link to={`/${lang}`} className="flex items-center gap-2 group">
|
||||
<div className="w-8 h-8 rounded bg-tech-text flex items-center justify-center text-white font-mono font-bold text-lg group-hover:bg-tech-primary transition-colors">
|
||||
N
|
||||
</div>
|
||||
<span className="font-bold text-lg tracking-tighter text-tech-text uppercase font-mono">
|
||||
nexus_
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<nav className="hidden md:flex items-center gap-10">
|
||||
<div className="flex gap-8">
|
||||
<Link to={`/${lang}`} className="text-[11px] font-mono font-bold text-tech-muted hover:text-tech-text transition-colors uppercase tracking-widest">{t.nav.home}</Link>
|
||||
<Link to={`/${lang}/blog/category/ai`} className="text-[11px] font-mono font-bold text-tech-muted hover:text-tech-primary transition-colors uppercase tracking-widest">{t.blog.categories.ai}</Link>
|
||||
<Link to={`/${lang}/blog/category/code`} className="text-[11px] font-mono font-bold text-tech-muted hover:text-tech-primary transition-colors uppercase tracking-widest">{t.blog.categories.code}</Link>
|
||||
<Link to={`/${lang}/blog/category/startups`} className="text-[11px] font-mono font-bold text-tech-muted hover:text-tech-primary transition-colors uppercase tracking-widest">{t.blog.categories.startups}</Link>
|
||||
<Link to={`/${lang}/blog/category/tools`} className="text-[11px] font-mono font-bold text-tech-muted hover:text-tech-primary transition-colors uppercase tracking-widest">{t.blog.categories.tools}</Link>
|
||||
</div>
|
||||
|
||||
<div className="h-4 w-px bg-tech-border" />
|
||||
|
||||
<div className="flex items-center gap-1 bg-tech-surface p-1 rounded border border-tech-border">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => changeLanguage(l.code)}
|
||||
className={cn(
|
||||
"text-[9px] font-mono font-bold px-2 py-1 rounded transition-all",
|
||||
lang === l.code
|
||||
? "bg-white text-tech-text shadow-sm ring-1 ring-tech-border"
|
||||
: "text-tech-muted hover:text-tech-text"
|
||||
)}
|
||||
>
|
||||
{l.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="bg-tech-text text-white px-5 py-2 font-mono font-bold text-[10px] uppercase tracking-widest shadow-lg hover:bg-slate-800 transition-all active:scale-95 flex items-center gap-2">
|
||||
<Terminal size={12} /> ./subscribe
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<button
|
||||
className="md:hidden text-slate-900"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
>
|
||||
{isMenuOpen ? <X size={20} /> : <Menu size={20} />}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: '100%' }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: '100%' }}
|
||||
className="fixed inset-0 z-40 bg-white pt-24 px-6 md:hidden"
|
||||
>
|
||||
<div className="flex flex-col gap-6">
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}`} className="text-2xl font-mono font-bold text-slate-900 flex items-center gap-2">
|
||||
<Command size={20} /> {t.nav.home}
|
||||
</Link>
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog`} className="text-2xl font-mono font-bold text-slate-900 flex items-center gap-2">
|
||||
<Terminal size={20} /> {t.nav.blog}
|
||||
</Link>
|
||||
<div className="flex flex-col gap-4 pl-6 border-l border-slate-100">
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog/category/ai`} className="text-lg font-mono font-medium text-slate-500 hover:text-tech-primary transition-colors uppercase tracking-tight">{t.blog.categories.ai}</Link>
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog/category/code`} className="text-lg font-mono font-medium text-slate-500 hover:text-tech-primary transition-colors uppercase tracking-tight">{t.blog.categories.code}</Link>
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog/category/startups`} className="text-lg font-mono font-medium text-slate-500 hover:text-tech-primary transition-colors uppercase tracking-tight">{t.blog.categories.startups}</Link>
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog/category/tools`} className="text-lg font-mono font-medium text-slate-500 hover:text-tech-primary transition-colors uppercase tracking-tight">{t.blog.categories.tools}</Link>
|
||||
</div>
|
||||
<div className="h-px w-full bg-slate-100 my-4" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => { changeLanguage(l.code); setIsMenuOpen(false); }}
|
||||
className={cn(
|
||||
"text-xs font-mono font-bold px-4 py-2 rounded border",
|
||||
lang === l.code ? "bg-slate-900 text-white border-slate-900" : "text-slate-500 border-slate-200"
|
||||
)}
|
||||
>
|
||||
{l.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<main className="flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
{/* Structured Technical Footer */}
|
||||
<footer className="bg-white border-t border-tech-border pt-20 pb-12">
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-12 mb-20">
|
||||
<div className="col-span-1 md:col-span-2">
|
||||
<Link to={`/${lang}`} className="flex items-center gap-2 mb-6 group">
|
||||
<div className="w-8 h-8 rounded bg-tech-text flex items-center justify-center text-white font-mono font-bold text-lg group-hover:bg-tech-primary transition-colors">N</div>
|
||||
<span className="font-bold text-xl tracking-tighter text-tech-text uppercase font-mono">nexus_labs</span>
|
||||
</Link>
|
||||
<p className="text-tech-muted max-w-sm text-sm font-medium leading-relaxed mb-8">
|
||||
{t.footer.about}
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<a href="#" className="w-8 h-8 rounded bg-tech-surface border border-tech-border flex items-center justify-center text-tech-muted hover:text-tech-text hover:border-tech-text transition-all"><Twitter size={14} /></a>
|
||||
<a href="#" className="w-8 h-8 rounded bg-tech-surface border border-tech-border flex items-center justify-center text-tech-muted hover:text-tech-text hover:border-tech-text transition-all"><Github size={14} /></a>
|
||||
<a href="#" className="w-8 h-8 rounded bg-tech-surface border border-tech-border flex items-center justify-center text-tech-muted hover:text-tech-text hover:border-tech-text transition-all"><Linkedin size={14} /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-mono text-[10px] font-bold text-tech-muted mb-6 uppercase tracking-widest">./nodes</h4>
|
||||
<ul className="flex flex-col gap-3 text-tech-text text-sm font-bold uppercase tracking-tight">
|
||||
<li><Link to={`/${lang}/blog/category/ai`} className="hover:text-tech-primary transition-colors">AI_Core</Link></li>
|
||||
<li><Link to={`/${lang}/blog/category/code`} className="hover:text-tech-primary transition-colors">Dev_Stack</Link></li>
|
||||
<li><Link to={`/${lang}/blog/category/startups`} className="hover:text-tech-primary transition-colors">Bus_Logic</Link></li>
|
||||
<li><Link to={`/${lang}/blog/category/tools`} className="hover:text-tech-primary transition-colors">Lib_Tools</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-mono text-[10px] font-bold text-tech-muted mb-6 uppercase tracking-widest">./connect</h4>
|
||||
<ul className="flex flex-col gap-3 text-tech-text text-sm font-bold uppercase tracking-tight">
|
||||
<li><a href="#" className="hover:text-tech-primary transition-colors">{t.footer.newsletter}</a></li>
|
||||
<li><a href="#" className="hover:text-tech-primary transition-colors">API_RSS</a></li>
|
||||
<li><Link to={`/${lang}/contact`} className="hover:text-tech-primary transition-colors">{t.footer.contact}</Link></li>
|
||||
<li><Link to={`/${lang}/ethics`} className="hover:text-tech-primary transition-colors">Legal_Notice</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-6 border-t border-tech-border pt-10 text-tech-muted font-mono text-[10px] font-bold uppercase tracking-widest">
|
||||
<p>{t.footer.rights}</p>
|
||||
<div className="flex gap-8">
|
||||
<Link to={`/${lang}/terms`} className="hover:text-tech-text transition-colors">Terms_01</Link>
|
||||
<Link to={`/${lang}/ethics`} className="hover:text-tech-text transition-colors">Ethics_02</Link>
|
||||
<Link to={`/${lang}/privacy`} className="hover:text-tech-text transition-colors">Privacy_03</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
72
Template-01/src/components/Newsletter.tsx
Normal file
72
Template-01/src/components/Newsletter.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Send, Sparkles } from 'lucide-react';
|
||||
|
||||
export const Newsletter = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<section className="py-24 relative overflow-hidden bg-tech-surface border-t border-tech-border">
|
||||
<div className="container mx-auto px-6 lg:px-12 relative z-10">
|
||||
<div className="max-w-5xl mx-auto bg-tech-text rounded-lg overflow-hidden shadow-2xl border border-slate-800">
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-slate-900 border-b border-slate-800">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-3 h-3 rounded-full bg-slate-700" />
|
||||
<div className="w-3 h-3 rounded-full bg-slate-700" />
|
||||
<div className="w-3 h-3 rounded-full bg-slate-700" />
|
||||
</div>
|
||||
<div className="flex-grow text-center">
|
||||
<span className="text-[10px] font-mono font-bold text-slate-500 uppercase tracking-widest">nexus_newsletter.sh</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row">
|
||||
<div className="flex-1 p-8 lg:p-12 border-b md:border-b-0 md:border-r border-slate-800">
|
||||
<div className="inline-flex items-center gap-2 px-2 py-0.5 bg-tech-primary/10 rounded text-tech-primary text-[10px] font-mono font-bold mb-6 border border-tech-primary/20">
|
||||
<Sparkles size={12} />
|
||||
<span>TECHNICAL_FEED</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold mb-4 text-white tracking-tight">
|
||||
Nexus Intelligence Report
|
||||
</h2>
|
||||
<p className="text-slate-400 text-sm leading-relaxed font-mono">
|
||||
> Receba semanalmente análises profundas sobre LLMs, Infraestrutura e Engenharia de Software.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-8 lg:p-12 flex flex-col justify-center bg-tech-text">
|
||||
<form
|
||||
className="w-full flex flex-col gap-4"
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between items-center text-[10px] font-mono font-bold text-slate-500">
|
||||
<span>INPUT_EMAIL</span>
|
||||
<span className="text-tech-primary">STABLE</span>
|
||||
</div>
|
||||
<input
|
||||
id="emailAddress"
|
||||
type="email"
|
||||
placeholder="user@nexus_blog.tech"
|
||||
className="w-full bg-slate-900 border border-slate-800 rounded px-4 py-3 text-slate-200 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="w-full bg-white hover:bg-slate-200 text-tech-text px-8 py-3 font-mono font-bold text-xs uppercase tracking-widest rounded transition-all shadow-md active:scale-[0.98] mt-2 flex items-center justify-center gap-2"
|
||||
>
|
||||
./subscribe.sh <Send size={14} />
|
||||
</button>
|
||||
<div className="flex items-center gap-4 mt-4">
|
||||
<div className="flex-grow h-px bg-slate-800" />
|
||||
<span className="text-[10px] font-mono text-slate-600">NULL_SPAM_POLICY</span>
|
||||
<div className="flex-grow h-px bg-slate-800" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
67
Template-01/src/components/PostCard.tsx
Normal file
67
Template-01/src/components/PostCard.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import React from 'react';
|
||||
import { Post } from '../types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { formatDate } from '../lib/utils';
|
||||
import { ArrowRight, Clock } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
interface PostCardProps {
|
||||
post: Post;
|
||||
}
|
||||
|
||||
export const PostCard: React.FC<PostCardProps> = ({ post }) => {
|
||||
const { lang } = useI18n();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="group h-full"
|
||||
>
|
||||
<Link to={`/${lang}/blog/${post.slug}`} className="tech-card group">
|
||||
<div className="relative aspect-video overflow-hidden bg-white border-b border-tech-border">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105 opacity-90 group-hover:opacity-100"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
<div className="absolute top-3 left-3">
|
||||
<span className="category-tag">
|
||||
{post.category}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 flex flex-col flex-grow">
|
||||
<div className="flex items-center gap-3 mb-3 font-mono text-[10px] font-bold text-tech-muted uppercase tracking-tighter">
|
||||
<span>{formatDate(post.date, lang)}</span>
|
||||
<span className="text-tech-border">/</span>
|
||||
<span className="flex items-center gap-1"><Clock size={10} /> {post.readingTime}</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-bold mb-2 text-tech-text group-hover:text-tech-primary transition-colors leading-tight">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
<p className="text-tech-muted text-sm line-clamp-2 leading-relaxed mb-6 font-medium">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
<div className="mt-auto flex items-center justify-between border-t border-tech-surface pt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-6 h-6 rounded bg-tech-surface flex items-center justify-center text-[10px] font-mono font-bold text-tech-muted border border-tech-border">
|
||||
{post.author[0]}
|
||||
</div>
|
||||
<span className="text-[10px] text-tech-muted font-mono font-bold uppercase">{post.author}</span>
|
||||
</div>
|
||||
<ArrowRight size={14} className="text-tech-border group-hover:text-tech-primary transition-all" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
127
Template-01/src/components/SEO.tsx
Normal file
127
Template-01/src/components/SEO.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
interface SEOProps {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
article?: boolean;
|
||||
author?: string;
|
||||
datePublished?: string;
|
||||
category?: string;
|
||||
}
|
||||
|
||||
export const SEO: React.FC<SEOProps> = ({
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
article,
|
||||
author = 'Nexus Tech Team',
|
||||
datePublished,
|
||||
category
|
||||
}) => {
|
||||
const siteName = 'Nexus Intelligence';
|
||||
const fullTitle = `${title} | ${siteName}`;
|
||||
const location = useLocation();
|
||||
const canonicalUrl = `https://nexus-blog.tech${location.pathname}`;
|
||||
const defaultImage = 'https://images.unsplash.com/photo-1677442136019-21780ecad995?q=80&w=2000';
|
||||
const ogImage = image || defaultImage;
|
||||
|
||||
const structuredData = article ? {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": title,
|
||||
"description": description,
|
||||
"image": ogImage,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": author
|
||||
},
|
||||
"datePublished": datePublished,
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": canonicalUrl
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": siteName,
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://nexus-blog.tech/logo.png"
|
||||
}
|
||||
}
|
||||
} : {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": siteName,
|
||||
"url": "https://nexus-blog.tech",
|
||||
"description": description
|
||||
};
|
||||
|
||||
const breadcrumbData = article ? {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": `https://nexus-blog.tech/${location.pathname.split('/')[1]}`
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "Blog",
|
||||
"item": `https://nexus-blog.tech/${location.pathname.split('/')[1]}/blog`
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 3,
|
||||
"name": title,
|
||||
"item": canonicalUrl
|
||||
}
|
||||
]
|
||||
} : null;
|
||||
|
||||
return (
|
||||
<Helmet
|
||||
htmlAttributes={{
|
||||
lang: location.pathname.split('/')[1] || 'pt'
|
||||
}}
|
||||
>
|
||||
{/* Basic Meta Tags */}
|
||||
<title>{fullTitle}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
|
||||
{/* Open Graph / Facebook */}
|
||||
<meta property="og:site_name" content={siteName} />
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
<meta property="og:title" content={fullTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:type" content={article ? 'article' : 'website'} />
|
||||
<meta property="og:image" content={ogImage} />
|
||||
{article && category && <meta property="article:section" content={category} />}
|
||||
{article && datePublished && <meta property="article:published_time" content={datePublished} />}
|
||||
|
||||
{/* Twitter */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:domain" content="nexus-blog.tech" />
|
||||
<meta name="twitter:url" content={canonicalUrl} />
|
||||
<meta name="twitter:title" content={fullTitle} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={ogImage} />
|
||||
|
||||
{/* Google Rich Results */}
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(structuredData)}
|
||||
</script>
|
||||
{breadcrumbData && (
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(breadcrumbData)}
|
||||
</script>
|
||||
)}
|
||||
</Helmet>
|
||||
);
|
||||
};
|
||||
12
Template-01/src/components/ScrollToTop.tsx
Normal file
12
Template-01/src/components/ScrollToTop.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const ScrollToTop = () => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
};
|
||||
33
Template-01/src/components/TableOfContents.tsx
Normal file
33
Template-01/src/components/TableOfContents.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Hash } from 'lucide-react';
|
||||
|
||||
interface TOCProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export const TableOfContents: React.FC<TOCProps> = ({ content }) => {
|
||||
const { t } = useI18n();
|
||||
const headings = content.split('\n').filter(line => line.startsWith('## ')).map(line => line.replace('## ', '').trim());
|
||||
|
||||
if (headings.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="hidden lg:block bg-white p-6 rounded-3xl border border-slate-200/60 shadow-sm min-w-[240px]">
|
||||
<h4 className="font-bold text-xs uppercase tracking-widest text-slate-900 mb-6 flex items-center gap-2">
|
||||
<Hash size={14} className="text-accent-blue" /> {t.blog.toc}
|
||||
</h4>
|
||||
<nav className="flex flex-col gap-3">
|
||||
{headings.map((heading) => (
|
||||
<a
|
||||
key={heading}
|
||||
href={`#${heading.toLowerCase().replace(/ /g, '-')}`}
|
||||
className="text-sm font-medium text-slate-500 hover:text-accent-blue transition-all border-l-2 border-slate-100 pl-4 hover:border-accent-blue py-1"
|
||||
>
|
||||
{heading}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
1500
Template-01/src/content/index.ts
Normal file
1500
Template-01/src/content/index.ts
Normal file
File diff suppressed because it is too large
Load diff
26
Template-01/src/hooks/useI18n.ts
Normal file
26
Template-01/src/hooks/useI18n.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { content } from '../content';
|
||||
import { Language } from '../types';
|
||||
|
||||
export function useI18n() {
|
||||
const { lang } = useParams<{ lang?: string }>();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const currentLang: Language = (lang as Language) || 'pt';
|
||||
const t = content.ui[currentLang];
|
||||
const posts = content.posts[currentLang];
|
||||
|
||||
const changeLanguage = (newLang: Language) => {
|
||||
const pathParts = location.pathname.split('/');
|
||||
pathParts[1] = newLang; // Replace the language segment
|
||||
navigate(pathParts.join('/'));
|
||||
};
|
||||
|
||||
return {
|
||||
lang: currentLang,
|
||||
t,
|
||||
posts,
|
||||
changeLanguage,
|
||||
};
|
||||
}
|
||||
107
Template-01/src/index.css
Normal file
107
Template-01/src/index.css
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
||||
|
||||
--color-tech-primary: #0070F3;
|
||||
--color-tech-secondary: #0ea5e9;
|
||||
--color-tech-accent: #06b6d4;
|
||||
--color-tech-surface: #f8fafc;
|
||||
--color-tech-border: #e2e8f0;
|
||||
--color-tech-text: #0f172a;
|
||||
--color-tech-muted: #64748b;
|
||||
--color-tech-glow: rgba(0, 112, 243, 0.15);
|
||||
|
||||
--animate-float: float 6s ease-in-out infinite;
|
||||
--animate-pulse-slow: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
--animate-fade-in: fadeIn 0.4s ease-out forwards;
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-white text-tech-text selection:bg-tech-primary/20 selection:text-tech-primary antialiased overflow-x-hidden;
|
||||
background-image: radial-gradient(var(--color-tech-border) 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-sans tracking-tight text-slate-950 font-bold;
|
||||
}
|
||||
|
||||
code {
|
||||
@apply font-mono bg-slate-100 px-1.5 py-0.5 rounded text-[0.9em] text-tech-primary;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.glass-card {
|
||||
@apply bg-white/70 backdrop-blur-xl border border-tech-border shadow-sm transition-all duration-300;
|
||||
}
|
||||
|
||||
.tech-card {
|
||||
@apply bg-white border border-tech-border rounded-lg transition-all duration-200 hover:border-tech-muted hover:shadow-lg hover:shadow-tech-glow flex flex-col h-full;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
@apply bg-clip-text text-transparent bg-linear-to-r from-tech-text to-tech-muted;
|
||||
}
|
||||
|
||||
.blob {
|
||||
@apply absolute rounded-full filter blur-[100px] opacity-20 pointer-events-none bg-tech-primary;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
@apply text-tech-muted leading-relaxed text-base font-sans;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
@apply text-2xl font-bold mt-12 mb-6 text-tech-text tracking-tight border-b border-tech-border pb-4;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
@apply text-xl font-bold mt-8 mb-4 text-tech-text tracking-tight;
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
@apply mb-6;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
@apply bg-slate-900 text-slate-100 p-6 rounded-xl overflow-x-auto my-8 font-mono text-sm shadow-xl;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
@apply border-l-2 border-tech-text pl-6 py-2 my-8 italic text-tech-muted bg-tech-surface;
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
@apply list-disc list-inside mb-6 space-y-2;
|
||||
}
|
||||
|
||||
.markdown-body ol {
|
||||
@apply list-decimal list-inside mb-6 space-y-2;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
.category-tag {
|
||||
@apply inline-flex items-center px-2 py-0.5 rounded bg-tech-surface border border-tech-border text-[10px] font-mono font-bold text-tech-muted uppercase tracking-wider;
|
||||
}
|
||||
|
||||
.btn-tech {
|
||||
@apply inline-flex items-center justify-center px-4 py-2 bg-tech-text text-white rounded font-mono text-xs font-bold uppercase tracking-widest transition-all hover:bg-slate-800 active:scale-95 shadow-sm;
|
||||
}
|
||||
}
|
||||
14
Template-01/src/lib/utils.ts
Normal file
14
Template-01/src/lib/utils.ts
Normal 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(dateString: string, lang: string) {
|
||||
return new Date(dateString).toLocaleDateString(lang, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
10
Template-01/src/main.tsx
Normal file
10
Template-01/src/main.tsx
Normal 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>,
|
||||
);
|
||||
87
Template-01/src/pages/BlogList.tsx
Normal file
87
Template-01/src/pages/BlogList.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { cn } from '../lib/utils';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
export default function BlogList() {
|
||||
const { category } = useParams<{ category?: string }>();
|
||||
const { lang, posts, t } = useI18n();
|
||||
|
||||
const validPosts = posts || [];
|
||||
const categories = ['all', 'ai', 'code', 'startups', 'tools'];
|
||||
const filteredPosts = category && category !== 'all'
|
||||
? validPosts.filter(p => p.category === category)
|
||||
: validPosts;
|
||||
|
||||
const activeCategory = category || 'all';
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO
|
||||
title={t.nav.blog}
|
||||
description="Explora nuestros artículos sobre tecnología, programación e inteligencia artificial."
|
||||
/>
|
||||
|
||||
<section className="py-24 lg:py-32 container mx-auto px-6 lg:px-12 bg-white min-h-screen">
|
||||
<header className="mb-20 max-w-4xl">
|
||||
<div className="inline-flex items-center gap-2 text-tech-primary text-[10px] font-mono font-bold uppercase tracking-[0.2em] mb-4">
|
||||
<div className="w-8 h-px bg-tech-primary" />
|
||||
<span>BLOG_ARCHIVE</span>
|
||||
</div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-4xl md:text-6xl font-extrabold mb-8 text-tech-text tracking-tighter"
|
||||
>
|
||||
{t.nav.blog}
|
||||
</motion.h1>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="flex flex-wrap gap-2"
|
||||
>
|
||||
{categories.map((cat) => (
|
||||
<Link
|
||||
key={cat}
|
||||
to={cat === 'all' ? `/${lang}/blog` : `/${lang}/blog/category/${cat}`}
|
||||
className={cn(
|
||||
"px-4 py-1.5 rounded font-mono text-[10px] font-bold uppercase tracking-widest transition-all border",
|
||||
activeCategory === cat
|
||||
? "bg-tech-text text-white border-tech-text shadow-md"
|
||||
: "bg-white border-tech-border text-tech-muted hover:border-tech-muted hover:text-tech-text hover:bg-tech-surface"
|
||||
)}
|
||||
>
|
||||
{t.blog.categories[cat as keyof typeof t.blog.categories]}
|
||||
</Link>
|
||||
))}
|
||||
</motion.div>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-0 border-t border-l border-tech-border">
|
||||
{filteredPosts.map((post, idx) => (
|
||||
<motion.div
|
||||
key={post.id}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: idx * 0.02 }}
|
||||
className="border-r border-b border-tech-border p-1"
|
||||
>
|
||||
<PostCard post={post} />
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredPosts.length === 0 && (
|
||||
<div className="text-center py-20 text-slate-400 font-mono text-xs font-bold uppercase tracking-widest border border-dashed border-slate-200 rounded-lg mt-10">
|
||||
[error] no_modules_found_in_category
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
132
Template-01/src/pages/Contact.tsx
Normal file
132
Template-01/src/pages/Contact.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowLeft, Send, Terminal, Mail, MapPin, Phone } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function Contact() {
|
||||
const { t, lang } = useI18n();
|
||||
|
||||
return (
|
||||
<div className="bg-white min-h-screen pt-32 pb-24">
|
||||
<SEO title={t.contact.title} description={t.contact.subtitle} />
|
||||
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<header className="max-w-4xl mb-20">
|
||||
<Link to={`/${lang}`} className="inline-flex items-center gap-2 text-tech-muted text-[10px] font-mono font-bold mb-8 hover:text-tech-text transition-colors uppercase tracking-widest">
|
||||
<ArrowLeft size={12} /> cd ..
|
||||
</Link>
|
||||
<div className="inline-flex items-center gap-2 text-tech-primary text-[10px] font-mono font-bold uppercase tracking-[0.2em] mb-4">
|
||||
<div className="w-8 h-px bg-tech-primary" />
|
||||
<span>COMMUNICATION_LINK</span>
|
||||
</div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-5xl md:text-7xl font-extrabold mb-8 text-tech-text tracking-tighter"
|
||||
>
|
||||
{t.contact.title}
|
||||
</motion.h1>
|
||||
<p className="text-xl text-tech-muted font-medium max-w-2xl leading-relaxed">
|
||||
{t.contact.subtitle}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-20">
|
||||
{/* Contact Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="bg-tech-surface border border-tech-border rounded-lg p-8 lg:p-12"
|
||||
>
|
||||
<form className="space-y-6" onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-mono font-bold text-tech-muted uppercase">{t.contact.form.name}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full bg-white border border-tech-border rounded px-4 py-3 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-sm"
|
||||
placeholder="Ex: Alex Nexus"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-mono font-bold text-tech-muted uppercase">{t.contact.form.email}</label>
|
||||
<input
|
||||
type="email"
|
||||
className="w-full bg-white border border-tech-border rounded px-4 py-3 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-sm"
|
||||
placeholder="alex@nexus-blog.tech"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-mono font-bold text-tech-muted uppercase">{t.contact.form.subject}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full bg-white border border-tech-border rounded px-4 py-3 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-sm"
|
||||
placeholder="Support / Inquiry / Collaboration"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-mono font-bold text-tech-muted uppercase">{t.contact.form.message}</label>
|
||||
<textarea
|
||||
rows={5}
|
||||
className="w-full bg-white border border-tech-border rounded px-4 py-3 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-sm resize-none"
|
||||
placeholder="> Type your message here..."
|
||||
></textarea>
|
||||
</div>
|
||||
<button className="btn-tech !w-full !py-4">
|
||||
{t.contact.form.submit} <Send size={14} className="ml-2" />
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
|
||||
{/* Info Side */}
|
||||
<div className="space-y-12">
|
||||
<div>
|
||||
<h3 className="text-[10px] font-mono font-bold text-tech-muted uppercase tracking-widest mb-6 border-b border-tech-border pb-2">./{t.contact.info.title.toLowerCase().replace(' ', '_')}</h3>
|
||||
<ul className="space-y-6">
|
||||
<li className="flex items-start gap-4">
|
||||
<div className="w-10 h-10 rounded bg-tech-surface border border-tech-border flex items-center justify-center text-tech-primary">
|
||||
<Mail size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-[10px] font-mono font-bold text-tech-muted uppercase">Email</span>
|
||||
<span className="font-bold text-tech-text">{t.contact.info.email}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-4">
|
||||
<div className="w-10 h-10 rounded bg-tech-surface border border-tech-border flex items-center justify-center text-tech-primary">
|
||||
<MapPin size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-[10px] font-mono font-bold text-tech-muted uppercase">Location</span>
|
||||
<span className="font-bold text-tech-text">{t.contact.info.location}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="p-6 bg-tech-text text-white rounded-lg border border-slate-800">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Terminal size={16} className="text-tech-primary" />
|
||||
<span className="font-mono text-[10px] font-bold uppercase tracking-widest">{t.contact.status.title.toLowerCase().replace(' ', '_')}</span>
|
||||
</div>
|
||||
<div className="text-xs font-mono text-slate-400 mb-4 space-y-1">
|
||||
<div>{t.contact.status.nodes}: 12</div>
|
||||
<div>{t.contact.status.uptime}: 99.998%</div>
|
||||
<div>{t.contact.status.latency}: 45ms</div>
|
||||
</div>
|
||||
<div className="h-1 w-full bg-slate-900 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: '85%' }}
|
||||
transition={{ duration: 2, repeat: Infinity }}
|
||||
className="h-full bg-tech-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
140
Template-01/src/pages/Home.tsx
Normal file
140
Template-01/src/pages/Home.tsx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { Newsletter } from '../components/Newsletter';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowRight, Sparkles, Terminal, Cpu, Database, Code2 } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { formatDate } from '../lib/utils';
|
||||
|
||||
export default function Home() {
|
||||
const { t, posts, lang } = useI18n();
|
||||
const validPosts = posts || [];
|
||||
const recentPosts = validPosts.slice(0, 6);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SEO title={t.home.hero.title} description={t.home.hero.subtitle} />
|
||||
|
||||
{/* Technical Lab Hero Section */}
|
||||
<section className="pt-32 pb-20 relative border-b border-slate-100 bg-white">
|
||||
<div className="container mx-auto px-6 lg:px-12 relative z-10">
|
||||
<div className="max-w-5xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="inline-flex items-center gap-2 px-2 py-1 bg-tech-surface border border-tech-border rounded text-tech-muted text-[10px] font-mono font-bold mb-8 uppercase tracking-widest"
|
||||
>
|
||||
<div className="w-2 h-2 rounded-full bg-tech-primary animate-pulse" />
|
||||
<span>nexus_core_system v4.02 // stable</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-6xl md:text-8xl font-extrabold mb-8 text-slate-950 tracking-tighter leading-[0.9] lg:max-w-4xl"
|
||||
>
|
||||
{t.home.hero.title.replace('Nexus Tech Blog', 'NEXUS_ENGINEERING')}
|
||||
</motion.h1>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="flex flex-col md:flex-row gap-8 items-start mb-12"
|
||||
>
|
||||
<p className="text-xl text-slate-500 font-medium max-w-2xl leading-relaxed">
|
||||
{t.home.hero.subtitle}
|
||||
</p>
|
||||
|
||||
<div className="hidden lg:grid grid-cols-2 gap-4 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-[10px] font-mono font-bold text-tech-muted border border-tech-border p-2 rounded">
|
||||
<Cpu size={14} className="text-tech-primary" />
|
||||
<span>CPU_LOAD: 12%</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[10px] font-mono font-bold text-tech-muted border border-tech-border p-2 rounded">
|
||||
<Database size={14} className="text-tech-accent" />
|
||||
<span>MODELS: GPT-O1</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex flex-wrap gap-4"
|
||||
>
|
||||
<Link to={`/${lang}/blog`} className="btn-tech !px-8 !py-4 bg-slate-900 text-white !text-sm">
|
||||
Executar Exploração <ArrowRight size={16} className="ml-2" />
|
||||
</Link>
|
||||
<div className="flex items-center gap-3 px-6 py-4 bg-white border border-slate-200 rounded font-mono text-xs font-bold text-slate-500">
|
||||
<Terminal size={14} className="text-slate-400" />
|
||||
<span>LAST_DEPLOY: {new Date().toISOString().split('T')[0]}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Grid Lab - Featured Articles */}
|
||||
<section className="py-24 bg-white relative">
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<div className="flex items-center justify-between mb-16">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-tech-primary text-[10px] font-mono font-bold uppercase tracking-[0.2em] mb-4">
|
||||
<div className="w-8 h-px bg-tech-primary" />
|
||||
<span>FETCHED_MODULES</span>
|
||||
</div>
|
||||
<h2 className="text-4xl font-bold tracking-tight text-tech-text">{t.home.recent}</h2>
|
||||
</div>
|
||||
<Link to={`/${lang}/blog`} className="hidden md:flex items-center gap-2 text-xs font-mono font-bold text-tech-muted hover:text-tech-text transition-all border border-tech-border px-4 py-2 rounded">
|
||||
LIST_ALL.SH <ArrowRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-0 border-t border-l border-tech-border">
|
||||
{recentPosts.map((post) => (
|
||||
<div key={post.id} className="border-r border-b border-tech-border p-1 group">
|
||||
<PostCard post={post} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Categories Tech Selector */}
|
||||
<section className="py-24 bg-slate-50 border-y border-slate-200 overflow-hidden">
|
||||
<div className="container mx-auto px-6 lg:px-12 lg:flex items-center gap-20">
|
||||
<div className="max-w-sm mb-12 lg:mb-0">
|
||||
<h3 className="text-2xl font-bold mb-4 tracking-tight">Filtrar por Domínio</h3>
|
||||
<p className="text-slate-500 font-medium text-sm leading-relaxed">
|
||||
Explore tópicos específicos isolando as frequências de conhecimento.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 flex-grow">
|
||||
{[
|
||||
{ id: 'ai', icon: Sparkles, label: 'Inteligência Artificial' },
|
||||
{ id: 'code', icon: Code2, label: 'Engenharia de Software' },
|
||||
{ id: 'startups', icon: Cpu, label: 'Product & Startup' },
|
||||
{ id: 'tools', icon: Terminal, label: 'Technical Tools' }
|
||||
].map(cat => (
|
||||
<Link
|
||||
key={cat.id}
|
||||
to={`/${lang}/blog/category/${cat.id}`}
|
||||
className="p-6 bg-white border border-tech-border rounded hover:border-tech-primary hover:shadow-lg transition-all group"
|
||||
>
|
||||
<cat.icon size={24} className="mb-4 text-tech-muted group-hover:text-tech-primary transition-colors" />
|
||||
<span className="block font-mono text-[10px] font-bold text-tech-muted uppercase group-hover:text-tech-text">{cat.id}</span>
|
||||
<span className="block font-bold text-tech-text text-sm mt-1">{cat.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Newsletter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
Template-01/src/pages/Legal.tsx
Normal file
65
Template-01/src/pages/Legal.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowLeft, Shield, FileText, Scale } from 'lucide-react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
export default function Legal() {
|
||||
const { t, lang } = useI18n();
|
||||
const location = useLocation();
|
||||
|
||||
const isPrivacy = location.pathname.includes('privacy');
|
||||
const isTerms = location.pathname.includes('terms');
|
||||
const isEthics = location.pathname.includes('ethics');
|
||||
|
||||
let config = t.legal.privacy;
|
||||
let Icon = Shield;
|
||||
let code = "PRIVACY_PROTOCOL";
|
||||
|
||||
if (isTerms) {
|
||||
config = t.legal.terms;
|
||||
Icon = FileText;
|
||||
code = "SERVICE_TERMS";
|
||||
} else if (isEthics) {
|
||||
config = t.legal.ethics;
|
||||
Icon = Scale;
|
||||
code = "ETHICS_V4.0";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white min-h-screen pt-32 pb-24">
|
||||
<SEO title={config.title} description={config.content} />
|
||||
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<header className="max-w-4xl mb-20">
|
||||
<Link to={`/${lang}`} className="inline-flex items-center gap-2 text-tech-muted text-[10px] font-mono font-bold mb-8 hover:text-tech-text transition-colors uppercase tracking-widest">
|
||||
<ArrowLeft size={12} /> cd ..
|
||||
</Link>
|
||||
<div className="inline-flex items-center gap-2 text-tech-primary text-[10px] font-mono font-bold uppercase tracking-[0.2em] mb-4">
|
||||
<Icon size={14} />
|
||||
<span>{code}</span>
|
||||
</div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-5xl md:text-7xl font-extrabold mb-8 text-tech-text tracking-tighter"
|
||||
>
|
||||
{config.title}
|
||||
</motion.h1>
|
||||
</header>
|
||||
|
||||
<article className="max-w-3xl prose prose-slate prose-invert prose-tech">
|
||||
<div className="bg-tech-surface border border-tech-border rounded p-8 lg:p-12 font-medium text-tech-muted leading-loose whitespace-pre-line">
|
||||
{config.content}
|
||||
|
||||
<div className="mt-12 pt-8 border-t border-tech-border text-[10px] font-mono uppercase">
|
||||
Last_Modified: {new Date().toISOString().split('T')[0]}<br/>
|
||||
Version: 4.2.0-STABLE
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
199
Template-01/src/pages/PostDetail.tsx
Normal file
199
Template-01/src/pages/PostDetail.tsx
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import React from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { TableOfContents } from '../components/TableOfContents';
|
||||
import { Newsletter } from '../components/Newsletter';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { formatDate } from '../lib/utils';
|
||||
import { ArrowLeft, Clock, Twitter, Linkedin, Link as LinkIcon, Cpu, Terminal } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
import Markdown from 'react-markdown';
|
||||
|
||||
export default function PostDetail() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const { lang, posts, t } = useI18n();
|
||||
|
||||
const validPosts = posts || [];
|
||||
const post = validPosts.find(p => p.slug === slug);
|
||||
const relatedPosts = validPosts.filter(p => p.slug !== slug && (p.category === post?.category || p.featured)).slice(0, 3);
|
||||
|
||||
if (!post) {
|
||||
return (
|
||||
<div className="container mx-auto px-6 py-40 text-center font-mono uppercase tracking-widest text-tech-muted">
|
||||
<h1 className="text-4xl font-bold mb-8 text-tech-text">[error] 404_NOT_FOUND</h1>
|
||||
<Link to={`/${lang}/blog`} className="text-tech-primary inline-flex items-center gap-2 font-bold bg-tech-surface px-6 py-2 rounded">
|
||||
<ArrowLeft size={16} /> RETURN_TO_BASE
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white min-h-screen">
|
||||
<SEO
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
image={post.image}
|
||||
article
|
||||
author={post.author}
|
||||
datePublished={post.date}
|
||||
category={post.category}
|
||||
/>
|
||||
|
||||
{/* Technical Header */}
|
||||
<section className="pt-32 pb-16 relative bg-white border-b border-slate-100">
|
||||
<div className="container mx-auto px-6 lg:px-12 relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
<Link to={`/${lang}/blog`} className="inline-flex items-center gap-2 text-tech-muted text-[10px] font-mono font-bold mb-8 hover:text-tech-text transition-colors uppercase tracking-widest">
|
||||
<ArrowLeft size={12} /> cd ../blog
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<span className="category-tag">
|
||||
{post.category}
|
||||
</span>
|
||||
<div className="w-1 h-1 rounded-full bg-tech-border" />
|
||||
<span className="text-tech-muted text-[10px] uppercase font-mono font-bold tracking-widest flex items-center gap-1">
|
||||
<Clock size={10} /> READ_TIME: {post.readingTime}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-7xl font-extrabold text-tech-text leading-[1] tracking-tighter mb-8 max-w-5xl">
|
||||
{post.title}
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-tech-muted mb-12 max-w-3xl leading-relaxed font-medium">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-center gap-6 pt-8 border-t border-tech-border">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded bg-tech-surface flex items-center justify-center font-mono font-bold text-tech-muted border border-tech-border">
|
||||
{post.author[0]}
|
||||
</div>
|
||||
<div className="text-left leading-tight">
|
||||
<span className="font-bold text-tech-text block text-sm">{post.author}</span>
|
||||
<span className="text-tech-muted text-[10px] font-mono font-bold uppercase">system_editor</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block h-8 w-px bg-tech-border" />
|
||||
<div className="text-left leading-tight">
|
||||
<span className="text-tech-muted text-[10px] font-mono font-bold uppercase block">timestamp</span>
|
||||
<span className="font-bold text-tech-text text-sm">{formatDate(post.date, lang)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Technical Image */}
|
||||
<section className="container mx-auto px-6 lg:px-12 py-12">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="aspect-video lg:aspect-[21/7] rounded-lg overflow-hidden border border-tech-border bg-tech-surface relative"
|
||||
>
|
||||
<img src={post.image} className="w-full h-full object-cover opacity-90 transition-opacity hover:opacity-100" alt={post.title} referrerPolicy="no-referrer" />
|
||||
<div className="absolute bottom-4 right-4 bg-tech-text/80 backdrop-blur-md px-3 py-1 rounded text-[10px] font-mono text-white border border-slate-700 uppercase">IMG_REF: {post.slug}.jpg</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Content Section */}
|
||||
<section className="container mx-auto px-6 lg:px-12 pb-32">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_300px] gap-16">
|
||||
|
||||
{/* Main Article body */}
|
||||
<article className="markdown-body w-full max-w-3xl border-r border-slate-50 pr-0 lg:pr-16">
|
||||
<Markdown>{post.content}</Markdown>
|
||||
</article>
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="space-y-12">
|
||||
<div className="sticky top-32">
|
||||
<TableOfContents content={post.content} />
|
||||
|
||||
<div className="mt-8 p-6 bg-tech-surface rounded border border-tech-border">
|
||||
<h4 className="text-[10px] font-mono font-bold uppercase tracking-widest text-tech-muted mb-4">./bio_metadata</h4>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-10 h-10 rounded bg-white flex items-center justify-center font-bold text-tech-muted border border-tech-border">
|
||||
{post.author[0]}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-bold text-tech-text block text-sm">{post.author}</span>
|
||||
<span className="text-tech-primary text-[10px] font-mono font-bold uppercase">@alex_nexus</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-tech-muted leading-relaxed font-medium">
|
||||
Technical architect with 10+ years optimizing distributed systems and multimodal AI agents.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex flex-col gap-2">
|
||||
<div className="text-[10px] font-mono font-bold text-tech-muted uppercase tracking-widest mb-2">Compartilhar_Modulo</div>
|
||||
<div className="flex gap-2">
|
||||
<a
|
||||
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(post.title)}&url=${encodeURIComponent(window.location.href)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-8 h-8 rounded bg-white border border-tech-border flex items-center justify-center text-tech-muted hover:text-tech-primary hover:border-tech-primary transition-all cursor-pointer"
|
||||
>
|
||||
<Twitter size={14} />
|
||||
</a>
|
||||
<a
|
||||
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(window.location.href)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-8 h-8 rounded bg-white border border-tech-border flex items-center justify-center text-tech-muted hover:text-tech-primary hover:border-tech-primary transition-all cursor-pointer"
|
||||
>
|
||||
<Linkedin size={14} />
|
||||
</a>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
alert('Link copiado!');
|
||||
}}
|
||||
className="w-8 h-8 rounded bg-white border border-tech-border flex items-center justify-center text-tech-muted hover:text-tech-text hover:border-tech-text transition-all cursor-pointer"
|
||||
>
|
||||
<LinkIcon size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Related Technical Modules */}
|
||||
{relatedPosts.length > 0 && (
|
||||
<section className="py-24 bg-tech-surface border-t border-tech-border">
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<div className="flex items-center justify-between mb-12">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-tech-primary text-[10px] font-mono font-bold uppercase tracking-widest mb-4">
|
||||
<div className="w-8 h-px bg-tech-primary" />
|
||||
<span>REL_MODULES</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold tracking-tight text-tech-text">Módulos Relacionados</h2>
|
||||
</div>
|
||||
<Link to={`/${lang}/blog`} className="text-[10px] font-mono font-bold text-tech-muted hover:text-tech-text transition-colors uppercase tracking-widest border border-tech-border px-4 py-2 rounded">view_all_entries</Link>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-0 border-t border-l border-tech-border">
|
||||
{relatedPosts.map(post => (
|
||||
<div key={post.id} className="border-r border-b border-tech-border p-1 bg-white">
|
||||
<PostCard post={post} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<Newsletter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
97
Template-01/src/types.ts
Normal file
97
Template-01/src/types.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
export type Language = 'pt' | 'en' | 'es';
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
category: string;
|
||||
author: string;
|
||||
date: string;
|
||||
readingTime: string;
|
||||
image: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export interface Translations {
|
||||
nav: {
|
||||
home: string;
|
||||
blog: string;
|
||||
about: string;
|
||||
categories: string;
|
||||
};
|
||||
home: {
|
||||
hero: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
cta: string;
|
||||
};
|
||||
featured: string;
|
||||
recent: string;
|
||||
newsletter: {
|
||||
title: string;
|
||||
desc: string;
|
||||
placeholder: string;
|
||||
button: string;
|
||||
};
|
||||
};
|
||||
blog: {
|
||||
categories: {
|
||||
all: string;
|
||||
ai: string;
|
||||
code: string;
|
||||
startups: string;
|
||||
tools: string;
|
||||
};
|
||||
readMore: string;
|
||||
related: string;
|
||||
back: string;
|
||||
toc: string;
|
||||
};
|
||||
footer: {
|
||||
about: string;
|
||||
connect: string;
|
||||
rights: string;
|
||||
terms: string;
|
||||
ethics: string;
|
||||
newsletter: string;
|
||||
rss: string;
|
||||
contact: string;
|
||||
privacy: string;
|
||||
};
|
||||
legal: {
|
||||
privacy: { title: string; content: string; };
|
||||
terms: { title: string; content: string; };
|
||||
ethics: { title: string; content: string; };
|
||||
};
|
||||
contact: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
info: {
|
||||
title: string;
|
||||
email: string;
|
||||
location: string;
|
||||
};
|
||||
status: {
|
||||
title: string;
|
||||
nodes: string;
|
||||
uptime: string;
|
||||
latency: string;
|
||||
};
|
||||
form: {
|
||||
name: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
submit: string;
|
||||
sending: string;
|
||||
success: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface SiteContent {
|
||||
posts: Record<Language, Post[]>;
|
||||
ui: Record<Language, Translations>;
|
||||
}
|
||||
26
Template-01/tsconfig.json
Normal file
26
Template-01/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
24
Template-01/vite.config.ts
Normal file
24
Template-01/vite.config.ts
Normal 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',
|
||||
},
|
||||
};
|
||||
});
|
||||
14
Template-02/README.md
Normal file
14
Template-02/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Nexus Tech Blog
|
||||
Very professional static blog.
|
||||
|
||||
## Setup
|
||||
Install dependencies:
|
||||
`npm install`
|
||||
|
||||
## Technologies
|
||||
- React 18
|
||||
- Vite
|
||||
- Tailwind CSS
|
||||
- Motion (framer-motion)
|
||||
- Lucide React
|
||||
- React Helmet Async (SEO)
|
||||
13
Template-02/index.html
Normal file
13
Template-02/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><rect width=%22100%22 height=%22100%22 rx=%2220%22 fill=%22%230070F3%22/><text y=%22.9em%22 font-size=%2280%22 x=%2250%%22 text-anchor=%22middle%22 fill=%22white%22 font-family=%22monospace%22 font-weight=%22bold%22>N</text></svg>" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
Template-02/metadata.json
Normal file
6
Template-02/metadata.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Lumix Tech Blog",
|
||||
"description": "A cutting-edge, future-focused technology blog with neo-futuristic design and bento layouts.",
|
||||
"requestFramePermissions": [],
|
||||
"majorCapabilities": []
|
||||
}
|
||||
5633
Template-02/package-lock.json
generated
Normal file
5633
Template-02/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
39
Template-02/package.json
Normal file
39
Template-02/package.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port=3000 --host=0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.29.0",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.2",
|
||||
"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.15.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
4
Template-02/public/robots.txt
Normal file
4
Template-02/public/robots.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://nexus-blog.tech/sitemap.xml
|
||||
62
Template-02/public/sitemap.xml
Normal file
62
Template-02/public/sitemap.xml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<!-- Main Pages -->
|
||||
<url><loc>https://nexus-blog.tech/pt</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/es</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog</loc><priority>0.8</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog</loc><priority>0.8</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/es/blog</loc><priority>0.8</priority></url>
|
||||
|
||||
<!-- Categories -->
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/ai</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/code</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/startups</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/tools</loc><priority>0.6</priority></url>
|
||||
|
||||
<!-- PT Posts -->
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/futuro-ia-generativa-2024</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/typescript-moderno-guia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/escalando-startups-bootstrap</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/ferramentas-indispensaveis-dev</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/web-components-nativos</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/clean-architecture-js</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/seguranca-ciber-ia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/trabalho-remoto-fatos</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/design-system-flexivel</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/webgpu-ias-locais</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/precificacao-saas</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/nextjs-14-enterprise</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/etica-algoritmica</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/figma-dev-mode</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/rust-backends</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/growth-hacking-real</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/documentacao-agil</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/medicina-ia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/testes-frontend</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/nomadismo-dev</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/sustentabilidade-tech</loc><priority>0.7</priority></url>
|
||||
|
||||
<!-- EN Posts -->
|
||||
<url><loc>https://nexus-blog.tech/en/blog/future-generative-ai-2024</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/modern-typescript-guide</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/scaling-startups-bootstrap</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/essential-dev-tools</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/native-web-components</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/clean-architecture-js</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/ai-cybersecurity</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/remote-work-facts</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/flexible-design-system</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/webgpu-local-ai</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/saas-pricing</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/nextjs-14-enterprise</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/algorithmic-ethics</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/figma-dev-mode</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/rust-backends</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/real-growth-hacking</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/agile-documentation</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/medicine-ai</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/frontend-testing</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/dev-nomadism</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/tech-sustainability</loc><priority>0.7</priority></url>
|
||||
</urlset>
|
||||
51
Template-02/src/App.tsx
Normal file
51
Template-02/src/App.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React, { lazy, Suspense } from 'react';
|
||||
import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { Layout } from './components/Layout';
|
||||
import Home from './pages/Home';
|
||||
import BlogList from './pages/BlogList';
|
||||
import PostDetail from './pages/PostDetail';
|
||||
|
||||
// Wrapper to handle language validation and layout injection
|
||||
const LangWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { lang } = useParams<{ lang: string }>();
|
||||
const validLangs = ['en', 'pt', 'es'];
|
||||
|
||||
if (!lang || !validLangs.includes(lang)) {
|
||||
return <Navigate to="/pt" replace />;
|
||||
}
|
||||
|
||||
return <Layout>{children}</Layout>;
|
||||
};
|
||||
|
||||
const Contact = lazy(() => import('./pages/Contact'));
|
||||
const Legal = lazy(() => import('./pages/Legal'));
|
||||
|
||||
import { ScrollToTop } from './components/ScrollToTop';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<BrowserRouter>
|
||||
<ScrollToTop />
|
||||
<Suspense fallback={<div className="min-h-screen bg-black flex items-center justify-center font-mono text-[10px] font-black uppercase tracking-[0.5em] text-tech-primary animate-pulse italic">VANTA_KERN_INIT...</div>}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/pt" replace />} />
|
||||
|
||||
<Route path="/:lang" element={<LangWrapper><Home /></LangWrapper>} />
|
||||
<Route path="/:lang/blog" element={<LangWrapper><BlogList /></LangWrapper>} />
|
||||
<Route path="/:lang/blog/category/:category" element={<LangWrapper><BlogList /></LangWrapper>} />
|
||||
<Route path="/:lang/blog/:slug" element={<LangWrapper><PostDetail /></LangWrapper>} />
|
||||
|
||||
<Route path="/:lang/contact" element={<LangWrapper><Contact /></LangWrapper>} />
|
||||
<Route path="/:lang/privacy" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
<Route path="/:lang/terms" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
<Route path="/:lang/ethics" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
|
||||
<Route path="*" element={<Navigate to="/pt" replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
</HelmetProvider>
|
||||
);
|
||||
}
|
||||
210
Template-02/src/components/Layout.tsx
Normal file
210
Template-02/src/components/Layout.tsx
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
import React from 'react';
|
||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Language } from '../types';
|
||||
import { Menu, X, Github, Twitter, Linkedin, Terminal, Search, User, Globe } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { lang, t, changeLanguage } = useI18n();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
||||
const [isScrolled, setIsScrolled] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
// If no language in URL, redirect to default PT
|
||||
if (location.pathname === '/' || location.pathname === '') {
|
||||
navigate('/pt', { replace: true });
|
||||
}
|
||||
}, [location.pathname, navigate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleScroll = () => setIsScrolled(window.scrollY > 20);
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const languages: { code: Language; label: string }[] = [
|
||||
{ code: 'pt', label: 'PT' },
|
||||
{ code: 'en', label: 'EN' },
|
||||
{ code: 'es', label: 'ES' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col font-sans">
|
||||
<div className="h-1 w-full bg-tech-primary fixed top-0 left-0 z-[60]" />
|
||||
{/* Top Meta Bar */}
|
||||
<div className="hidden md:block bg-black border-b border-white/5 py-1.5 text-[9px] font-bold text-tech-muted uppercase tracking-[0.2em]">
|
||||
<div className="container mx-auto px-6 lg:px-12 flex justify-between items-center">
|
||||
<div className="flex gap-6">
|
||||
<span>Terminal: <span className="text-tech-primary">Active</span></span>
|
||||
<span>Region: <span className="text-tech-primary">Global</span></span>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<span className="flex items-center gap-1 cursor-pointer hover:text-white transition-colors"><Search size={10} /> Search_Database</span>
|
||||
<span className="flex items-center gap-1 cursor-pointer hover:text-white transition-colors"><User size={10} /> Node_Access</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Magazine Header */}
|
||||
<header
|
||||
className={cn(
|
||||
"sticky top-0 z-50 transition-all duration-300 border-b",
|
||||
isScrolled
|
||||
? "bg-black/98 backdrop-blur-md border-tech-primary/20 py-1.5"
|
||||
: "bg-black border-white/5 py-3"
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto px-6 lg:px-12 flex items-center justify-between">
|
||||
<Link to={`/${lang}`} className="flex items-center gap-2 group">
|
||||
<div className="w-7 h-7 bg-tech-primary flex items-center justify-center text-black font-black text-lg italic skew-x-[-10deg]">
|
||||
L
|
||||
</div>
|
||||
<span className="font-black text-lg tracking-tighter text-white uppercase italic">
|
||||
Lumix<span className="text-tech-primary">.</span>Tech
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<nav className="hidden lg:flex items-center gap-1">
|
||||
<Link to={`/${lang}`} className="px-3 py-1 text-[10px] font-black text-white hover:text-tech-primary transition-colors uppercase tracking-[0.2em] italic">{t.nav.home}</Link>
|
||||
<div className="w-px h-3 bg-white/10 mx-2" />
|
||||
<Link to={`/${lang}/blog/category/ai`} className="px-3 py-1 text-[10px] font-black text-white hover:text-tech-primary transition-colors uppercase tracking-[0.2em] italic">{t.blog.categories.ai}</Link>
|
||||
<Link to={`/${lang}/blog/category/code`} className="px-3 py-1 text-[10px] font-black text-white hover:text-tech-primary transition-colors uppercase tracking-[0.2em] italic">{t.blog.categories.code}</Link>
|
||||
<Link to={`/${lang}/blog/category/tools`} className="px-3 py-1 text-[10px] font-black text-white hover:text-tech-primary transition-colors uppercase tracking-[0.2em] italic">{t.blog.categories.tools}</Link>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="hidden sm:flex items-center gap-1 bg-white/5 p-0.5 rounded-xs border border-white/5">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => changeLanguage(l.code)}
|
||||
className={cn(
|
||||
"text-[9px] font-black uppercase px-2 py-0.5 transition-all outline-hidden rounded-xs",
|
||||
lang === l.code
|
||||
? "bg-tech-primary text-black"
|
||||
: "text-tech-muted hover:text-white"
|
||||
)}
|
||||
>
|
||||
{l.code}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="lg:hidden text-white/60 hover:text-white transition-colors" onClick={() => setIsMenuOpen(!isMenuOpen)}>
|
||||
{isMenuOpen ? <X size={20} /> : <Menu size={20} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: '-100%' }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: '-100%' }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||
className="fixed inset-0 z-40 bg-black/98 backdrop-blur-xl pt-32 px-6 lg:hidden"
|
||||
>
|
||||
<div className="flex flex-col h-full justify-between pb-12">
|
||||
<nav className="flex flex-col gap-6">
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}`} className="text-3xl font-black text-white uppercase italic tracking-tighter hover:text-tech-primary transition-colors">
|
||||
{t.nav.home}
|
||||
</Link>
|
||||
<div className="w-12 h-1 bg-tech-primary" />
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog/category/ai`} className="text-2xl font-black text-white/50 hover:text-tech-primary uppercase italic tracking-tighter transition-colors">{t.blog.categories.ai}</Link>
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog/category/code`} className="text-2xl font-black text-white/50 hover:text-tech-primary uppercase italic tracking-tighter transition-colors">{t.blog.categories.code}</Link>
|
||||
<Link onClick={() => setIsMenuOpen(false)} to={`/${lang}/blog/category/tools`} className="text-2xl font-black text-white/50 hover:text-tech-primary uppercase italic tracking-tighter transition-colors">{t.blog.categories.tools}</Link>
|
||||
</nav>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-4 py-4 border-y border-white/5">
|
||||
<span className="text-[10px] font-black text-tech-muted uppercase tracking-[0.2em]">Select_Language:</span>
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={`mobile-${l.code}`}
|
||||
onClick={() => { changeLanguage(l.code); setIsMenuOpen(false); }}
|
||||
className={cn(
|
||||
"text-xs font-black uppercase tracking-widest",
|
||||
lang === l.code ? "text-tech-primary" : "text-white/40"
|
||||
)}
|
||||
>
|
||||
{l.code.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-6">
|
||||
<Twitter size={20} className="text-white/40 hover:text-tech-primary transition-colors" />
|
||||
<Github size={20} className="text-white/40 hover:text-tech-primary transition-colors" />
|
||||
<Linkedin size={20} className="text-white/40 hover:text-tech-primary transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button className="absolute top-6 right-6 text-white/60 hover:text-white" onClick={() => setIsMenuOpen(false)}>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<main className="flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<footer className="bg-black border-t border-white/10 py-16 md:py-24 mt-20">
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 mb-16">
|
||||
<div className="md:col-span-5">
|
||||
<Link to={`/${lang}`} className="flex items-center gap-2 mb-6 group">
|
||||
<div className="w-8 h-8 bg-tech-primary flex items-center justify-center text-black font-black text-xl italic skew-x-[-10deg]">
|
||||
L
|
||||
</div>
|
||||
<span className="font-black text-xl tracking-tighter text-white uppercase italic">
|
||||
Lumix Tech
|
||||
</span>
|
||||
</Link>
|
||||
<p className="text-tech-muted leading-relaxed text-xs max-w-sm uppercase font-bold tracking-tight opacity-70">
|
||||
{t.footer.about}
|
||||
</p>
|
||||
</div>
|
||||
<div className="md:col-span-7 grid grid-cols-2 lg:grid-cols-3 gap-8 md:gap-12">
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-white font-black uppercase tracking-[0.2em] text-[10px] italic">Protocol_Modules</h4>
|
||||
<ul className="space-y-2 text-[10px] text-tech-muted font-bold uppercase tracking-widest">
|
||||
<li><Link to={`/${lang}/blog`} className="hover:text-tech-primary transition-colors">Intelligence_Archive</Link></li>
|
||||
<li><Link to={`/${lang}/blog/category/ai`} className="hover:text-tech-primary transition-colors">Neural_Core</Link></li>
|
||||
<li><Link to={`/${lang}/blog/category/code`} className="hover:text-tech-primary transition-colors">System_Logic</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-white font-black uppercase tracking-[0.2em] text-[10px] italic">Security_Clearance</h4>
|
||||
<ul className="space-y-2 text-[10px] text-tech-muted font-bold uppercase tracking-widest">
|
||||
<li><Link to={`/${lang}/privacy`} className="hover:text-tech-primary transition-colors">Protocol_Privacy</Link></li>
|
||||
<li><Link to={`/${lang}/ethics`} className="hover:text-tech-primary transition-colors">Neural_Ethics</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="space-y-4 col-span-2 lg:col-span-1">
|
||||
<h4 className="text-white font-black uppercase tracking-[0.2em] text-[10px] italic">External_Links</h4>
|
||||
<div className="flex gap-4">
|
||||
<a href="#" className="w-8 h-8 bg-white/5 border border-white/5 flex items-center justify-center text-white/60 hover:text-tech-primary hover:border-tech-primary transition-all"><Twitter size={14} /></a>
|
||||
<a href="#" className="w-8 h-8 bg-white/5 border border-white/5 flex items-center justify-center text-white/60 hover:text-tech-primary hover:border-tech-primary transition-all"><Github size={14} /></a>
|
||||
<a href="#" className="w-8 h-8 bg-white/5 border border-white/5 flex items-center justify-center text-white/60 hover:text-tech-primary hover:border-tech-primary transition-all"><Linkedin size={14} /></a>
|
||||
</div>
|
||||
</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-[9px] font-black uppercase tracking-[0.3em] text-tech-muted">
|
||||
<p>{t.footer.rights}</p>
|
||||
<div className="flex gap-6">
|
||||
<span>EST_2026 // NODE_LUMIX_v1.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
57
Template-02/src/components/Newsletter.tsx
Normal file
57
Template-02/src/components/Newsletter.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Send, Sparkles } from 'lucide-react';
|
||||
|
||||
export const Newsletter = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<section className="py-20 md:py-32 bg-tech-primary border-y-4 md:border-y-8 border-black overflow-hidden relative">
|
||||
<div className="absolute top-0 right-0 text-[100px] md:text-[180px] font-black text-black/10 leading-none select-none pointer-events-none -translate-y-8 md:-translate-y-12 translate-x-8 md:translate-x-12 italic">
|
||||
NEWS
|
||||
</div>
|
||||
<div className="container mx-auto px-6 lg:px-12 relative z-10">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 md:gap-16 items-center">
|
||||
<div>
|
||||
<div className="inline-block bg-black text-tech-primary px-3 py-1 text-[9px] md:text-[10px] font-black uppercase tracking-[0.4em] mb-4 md:mb-6 skew-x-[-10deg]">
|
||||
Intelligence_Unit
|
||||
</div>
|
||||
<h2 className="text-3xl md:text-6xl font-black text-black leading-[1] tracking-[-0.04em] uppercase mb-4 md:mb-6 italic">
|
||||
Join the <br />
|
||||
<span className="text-white drop-shadow-[2px_2px_0_rgba(249,115,22,0.4)]">Elite Network</span>
|
||||
</h2>
|
||||
<p className="text-black font-bold text-xs md:text-sm max-w-xs uppercase tracking-tight opacity-80">
|
||||
Deep-level systems architecture analysis delivered to your node.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-black p-6 md:p-8 skew-x-[-1deg] shadow-[8px_8px_0_0_rgba(255,255,255,1)] md:shadow-[12px_12px_0_0_rgba(255,255,255,1)]">
|
||||
<form
|
||||
className="space-y-4"
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<label htmlFor="emailAddress" className="text-[8px] font-black text-tech-primary uppercase tracking-[0.2em]">Neural_Endpoint</label>
|
||||
<input
|
||||
id="emailAddress"
|
||||
type="email"
|
||||
placeholder="USER@LUMIX.TECH"
|
||||
className="w-full bg-white/5 border border-white/10 text-white px-5 py-3 outline-hidden focus:border-tech-primary transition-all font-bold text-xs uppercase placeholder:text-white/10 italic"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="w-full bg-tech-primary text-black font-black py-4 text-xs uppercase tracking-[0.1em] italic flex items-center justify-center gap-2 hover:bg-white transition-colors cursor-pointer group"
|
||||
>
|
||||
Establish Sync <Send size={14} className="group-hover:translate-x-1 transition-transform" />
|
||||
</button>
|
||||
<p className="text-[9px] font-bold text-white/40 uppercase tracking-widest text-center">
|
||||
By connecting, you agree to our protocol terms and neural ethics guidelines.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
65
Template-02/src/components/PostCard.tsx
Normal file
65
Template-02/src/components/PostCard.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import React from 'react';
|
||||
import { Post } from '../types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { formatDate } from '../lib/utils';
|
||||
import { ArrowRight, User } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
interface PostCardProps {
|
||||
post: Post;
|
||||
}
|
||||
|
||||
export const PostCard: React.FC<PostCardProps> = ({ post }) => {
|
||||
const { lang } = useI18n();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
viewport={{ once: true }}
|
||||
className="group h-full"
|
||||
>
|
||||
<Link to={`/${lang}/blog/${post.slug}`} className="flex flex-col h-full bg-white/[0.01] border border-white/5 hover:bg-white/[0.03] hover:border-tech-primary/30 transition-all duration-300">
|
||||
<div className="relative aspect-[16/10] overflow-hidden">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
<div className="absolute top-2 left-2 md:top-3 md:left-3">
|
||||
<span className="category-tag !text-[8px] !px-1.5 !py-0.5">
|
||||
{post.category}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 md:p-5 flex flex-col flex-grow">
|
||||
<div className="flex items-center gap-3 md:gap-4 mb-2 md:mb-3 text-[9px] md:text-[10px] font-bold text-tech-muted uppercase tracking-widest">
|
||||
<span>{formatDate(post.date, lang)}</span>
|
||||
<span className="w-1 h-1 rounded-full bg-tech-primary" />
|
||||
<span>{post.readingTime}</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-base md:text-lg font-bold mb-3 text-white group-hover:text-tech-primary transition-colors leading-tight line-clamp-2 uppercase italic tracking-tighter">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
<p className="text-tech-muted text-[11px] line-clamp-2 leading-relaxed mb-4 md:mb-6">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
<div className="mt-auto flex items-center justify-between pt-3 md:pt-4 border-t border-white/5">
|
||||
<span className="text-[9px] md:text-[10px] font-bold text-white uppercase flex items-center gap-2 tracking-tight">
|
||||
<User size={10} className="text-tech-primary" /> {post.author}
|
||||
</span>
|
||||
<ArrowRight size={12} className="text-tech-primary group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
127
Template-02/src/components/SEO.tsx
Normal file
127
Template-02/src/components/SEO.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
interface SEOProps {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
article?: boolean;
|
||||
author?: string;
|
||||
datePublished?: string;
|
||||
category?: string;
|
||||
}
|
||||
|
||||
export const SEO: React.FC<SEOProps> = ({
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
article,
|
||||
author = 'Vanta Architecture Team',
|
||||
datePublished,
|
||||
category
|
||||
}) => {
|
||||
const siteName = 'Vanta Technical Journal';
|
||||
const fullTitle = `${title} | ${siteName}`;
|
||||
const location = useLocation();
|
||||
const canonicalUrl = `https://vanta-journal.io${location.pathname}`;
|
||||
const defaultImage = 'https://images.unsplash.com/photo-1620121692029-d088224efc74?q=80&w=2000';
|
||||
const ogImage = image || defaultImage;
|
||||
|
||||
const structuredData = article ? {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": title,
|
||||
"description": description,
|
||||
"image": ogImage,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": author
|
||||
},
|
||||
"datePublished": datePublished,
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": canonicalUrl
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": siteName,
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://nexus-blog.tech/logo.png"
|
||||
}
|
||||
}
|
||||
} : {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": siteName,
|
||||
"url": "https://nexus-blog.tech",
|
||||
"description": description
|
||||
};
|
||||
|
||||
const breadcrumbData = article ? {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": `https://nexus-blog.tech/${location.pathname.split('/')[1]}`
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "Blog",
|
||||
"item": `https://nexus-blog.tech/${location.pathname.split('/')[1]}/blog`
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 3,
|
||||
"name": title,
|
||||
"item": canonicalUrl
|
||||
}
|
||||
]
|
||||
} : null;
|
||||
|
||||
return (
|
||||
<Helmet
|
||||
htmlAttributes={{
|
||||
lang: location.pathname.split('/')[1] || 'pt'
|
||||
}}
|
||||
>
|
||||
{/* Basic Meta Tags */}
|
||||
<title>{fullTitle}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
|
||||
{/* Open Graph / Facebook */}
|
||||
<meta property="og:site_name" content={siteName} />
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
<meta property="og:title" content={fullTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:type" content={article ? 'article' : 'website'} />
|
||||
<meta property="og:image" content={ogImage} />
|
||||
{article && category && <meta property="article:section" content={category} />}
|
||||
{article && datePublished && <meta property="article:published_time" content={datePublished} />}
|
||||
|
||||
{/* Twitter */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:domain" content="vanta-journal.io" />
|
||||
<meta name="twitter:url" content={canonicalUrl} />
|
||||
<meta name="twitter:title" content={fullTitle} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={ogImage} />
|
||||
|
||||
{/* Google Rich Results */}
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(structuredData)}
|
||||
</script>
|
||||
{breadcrumbData && (
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(breadcrumbData)}
|
||||
</script>
|
||||
)}
|
||||
</Helmet>
|
||||
);
|
||||
};
|
||||
12
Template-02/src/components/ScrollToTop.tsx
Normal file
12
Template-02/src/components/ScrollToTop.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const ScrollToTop = () => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
};
|
||||
30
Template-02/src/components/TableOfContents.tsx
Normal file
30
Template-02/src/components/TableOfContents.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
interface TOCProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export const TableOfContents: React.FC<TOCProps> = ({ content }) => {
|
||||
const headings = content.split('\n').filter(line => line.startsWith('## ')).map(line => line.replace('## ', '').trim());
|
||||
|
||||
if (headings.length === 0) return null;
|
||||
|
||||
return (
|
||||
<nav className="flex flex-col">
|
||||
{headings.map((heading, index) => (
|
||||
<a
|
||||
key={heading}
|
||||
href={`#${heading.toLowerCase().replace(/ /g, '-')}`}
|
||||
className="group flex items-center justify-between py-3 border-b border-white/5 text-[11px] font-black uppercase italic tracking-widest text-tech-muted hover:text-tech-primary transition-all overflow-hidden"
|
||||
>
|
||||
<span className="flex items-center gap-3">
|
||||
<span className="text-[8px] text-white/20 group-hover:text-tech-primary transition-colors">0{index + 1}</span>
|
||||
{heading}
|
||||
</span>
|
||||
<ArrowRight size={12} className="opacity-0 group-hover:opacity-100 transition-all -translate-x-4 group-hover:translate-x-0" />
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
1500
Template-02/src/content/index.ts
Normal file
1500
Template-02/src/content/index.ts
Normal file
File diff suppressed because it is too large
Load diff
26
Template-02/src/hooks/useI18n.ts
Normal file
26
Template-02/src/hooks/useI18n.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { content } from '../content';
|
||||
import { Language } from '../types';
|
||||
|
||||
export function useI18n() {
|
||||
const { lang } = useParams<{ lang?: string }>();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const currentLang: Language = (lang as Language) || 'pt';
|
||||
const t = content.ui[currentLang];
|
||||
const posts = content.posts[currentLang];
|
||||
|
||||
const changeLanguage = (newLang: Language) => {
|
||||
const pathParts = location.pathname.split('/');
|
||||
pathParts[1] = newLang; // Replace the language segment
|
||||
navigate(pathParts.join('/'));
|
||||
};
|
||||
|
||||
return {
|
||||
lang: currentLang,
|
||||
t,
|
||||
posts,
|
||||
changeLanguage,
|
||||
};
|
||||
}
|
||||
139
Template-02/src/index.css
Normal file
139
Template-02/src/index.css
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-sans: "Inter", system-ui, sans-serif;
|
||||
--font-mono: "Fira Code", monospace;
|
||||
|
||||
--color-tech-primary: #f97316; /* Vivid Orange */
|
||||
--color-tech-secondary: #64748b; /* Slate Gray */
|
||||
--color-tech-accent: #ffffff; /* White */
|
||||
--color-tech-surface: #181818; /* Lighter Industrial Dark */
|
||||
--color-tech-border: rgba(255, 255, 255, 0.08);
|
||||
--color-tech-text: #f1f5f9;
|
||||
--color-tech-muted: #94a3b8;
|
||||
--color-tech-glow: rgba(249, 115, 22, 0.15);
|
||||
|
||||
--animate-float: float 6s ease-in-out infinite;
|
||||
--animate-pulse-slow: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
--animate-fade-in: fadeIn 0.4s ease-out forwards;
|
||||
--animate-marquee: marquee 30s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-15px); }
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
@utility article-content {
|
||||
@apply text-tech-text leading-relaxed;
|
||||
|
||||
& h2 {
|
||||
@apply text-xl md:text-2xl font-black text-white mt-12 mb-6 uppercase italic tracking-tighter border-l-4 border-tech-primary pl-4;
|
||||
}
|
||||
|
||||
& h3 {
|
||||
@apply text-lg font-black text-white mt-8 mb-4 uppercase tracking-tight;
|
||||
}
|
||||
|
||||
& p {
|
||||
@apply mb-6 text-sm md:text-base text-tech-muted leading-[1.6];
|
||||
}
|
||||
|
||||
& blockquote {
|
||||
@apply my-8 md:my-10 p-5 md:p-8 bg-white/5 border-l-4 border-tech-primary italic text-base md:text-xl font-bold text-white relative;
|
||||
&::before {
|
||||
content: '"';
|
||||
@apply absolute -top-1 -left-1 text-4xl md:text-5xl text-white/10 font-serif leading-none;
|
||||
}
|
||||
}
|
||||
|
||||
& ul {
|
||||
@apply mb-8 space-y-3;
|
||||
& li {
|
||||
@apply flex items-start gap-3 text-tech-muted;
|
||||
&::before {
|
||||
content: "→";
|
||||
@apply text-tech-primary font-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& img {
|
||||
@apply my-12 border border-white/10 shadow-2xl grayscale hover:grayscale-0 transition-all duration-500;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-tech-surface text-white selection:bg-tech-primary/30 selection:text-white antialiased overflow-x-hidden;
|
||||
background-image:
|
||||
radial-gradient(at 0% 0%, rgba(249, 115, 22, 0.05) 0px, transparent 50%),
|
||||
radial-gradient(at 100% 100%, rgba(100, 116, 139, 0.03) 0px, transparent 50%);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-sans tracking-tight text-white font-bold leading-tight;
|
||||
}
|
||||
}
|
||||
|
||||
@utility glass-card {
|
||||
@apply bg-white/[0.02] backdrop-blur-xl border border-white/5 transition-all duration-500;
|
||||
}
|
||||
|
||||
@utility tech-card {
|
||||
@apply glass-card hover:bg-white/[0.05] hover:border-tech-primary/40 flex flex-col h-full overflow-hidden;
|
||||
}
|
||||
|
||||
@utility mag-feature-card {
|
||||
@apply relative overflow-hidden cursor-pointer aspect-video md:aspect-auto;
|
||||
}
|
||||
|
||||
@utility gradient-text {
|
||||
@apply bg-clip-text text-transparent bg-linear-to-r from-tech-primary via-tech-secondary to-tech-accent;
|
||||
}
|
||||
|
||||
@utility category-tag {
|
||||
@apply inline-flex items-center px-2 py-0.5 rounded-sm bg-tech-primary text-[10px] font-bold text-white uppercase tracking-wider;
|
||||
}
|
||||
|
||||
@utility btn-mag {
|
||||
@apply inline-flex items-center justify-center px-5 py-2 bg-tech-primary text-white font-bold text-sm transition-all hover:bg-tech-primary/80 active:scale-95;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.markdown-body {
|
||||
@apply text-tech-muted leading-relaxed text-lg font-sans;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
@apply text-3xl font-bold mt-16 mb-8 text-white tracking-tight flex items-center gap-4;
|
||||
}
|
||||
|
||||
.markdown-body h2::before {
|
||||
content: "";
|
||||
@apply w-1.5 h-8 bg-tech-primary;
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
@apply mb-8;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
@apply bg-[#0f172a] border border-white/10 p-6 rounded-lg my-8 font-mono text-sm overflow-x-auto shadow-2xl;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
@apply border-l-4 border-tech-primary pl-8 py-2 my-10 italic text-white bg-tech-primary/5 rounded-r-lg;
|
||||
}
|
||||
}
|
||||
14
Template-02/src/lib/utils.ts
Normal file
14
Template-02/src/lib/utils.ts
Normal 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(dateString: string, lang: string) {
|
||||
return new Date(dateString).toLocaleDateString(lang, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
10
Template-02/src/main.tsx
Normal file
10
Template-02/src/main.tsx
Normal 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>,
|
||||
);
|
||||
88
Template-02/src/pages/BlogList.tsx
Normal file
88
Template-02/src/pages/BlogList.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { cn } from '../lib/utils';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
export default function BlogList() {
|
||||
const { category } = useParams<{ category?: string }>();
|
||||
const { lang, posts, t } = useI18n();
|
||||
|
||||
const validPosts = posts || [];
|
||||
const categories = ['all', 'ai', 'code', 'startups', 'tools'];
|
||||
const filteredPosts = category && category !== 'all'
|
||||
? validPosts.filter(p => p.category === category)
|
||||
: validPosts;
|
||||
|
||||
const activeCategory = category || 'all';
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO
|
||||
title={t.nav.blog}
|
||||
description="Explora nuestros artículos sobre tecnología, programación e inteligencia artificial."
|
||||
/>
|
||||
|
||||
<section className="py-24 lg:py-48 container mx-auto px-6 lg:px-12 bg-black min-h-screen">
|
||||
<header className="mb-24 max-w-5xl">
|
||||
<div className="inline-flex items-center gap-3 text-tech-primary text-[11px] font-mono font-black uppercase tracking-[0.4em] mb-6">
|
||||
<div className="w-12 h-1 bg-tech-primary" />
|
||||
<span>ARCHIVE_STREAM</span>
|
||||
</div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-4xl md:text-6xl font-bold mb-8 text-white tracking-tight"
|
||||
>
|
||||
{t.nav.blog}
|
||||
</motion.h1>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="flex flex-wrap gap-4"
|
||||
>
|
||||
{categories.map((cat) => (
|
||||
<Link
|
||||
key={cat}
|
||||
to={cat === 'all' ? `/${lang}/blog` : `/${lang}/blog/category/${cat}`}
|
||||
className={cn(
|
||||
"px-6 py-2 font-mono text-[11px] font-black uppercase tracking-widest transition-all border-2",
|
||||
activeCategory === cat
|
||||
? "bg-tech-primary text-black border-tech-primary shadow-[4px_4px_0px_0px_rgba(255,255,255,1)]"
|
||||
: "bg-black border-tech-border text-tech-muted hover:border-white hover:text-white"
|
||||
)}
|
||||
>
|
||||
{t.blog.categories[cat as keyof typeof t.blog.categories]}
|
||||
</Link>
|
||||
))}
|
||||
</motion.div>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
|
||||
{filteredPosts.map((post, idx) => (
|
||||
<motion.div
|
||||
key={post.id}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: idx * 0.05 }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="absolute -top-3 -left-3 w-6 h-6 border-t-2 border-l-2 border-tech-primary pointer-events-none z-10" />
|
||||
<PostCard post={post} />
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredPosts.length === 0 && (
|
||||
<div className="text-center py-20 text-slate-400 font-mono text-xs font-bold uppercase tracking-widest border border-dashed border-slate-200 rounded-lg mt-10">
|
||||
[error] no_modules_found_in_category
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
125
Template-02/src/pages/Contact.tsx
Normal file
125
Template-02/src/pages/Contact.tsx
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowLeft, Send, Terminal, Mail, MapPin, Phone } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function Contact() {
|
||||
const { t, lang } = useI18n();
|
||||
|
||||
return (
|
||||
<div className="bg-black min-h-screen pt-40 pb-32">
|
||||
<SEO title={t.contact.title} description={t.contact.subtitle} />
|
||||
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<header className="max-w-4xl mb-24">
|
||||
<Link to={`/${lang}`} className="inline-flex items-center gap-3 text-tech-primary text-[11px] font-mono font-black mb-12 hover:text-white transition-colors uppercase tracking-[0.3em] bg-tech-surface px-4 py-2 border border-tech-border">
|
||||
<ArrowLeft size={14} /> ./RETURN_TO_ROOT
|
||||
</Link>
|
||||
<div className="inline-flex items-center gap-3 text-tech-primary text-[11px] font-mono font-black uppercase tracking-[0.4em] mb-6">
|
||||
<div className="w-12 h-1 bg-tech-primary" />
|
||||
<span>ENCRYPTED_COMMS</span>
|
||||
</div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-4xl md:text-6xl font-bold mb-8 text-white tracking-tight"
|
||||
>
|
||||
{t.contact.title}
|
||||
</motion.h1>
|
||||
<p className="text-xl text-tech-muted font-medium max-w-2xl leading-relaxed">
|
||||
{t.contact.subtitle}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-24">
|
||||
{/* Contact Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="bg-black border-4 border-white p-10 lg:p-16 shadow-[16px_16px_0px_0px_rgba(204,255,0,1)]"
|
||||
>
|
||||
<form className="space-y-10" onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="space-y-4">
|
||||
<label className="text-[11px] font-mono font-black text-tech-primary uppercase tracking-widest">INPUT::NAME</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full bg-black border-b-2 border-tech-border py-4 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-lg text-white uppercase tracking-tight"
|
||||
placeholder="ANONYMOUS_USER"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<label className="text-[11px] font-mono font-black text-tech-primary uppercase tracking-widest">INPUT::EMAIL</label>
|
||||
<input
|
||||
type="email"
|
||||
className="w-full bg-black border-b-2 border-tech-border py-4 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-lg text-white uppercase tracking-tight"
|
||||
placeholder="NODE@VANTA_JOURNAL.IO"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<label className="text-[11px] font-mono font-black text-tech-primary uppercase tracking-widest">INPUT::MESSAGE_PAYLOAD</label>
|
||||
<textarea
|
||||
rows={4}
|
||||
className="w-full bg-black border-b-2 border-tech-border py-4 focus:outline-hidden focus:border-tech-primary transition-all font-mono text-lg text-white uppercase tracking-tight resize-none"
|
||||
placeholder="> SYSTEM_LOGS: INIT_MESSAGE..."
|
||||
></textarea>
|
||||
</div>
|
||||
<button className="btn-tech !w-full !py-6 !text-lg">
|
||||
{t.contact.form.submit} <Send size={20} className="ml-3" />
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
|
||||
{/* Info Side */}
|
||||
<div className="space-y-20">
|
||||
<div>
|
||||
<h3 className="text-[12px] font-mono font-black text-white uppercase tracking-[0.4em] mb-12 border-l-4 border-tech-primary pl-6 leading-none">NODE_COORDINATES</h3>
|
||||
<ul className="space-y-10">
|
||||
<li className="flex items-start gap-6">
|
||||
<div className="w-14 h-14 bg-tech-border flex items-center justify-center text-tech-primary shadow-[4px_4px_0px_0px_rgba(255,255,255,1)]">
|
||||
<Mail size={24} />
|
||||
</div>
|
||||
<div className="uppercase italic">
|
||||
<span className="block text-[10px] font-mono font-black text-tech-muted tracking-widest mb-1">DIGITAL_MAIL</span>
|
||||
<span className="font-black text-white text-2xl tracking-tighter">{t.contact.info.email}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-6">
|
||||
<div className="w-14 h-14 bg-tech-border flex items-center justify-center text-tech-primary shadow-[4px_4px_0px_0px_rgba(255,255,255,1)]">
|
||||
<MapPin size={24} />
|
||||
</div>
|
||||
<div className="uppercase italic">
|
||||
<span className="block text-[10px] font-mono font-black text-tech-muted tracking-widest mb-1">PHYSICAL_SECTOR</span>
|
||||
<span className="font-black text-white text-2xl tracking-tighter">{t.contact.info.location}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="p-10 bg-tech-surface border-4 border-tech-primary relative text-white">
|
||||
<div className="absolute -top-1 -right-1 w-6 h-6 bg-white" />
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<Terminal size={20} className="text-tech-primary" />
|
||||
<span className="font-mono font-black uppercase tracking-[0.3em] overflow-hidden whitespace-nowrap">CORE_SYSTEM_STATUS</span>
|
||||
</div>
|
||||
<div className="text-sm font-mono text-tech-muted mb-10 space-y-3 uppercase tracking-widest">
|
||||
<div className="flex justify-between"><span>ACTIVE_SATELLITES:</span> <span className="text-white">12</span></div>
|
||||
<div className="flex justify-between"><span>UPTIME_INDEX:</span> <span className="text-white">99.99%</span></div>
|
||||
<div className="flex justify-between"><span>GLOBAL_PING:</span> <span className="text-white">45MS</span></div>
|
||||
</div>
|
||||
<div className="h-4 w-full bg-black border border-white p-1">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: '85%' }}
|
||||
transition={{ duration: 1.5, repeat: Infinity, ease: "linear" }}
|
||||
className="h-full bg-tech-primary shadow-[0_0_15px_rgba(204,255,0,0.5)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
240
Template-02/src/pages/Home.tsx
Normal file
240
Template-02/src/pages/Home.tsx
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { Newsletter } from '../components/Newsletter';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowRight, Sparkles, Terminal, Cpu, Database, Code2, TrendingUp, Clock, User } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { cn, formatDate } from '../lib/utils';
|
||||
|
||||
export default function Home() {
|
||||
const { t, posts, lang } = useI18n();
|
||||
const validPosts = (posts || []).filter(p => p.id !== 'privacy-policy' && p.id !== 'terms-of-service');
|
||||
|
||||
// Featured posts for the masonry hero
|
||||
const featuredPosts = validPosts.slice(0, 3);
|
||||
// Posts for the main list
|
||||
const recentPosts = validPosts.slice(3, 9);
|
||||
// Posts for the sidebar
|
||||
const sidebarPosts = validPosts.slice(0, 5);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen pb-20">
|
||||
<SEO title={t.home.hero.title} description={t.home.hero.subtitle} />
|
||||
|
||||
{/* Magazine Hero Masonry */}
|
||||
<section className="container mx-auto px-4 md:px-6 lg:px-12 pt-6 md:pt-12 pb-16">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-1 mb-8 md:mb-16 h-auto lg:h-[600px] bg-white/5 border border-white/5 p-1">
|
||||
{/* Main Hero (Slot 1) */}
|
||||
<div className="lg:col-span-8 h-[350px] md:h-[450px] lg:h-full relative overflow-hidden group">
|
||||
<Link to={`/${lang}/blog/${featuredPosts[0]?.slug}`} className="block h-full cursor-pointer">
|
||||
<img
|
||||
src={featuredPosts[0]?.image}
|
||||
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-105"
|
||||
alt={featuredPosts[0]?.title}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-t from-black via-black/40 to-transparent" />
|
||||
<div className="absolute bottom-0 left-0 p-6 md:p-10 w-full">
|
||||
<span className="category-tag mb-4 shadow-lg">{featuredPosts[0]?.category}</span>
|
||||
<h2 className="text-xl md:text-3xl lg:text-4xl font-extrabold text-white mb-4 md:mb-6 leading-tight max-w-3xl group-hover:text-tech-primary transition-colors uppercase italic tracking-tighter">
|
||||
{featuredPosts[0]?.title}
|
||||
</h2>
|
||||
<div className="flex items-center gap-4 md:gap-6 text-[10px] md:text-[11px] font-bold text-white/50 uppercase tracking-widest">
|
||||
<span className="flex items-center gap-2"><User size={12} className="text-tech-primary" /> {featuredPosts[0]?.author}</span>
|
||||
<span className="flex items-center gap-2"><Clock size={12} className="text-tech-primary" /> {featuredPosts[0]?.readingTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Secondary Heroes (Slots 2 & 3) */}
|
||||
<div className="lg:col-span-4 flex flex-col md:flex-row lg:flex-col gap-1 h-auto lg:h-full">
|
||||
{featuredPosts.slice(1, 3).map((post) => (
|
||||
<div key={post.id} className="flex-1 relative overflow-hidden group min-h-[200px] md:min-h-[250px]">
|
||||
<Link to={`/${lang}/blog/${post.slug}`} className="block h-full cursor-pointer">
|
||||
<img
|
||||
src={post.image}
|
||||
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-105"
|
||||
alt={post.title}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-t from-black via-black/40 to-transparent" />
|
||||
<div className="absolute bottom-0 left-0 p-5 md:p-8">
|
||||
<span className="category-tag mb-3 !text-[8px]">{post.category}</span>
|
||||
<h3 className="text-lg md:text-xl font-bold text-white leading-tight group-hover:text-tech-primary transition-colors uppercase italic tracking-tighter">
|
||||
{post.title}
|
||||
</h3>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Trending Marquee Bar */}
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 md:gap-6 py-4 border-y border-white/5 text-[10px] md:text-sm">
|
||||
<div className="flex items-center gap-2 text-tech-primary font-black whitespace-nowrap">
|
||||
<TrendingUp size={16} />
|
||||
<span className="uppercase tracking-widest text-[9px] md:text-xs">Trending:</span>
|
||||
</div>
|
||||
<div className="flex-grow overflow-hidden relative grayscale hover:grayscale-0 transition-all">
|
||||
<div className="flex gap-8 md:gap-12 animate-marquee whitespace-nowrap">
|
||||
{validPosts.slice(0, 5).map((post) => (
|
||||
<Link
|
||||
key={`trend-${post.id}`}
|
||||
to={`/${lang}/blog/${post.slug}`}
|
||||
className="hover:text-tech-primary transition-colors font-bold text-tech-muted uppercase tracking-tight"
|
||||
>
|
||||
{post.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Main Magazine Layout */}
|
||||
<section className="container mx-auto px-4 md:px-6 lg:px-12 grid grid-cols-1 lg:grid-cols-12 gap-12 pt-12 md:pt-16">
|
||||
{/* Primary Content Area */}
|
||||
<div className="lg:col-span-8">
|
||||
<div className="flex items-center justify-between mb-8 pb-4 border-b border-white/10">
|
||||
<h2 className="text-xl md:text-2xl font-black uppercase tracking-tighter skew-x-[-10deg] italic">
|
||||
<span className="bg-tech-primary text-black px-3 py-1 mr-2 tracking-widest">Latest</span>
|
||||
Intelligence
|
||||
</h2>
|
||||
<Link to={`/${lang}/blog`} className="text-[9px] font-bold text-tech-muted hover:text-tech-primary flex items-center gap-2 uppercase tracking-[0.2em]">
|
||||
Archive <ArrowRight size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8">
|
||||
{recentPosts.map((post) => (
|
||||
<PostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mid-page Feature */}
|
||||
{validPosts[10] && (
|
||||
<div className="mt-16 relative aspect-[4/3] md:aspect-[21/9] overflow-hidden group border border-white/5">
|
||||
<img src={validPosts[10].image} className="w-full h-full object-cover grayscale opacity-60 group-hover:grayscale-0 group-hover:opacity-100 transition-all duration-1000" alt="Featured" />
|
||||
<div className="absolute inset-0 bg-linear-to-r from-black via-black/50 to-transparent flex flex-col justify-end md:justify-center p-6 md:p-16">
|
||||
<span className="category-tag mb-4 w-fit !text-[8px]">{validPosts[10].category}</span>
|
||||
<h2 className="text-2xl md:text-5xl font-black text-white max-w-2xl mb-6 leading-tight uppercase italic tracking-tighter">
|
||||
{validPosts[10].title}
|
||||
</h2>
|
||||
<Link to={`/${lang}/blog/${validPosts[10].slug}`} className="btn-mag w-fit uppercase tracking-tighter !px-4 !py-1.5 !text-xs">
|
||||
Read Depth Feature
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Intelligence Sidebar */}
|
||||
<aside className="lg:col-span-4 space-y-12">
|
||||
{/* Section: Categories */}
|
||||
<div>
|
||||
<h3 className="text-sm font-black uppercase tracking-[0.2em] mb-8 flex items-center gap-3">
|
||||
<div className="w-8 h-1 bg-tech-primary" />
|
||||
Knowledge Nodes
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{[
|
||||
{ id: 'ai', label: 'NEURAL_NETWORKS', count: 12, icon: Sparkles },
|
||||
{ id: 'code', label: 'SYSTEM_LOGIC', count: 8, icon: Code2 },
|
||||
{ id: 'tools', label: 'ENGINEERING_LAB', count: 15, icon: Terminal },
|
||||
{ id: 'startups', label: 'GROWTH_STRAT', count: 5, icon: Cpu }
|
||||
].map((cat) => (
|
||||
<Link
|
||||
key={cat.id}
|
||||
to={`/${lang}/blog/category/${cat.id}`}
|
||||
className="flex items-center justify-between p-4 bg-white/[0.01] border-l-2 border-transparent hover:border-tech-primary hover:bg-white/[0.03] transition-all group"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<cat.icon size={20} className="text-tech-muted group-hover:text-tech-primary transition-colors" />
|
||||
<span className="font-bold text-xs uppercase tracking-widest">{cat.label}</span>
|
||||
</div>
|
||||
<span className="text-[10px] bg-white/5 px-2 py-1 font-mono text-tech-muted">{cat.count}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section: Must Read */}
|
||||
<div>
|
||||
<h3 className="text-xs font-black uppercase tracking-[0.2em] mb-8 flex items-center gap-3">
|
||||
<div className="w-6 h-1 bg-tech-secondary" />
|
||||
Priority Protocol
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
{sidebarPosts.map((post, i) => (
|
||||
<Link key={`popular-${post.id}`} to={`/${lang}/blog/${post.slug}`} className="flex gap-4 group items-center border-b border-white/5 pb-6 last:border-0">
|
||||
<div className="text-xl font-black text-white/5 group-hover:text-tech-primary/20 transition-colors leading-none">0{i+1}</div>
|
||||
<div className="flex-grow">
|
||||
<span className="text-[8px] font-bold text-tech-primary uppercase tracking-widest block mb-1">{post.category}</span>
|
||||
<h4 className="font-bold text-xs text-white group-hover:text-tech-primary transition-colors leading-tight uppercase tracking-tight">{post.title}</h4>
|
||||
</div>
|
||||
<div className="w-12 h-12 shrink-0 bg-white/5 overflow-hidden">
|
||||
<img src={post.image} className="w-full h-full object-cover group-hover:scale-110 transition-transform grayscale opacity-50 group-hover:grayscale-0 group-hover:opacity-100" alt="" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Newsletter Module */}
|
||||
<div className="relative group p-8 bg-tech-surface border border-white/5 overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-2 opacity-20">
|
||||
<Terminal size={40} className="text-tech-primary" />
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center gap-2 text-tech-primary text-[10px] font-black uppercase tracking-[0.3em] mb-4">
|
||||
<span className="w-6 h-[2px] bg-tech-primary"></span>
|
||||
<span>Sub_Protocol</span>
|
||||
</div>
|
||||
<h3 className="text-2xl font-black text-white italic tracking-tighter uppercase mb-2 leading-none">
|
||||
Direct <br /> Intel_Feed
|
||||
</h3>
|
||||
<p className="text-tech-muted text-[11px] font-bold uppercase tracking-tight mb-8 leading-tight">
|
||||
Architectural insights delivered to your local node weekly.
|
||||
</p>
|
||||
|
||||
<form className="space-y-4" onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="USER_EMAIL@PROTO.IO"
|
||||
className="w-full bg-white/5 border-b-2 border-white/10 px-0 py-3 text-xs font-mono text-white placeholder:text-white/20 focus:outline-none focus:border-tech-primary transition-colors uppercase"
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 w-0 h-[2px] bg-tech-primary transition-all duration-500 group-focus-within:w-full"></div>
|
||||
</div>
|
||||
<button className="w-full bg-tech-primary text-black font-black text-[10px] uppercase tracking-[0.4em] py-4 skew-x-[-10deg] hover:bg-white hover:text-black transition-all active:scale-95 italic">
|
||||
[ Execute_Sync ]
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 flex items-center justify-between text-[8px] font-mono text-white/20 uppercase tracking-widest">
|
||||
<span>Status: Ready</span>
|
||||
<span>Enc_V2.4</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Design Detail */}
|
||||
<div className="absolute -bottom-4 -right-4 w-24 h-24 bg-tech-primary/5 rounded-full blur-3xl group-hover:bg-tech-primary/10 transition-colors"></div>
|
||||
</div>
|
||||
|
||||
{/* Connect Tags */}
|
||||
<div>
|
||||
<h3 className="text-xs font-black uppercase tracking-[0.2em] mb-6">Popular_Tags</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{['#NEXTJS', '#CYBERSECURITY', '#VECTOR_DB', '#LLM', '#ARCHITECTURE', '#RUST', '#SCALABILITY'].map(tag => (
|
||||
<span key={tag} className="px-3 py-1 bg-white/5 hover:bg-tech-primary/10 transition-colors border border-white/5 text-[10px] font-bold text-tech-muted hover:text-white cursor-pointer uppercase tracking-tighter">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
Template-02/src/pages/Legal.tsx
Normal file
65
Template-02/src/pages/Legal.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { motion } from 'motion/react';
|
||||
import { ArrowLeft, Shield, FileText, Scale } from 'lucide-react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
export default function Legal() {
|
||||
const { t, lang } = useI18n();
|
||||
const location = useLocation();
|
||||
|
||||
const isPrivacy = location.pathname.includes('privacy');
|
||||
const isTerms = location.pathname.includes('terms');
|
||||
const isEthics = location.pathname.includes('ethics');
|
||||
|
||||
let config = t.legal.privacy;
|
||||
let Icon = Shield;
|
||||
let code = "PRIVACY_PROTOCOL";
|
||||
|
||||
if (isTerms) {
|
||||
config = t.legal.terms;
|
||||
Icon = FileText;
|
||||
code = "SERVICE_TERMS";
|
||||
} else if (isEthics) {
|
||||
config = t.legal.ethics;
|
||||
Icon = Scale;
|
||||
code = "ETHICS_V4.0";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-black min-h-screen pt-40 pb-32">
|
||||
<SEO title={config.title} description={config.content} />
|
||||
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<header className="max-w-4xl mb-24">
|
||||
<Link to={`/${lang}`} className="inline-flex items-center gap-3 text-tech-primary text-[11px] font-mono font-black mb-12 hover:text-white transition-colors uppercase tracking-[0.3em] bg-tech-surface px-4 py-2 border border-tech-border">
|
||||
<ArrowLeft size={14} /> ./GET_BACK
|
||||
</Link>
|
||||
<div className="inline-flex items-center gap-3 text-tech-primary text-[11px] font-mono font-black uppercase tracking-[0.4em] mb-6">
|
||||
<Icon size={18} />
|
||||
<span>{code}</span>
|
||||
</div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-4xl md:text-6xl font-bold mb-8 text-white tracking-tight"
|
||||
>
|
||||
{config.title}
|
||||
</motion.h1>
|
||||
</header>
|
||||
|
||||
<article className="max-w-5xl">
|
||||
<div className="bg-tech-surface border-4 border-tech-border p-12 lg:p-20 font-medium text-tech-muted text-xl leading-relaxed whitespace-pre-line italic shadow-[16px_16px_0px_0px_rgba(255,255,255,1)]">
|
||||
{config.content}
|
||||
|
||||
<div className="mt-20 pt-10 border-t border-tech-border text-[12px] font-mono font-black uppercase tracking-widest text-tech-primary">
|
||||
LAST_COMMIT: {new Date().toISOString().split('T')[0]}<br/>
|
||||
REVISION: v2.0.18-FINAL
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
232
Template-02/src/pages/PostDetail.tsx
Normal file
232
Template-02/src/pages/PostDetail.tsx
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
import React from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { TableOfContents } from '../components/TableOfContents';
|
||||
import { Newsletter } from '../components/Newsletter';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { formatDate } from '../lib/utils';
|
||||
import { ArrowLeft, Clock, Twitter, Linkedin, Link as LinkIcon, Cpu, Terminal } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
import Markdown from 'react-markdown';
|
||||
|
||||
export default function PostDetail() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const { lang, posts, t } = useI18n();
|
||||
|
||||
const validPosts = posts || [];
|
||||
const post = validPosts.find(p => p.slug === slug);
|
||||
const relatedPosts = validPosts.filter(p => p.slug !== slug && (p.category === post?.category || p.featured)).slice(0, 3);
|
||||
|
||||
if (!post) {
|
||||
return (
|
||||
<div className="container mx-auto px-6 py-40 text-center font-mono uppercase tracking-widest text-tech-muted">
|
||||
<h1 className="text-4xl font-bold mb-8 text-tech-text">[error] 404_NOT_FOUND</h1>
|
||||
<Link to={`/${lang}/blog`} className="text-tech-primary inline-flex items-center gap-2 font-bold bg-tech-surface px-6 py-2 rounded">
|
||||
<ArrowLeft size={16} /> RETURN_TO_BASE
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-black min-h-screen">
|
||||
<SEO
|
||||
title={post.title}
|
||||
description={post.description}
|
||||
image={post.image}
|
||||
article
|
||||
author={post.author}
|
||||
datePublished={post.date}
|
||||
category={post.category}
|
||||
/>
|
||||
|
||||
{/* Magazine Article Header */}
|
||||
<section className="pt-20 pb-10 bg-black border-b border-white/5">
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
<div className="max-w-4xl">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Link to={`/${lang}/blog`} className="text-[9px] font-black uppercase text-tech-muted hover:text-white transition-colors tracking-[0.2em]">
|
||||
{t.nav.blog}
|
||||
</Link>
|
||||
<span className="text-white/20">/</span>
|
||||
<span className="category-tag !text-[9px] !px-2 !py-0.5">
|
||||
{post.category}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl md:text-5xl lg:text-6xl font-black text-white leading-[1.05] tracking-tighter mb-8 uppercase italic">
|
||||
{post.title}
|
||||
</h1>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-6 mb-8 py-4 border-y border-white/5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-tech-primary flex items-center justify-center text-black font-black italic skew-x-[-10deg]">
|
||||
{post.author[0]}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<span className="text-[8px] font-bold text-tech-muted uppercase block tracking-[0.2em] mb-0.5">Author</span>
|
||||
<span className="font-bold text-xs text-white uppercase">{post.author}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:block w-px h-6 bg-white/10" />
|
||||
|
||||
<div className="text-left">
|
||||
<span className="text-[8px] font-bold text-tech-muted uppercase block tracking-[0.2em] mb-0.5">Record</span>
|
||||
<span className="font-bold text-xs text-white uppercase">{formatDate(post.date, lang)}</span>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:block w-px h-6 bg-white/10" />
|
||||
|
||||
<div className="text-left">
|
||||
<span className="text-[8px] font-bold text-tech-muted uppercase block tracking-[0.2em] mb-0.5">Complexity</span>
|
||||
<span className="font-bold text-xs text-white uppercase">{post.readingTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Hero Image Section */}
|
||||
<section className="container mx-auto px-6 lg:px-12 -mt-4 mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="aspect-video lg:aspect-[21/9] overflow-hidden border border-white/5 bg-white/5 relative"
|
||||
>
|
||||
<img
|
||||
src={post.image}
|
||||
className="w-full h-full object-cover grayscale opacity-90 hover:grayscale-0 hover:opacity-100 transition-all duration-700"
|
||||
alt={post.title}
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-t from-black/60 to-transparent" />
|
||||
<div className="absolute bottom-6 left-6 text-[10px] font-mono font-bold text-white/40 uppercase tracking-[0.3em]">
|
||||
Lumix_Asset_ID: {post.slug}
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Main Content Layout */}
|
||||
<section className="container mx-auto px-6 lg:px-12 pb-32">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16">
|
||||
|
||||
{/* Main Body */}
|
||||
<div className="lg:col-span-8">
|
||||
<article className="markdown-body">
|
||||
<Markdown>{post.content}</Markdown>
|
||||
</article>
|
||||
|
||||
{/* Post Footer/Sharing */}
|
||||
<div className="mt-20 pt-10 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-8">
|
||||
<div className="flex flex-wrap gap-2 text-[10px] font-bold">
|
||||
{['#TECH', '#FUTURE', '#AI', post.category.toUpperCase()].map(tag => (
|
||||
<span key={tag} className="px-3 py-1 bg-white/5 border border-white/5 text-tech-muted hover:text-white cursor-pointer uppercase">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<span className="text-[10px] font-bold text-tech-muted uppercase tracking-widest self-center mr-2">Share Transmission:</span>
|
||||
<a href="#" className="w-10 h-10 bg-white/5 border border-white/5 flex items-center justify-center text-white hover:border-tech-primary hover:text-tech-primary transition-all"><Twitter size={18} /></a>
|
||||
<a href="#" className="w-10 h-10 bg-white/5 border border-white/5 flex items-center justify-center text-white hover:border-tech-primary hover:text-tech-primary transition-all"><Linkedin size={18} /></a>
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(window.location.href)}
|
||||
className="w-10 h-10 bg-white/5 border border-white/5 flex items-center justify-center text-white hover:border-tech-primary hover:text-tech-primary transition-all"
|
||||
>
|
||||
<LinkIcon size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Area */}
|
||||
<aside className="lg:col-span-4 space-y-16">
|
||||
<div className="sticky top-32 space-y-16">
|
||||
{/* TOC Table of Contents */}
|
||||
<div>
|
||||
<h3 className="text-sm font-black uppercase tracking-[0.2em] mb-8 flex items-center gap-3">
|
||||
<div className="w-8 h-1 bg-tech-primary" />
|
||||
Archive Index
|
||||
</h3>
|
||||
<TableOfContents content={post.content} />
|
||||
</div>
|
||||
|
||||
{/* Author Extra */}
|
||||
<div className="p-6 border border-white/5 bg-white/[0.01] relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 w-1.5 h-1.5 bg-tech-primary" />
|
||||
<h4 className="text-[9px] font-black uppercase tracking-[0.2em] text-tech-primary mb-4">Database_Entry</h4>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-10 h-10 bg-tech-primary flex items-center justify-center text-black font-black text-xl italic skew-x-[-10deg]">
|
||||
{post.author[0]}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-black text-white block text-sm uppercase italic tracking-tighter leading-tight">{post.author}</span>
|
||||
<span className="text-tech-muted text-[8px] font-bold uppercase tracking-widest">Senior Analyst</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[11px] text-tech-muted leading-relaxed italic mb-4">
|
||||
Decoding technical architectures and future protocols.
|
||||
</p>
|
||||
<button className="text-[9px] font-bold text-tech-primary uppercase tracking-widest hover:underline">View Intelligence</button>
|
||||
</div>
|
||||
|
||||
{/* Priority Recs */}
|
||||
<div>
|
||||
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] mb-6 flex items-center gap-2">
|
||||
<div className="w-6 h-1 bg-tech-secondary" />
|
||||
Sync Related
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
{relatedPosts.map((rp, i) => (
|
||||
<Link key={rp.id} to={`/${lang}/blog/${rp.slug}`} className="flex gap-4 group items-center">
|
||||
<div className="text-xl font-black text-white/5 group-hover:text-tech-primary/20 transition-colors leading-none">0{i+1}</div>
|
||||
<div className="flex-grow">
|
||||
<span className="text-[8px] font-bold text-tech-primary uppercase tracking-widest block mb-1">{rp.category}</span>
|
||||
<h4 className="font-bold text-[11px] text-white group-hover:text-tech-primary transition-colors leading-tight">{rp.title}</h4>
|
||||
</div>
|
||||
<div className="w-12 h-12 shrink-0 bg-white/5">
|
||||
<img src={rp.image} className="w-full h-full object-cover grayscale opacity-50 group-hover:grayscale-0 group-hover:opacity-100 transition-all" alt="" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Related Technical Modules */}
|
||||
{relatedPosts.length > 0 && (
|
||||
<section className="py-32 bg-tech-surface border-t border-tech-border relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(#1a1a1a_1px,transparent_1px)] bg-[size:32px_32px] opacity-30" />
|
||||
<div className="container mx-auto px-6 lg:px-12 relative z-10">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-end justify-between mb-12 md:mb-20 gap-8">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 text-tech-primary text-[10px] md:text-[11px] font-mono font-black uppercase tracking-[0.4em] mb-4 md:mb-6">
|
||||
<div className="w-8 md:w-12 h-1 bg-tech-primary" />
|
||||
<span>SYNC_REL_NODES</span>
|
||||
</div>
|
||||
<h2 className="text-3xl md:text-5xl lg:text-6xl font-black tracking-tighter text-white uppercase italic leading-none">PEER_STREAM</h2>
|
||||
</div>
|
||||
<Link to={`/${lang}/blog`} className="text-[10px] md:text-[11px] font-mono font-black text-white hover:text-tech-primary transition-colors uppercase tracking-widest border-b-2 border-tech-primary pb-1 transform hover:translate-x-2 transition-transform italic w-fit">[ LIST_FULL_RECORDS ]</Link>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
|
||||
{relatedPosts.map(post => (
|
||||
<div key={post.id} className="relative">
|
||||
<div className="absolute -top-3 -left-3 w-6 h-6 border-t-2 border-l-2 border-tech-primary pointer-events-none z-10" />
|
||||
<PostCard post={post} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<Newsletter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
97
Template-02/src/types.ts
Normal file
97
Template-02/src/types.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
export type Language = 'pt' | 'en' | 'es';
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
category: string;
|
||||
author: string;
|
||||
date: string;
|
||||
readingTime: string;
|
||||
image: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export interface Translations {
|
||||
nav: {
|
||||
home: string;
|
||||
blog: string;
|
||||
about: string;
|
||||
categories: string;
|
||||
};
|
||||
home: {
|
||||
hero: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
cta: string;
|
||||
};
|
||||
featured: string;
|
||||
recent: string;
|
||||
newsletter: {
|
||||
title: string;
|
||||
desc: string;
|
||||
placeholder: string;
|
||||
button: string;
|
||||
};
|
||||
};
|
||||
blog: {
|
||||
categories: {
|
||||
all: string;
|
||||
ai: string;
|
||||
code: string;
|
||||
startups: string;
|
||||
tools: string;
|
||||
};
|
||||
readMore: string;
|
||||
related: string;
|
||||
back: string;
|
||||
toc: string;
|
||||
};
|
||||
footer: {
|
||||
about: string;
|
||||
connect: string;
|
||||
rights: string;
|
||||
terms: string;
|
||||
ethics: string;
|
||||
newsletter: string;
|
||||
rss: string;
|
||||
contact: string;
|
||||
privacy: string;
|
||||
};
|
||||
legal: {
|
||||
privacy: { title: string; content: string; };
|
||||
terms: { title: string; content: string; };
|
||||
ethics: { title: string; content: string; };
|
||||
};
|
||||
contact: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
info: {
|
||||
title: string;
|
||||
email: string;
|
||||
location: string;
|
||||
};
|
||||
status: {
|
||||
title: string;
|
||||
nodes: string;
|
||||
uptime: string;
|
||||
latency: string;
|
||||
};
|
||||
form: {
|
||||
name: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
submit: string;
|
||||
sending: string;
|
||||
success: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface SiteContent {
|
||||
posts: Record<Language, Post[]>;
|
||||
ui: Record<Language, Translations>;
|
||||
}
|
||||
26
Template-02/tsconfig.json
Normal file
26
Template-02/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
24
Template-02/vite.config.ts
Normal file
24
Template-02/vite.config.ts
Normal 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',
|
||||
},
|
||||
};
|
||||
});
|
||||
14
Template-03/README.md
Normal file
14
Template-03/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Nexus Tech Blog
|
||||
Very professional static blog.
|
||||
|
||||
## Setup
|
||||
Install dependencies:
|
||||
`npm install`
|
||||
|
||||
## Technologies
|
||||
- React 18
|
||||
- Vite
|
||||
- Tailwind CSS
|
||||
- Motion (framer-motion)
|
||||
- Lucide React
|
||||
- React Helmet Async (SEO)
|
||||
13
Template-03/index.html
Normal file
13
Template-03/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><rect width=%22100%22 height=%22100%22 rx=%2220%22 fill=%22%230070F3%22/><text y=%22.9em%22 font-size=%2280%22 x=%2250%%22 text-anchor=%22middle%22 fill=%22white%22 font-family=%22monospace%22 font-weight=%22bold%22>N</text></svg>" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
Template-03/metadata.json
Normal file
6
Template-03/metadata.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Lumix Tech Blog",
|
||||
"description": "A cutting-edge, future-focused technology blog with neo-futuristic design and bento layouts.",
|
||||
"requestFramePermissions": [],
|
||||
"majorCapabilities": []
|
||||
}
|
||||
5633
Template-03/package-lock.json
generated
Normal file
5633
Template-03/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
39
Template-03/package.json
Normal file
39
Template-03/package.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port=3000 --host=0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.29.0",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.2",
|
||||
"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.15.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
4
Template-03/public/robots.txt
Normal file
4
Template-03/public/robots.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://nexus-blog.tech/sitemap.xml
|
||||
62
Template-03/public/sitemap.xml
Normal file
62
Template-03/public/sitemap.xml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<!-- Main Pages -->
|
||||
<url><loc>https://nexus-blog.tech/pt</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/es</loc><priority>1.0</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog</loc><priority>0.8</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog</loc><priority>0.8</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/es/blog</loc><priority>0.8</priority></url>
|
||||
|
||||
<!-- Categories -->
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/ai</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/code</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/startups</loc><priority>0.6</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/category/tools</loc><priority>0.6</priority></url>
|
||||
|
||||
<!-- PT Posts -->
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/futuro-ia-generativa-2024</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/typescript-moderno-guia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/escalando-startups-bootstrap</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/ferramentas-indispensaveis-dev</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/web-components-nativos</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/clean-architecture-js</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/seguranca-ciber-ia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/trabalho-remoto-fatos</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/design-system-flexivel</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/webgpu-ias-locais</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/precificacao-saas</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/nextjs-14-enterprise</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/etica-algoritmica</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/figma-dev-mode</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/rust-backends</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/growth-hacking-real</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/documentacao-agil</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/medicina-ia</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/testes-frontend</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/nomadismo-dev</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/pt/blog/sustentabilidade-tech</loc><priority>0.7</priority></url>
|
||||
|
||||
<!-- EN Posts -->
|
||||
<url><loc>https://nexus-blog.tech/en/blog/future-generative-ai-2024</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/modern-typescript-guide</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/scaling-startups-bootstrap</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/essential-dev-tools</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/native-web-components</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/clean-architecture-js</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/ai-cybersecurity</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/remote-work-facts</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/flexible-design-system</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/webgpu-local-ai</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/saas-pricing</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/nextjs-14-enterprise</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/algorithmic-ethics</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/figma-dev-mode</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/rust-backends</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/real-growth-hacking</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/agile-documentation</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/medicine-ai</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/frontend-testing</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/dev-nomadism</loc><priority>0.7</priority></url>
|
||||
<url><loc>https://nexus-blog.tech/en/blog/tech-sustainability</loc><priority>0.7</priority></url>
|
||||
</urlset>
|
||||
57
Template-03/src/App.tsx
Normal file
57
Template-03/src/App.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import React, { lazy, Suspense } from 'react';
|
||||
import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { Layout } from './components/Layout';
|
||||
import Home from './pages/Home';
|
||||
import BlogList from './pages/BlogList';
|
||||
import PostDetail from './pages/PostDetail';
|
||||
|
||||
// Wrapper to handle language validation and layout injection
|
||||
const LangWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { lang } = useParams<{ lang: string }>();
|
||||
const validLangs = ['en', 'pt', 'es'];
|
||||
|
||||
if (!lang || !validLangs.includes(lang)) {
|
||||
return <Navigate to="/pt" replace />;
|
||||
}
|
||||
|
||||
return <Layout>{children}</Layout>;
|
||||
};
|
||||
|
||||
const Contact = lazy(() => import('./pages/Contact'));
|
||||
const Legal = lazy(() => import('./pages/Legal'));
|
||||
const About = lazy(() => import('./pages/About'));
|
||||
const Write = lazy(() => import('./pages/Write'));
|
||||
const Login = lazy(() => import('./pages/Login'));
|
||||
|
||||
import { ScrollToTop } from './components/ScrollToTop';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<BrowserRouter>
|
||||
<ScrollToTop />
|
||||
<Suspense fallback={<div className="min-h-screen bg-black flex items-center justify-center font-mono text-[10px] font-black uppercase tracking-[0.5em] text-tech-primary animate-pulse italic">VANTA_KERN_INIT...</div>}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/pt" replace />} />
|
||||
|
||||
<Route path="/:lang" element={<LangWrapper><Home /></LangWrapper>} />
|
||||
<Route path="/:lang/blog" element={<LangWrapper><BlogList /></LangWrapper>} />
|
||||
<Route path="/:lang/blog/category/:category" element={<LangWrapper><BlogList /></LangWrapper>} />
|
||||
<Route path="/:lang/blog/:slug" element={<LangWrapper><PostDetail /></LangWrapper>} />
|
||||
|
||||
<Route path="/:lang/contact" element={<LangWrapper><Contact /></LangWrapper>} />
|
||||
<Route path="/:lang/about" element={<LangWrapper><About /></LangWrapper>} />
|
||||
<Route path="/:lang/write" element={<LangWrapper><Write /></LangWrapper>} />
|
||||
<Route path="/:lang/login" element={<LangWrapper><Login /></LangWrapper>} />
|
||||
<Route path="/:lang/privacy" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
<Route path="/:lang/terms" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
<Route path="/:lang/ethics" element={<LangWrapper><Legal /></LangWrapper>} />
|
||||
|
||||
<Route path="*" element={<Navigate to="/pt" replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
</HelmetProvider>
|
||||
);
|
||||
}
|
||||
275
Template-03/src/components/Layout.tsx
Normal file
275
Template-03/src/components/Layout.tsx
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import React from 'react';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Language } from '../types';
|
||||
import { Menu, X, Github, Twitter, Linkedin, Search, User, ChevronDown } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
export const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { lang, t, changeLanguage } = useI18n();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
||||
const [isScrolled, setIsScrolled] = React.useState(false);
|
||||
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
|
||||
const [searchQuery, setSearchQuery] = React.useState('');
|
||||
|
||||
React.useEffect(() => {
|
||||
if (location.pathname === '/' || location.pathname === '') {
|
||||
navigate('/pt', { replace: true });
|
||||
}
|
||||
}, [location.pathname, navigate]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleScroll = () => setIsScrolled(window.scrollY > 50);
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (searchQuery.trim()) {
|
||||
setIsSearchOpen(false);
|
||||
navigate(`/${lang}/blog?q=${encodeURIComponent(searchQuery)}`);
|
||||
setSearchQuery('');
|
||||
}
|
||||
};
|
||||
|
||||
const languages: { code: Language; label: string }[] = [
|
||||
{ code: 'pt', label: 'PT' },
|
||||
{ code: 'en', label: 'EN' },
|
||||
{ code: 'es', label: 'ES' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col font-sans bg-gray-50">
|
||||
|
||||
{/* Top Bar */}
|
||||
<div className="bg-gray-900 border-b border-gray-800 py-2 hidden md:block">
|
||||
<div className="container-inner flex justify-between items-center text-[12px] font-medium text-gray-400">
|
||||
<div className="flex gap-6 items-center">
|
||||
<span>Quinta-feira, 14 de Maio de 2026</span>
|
||||
<div className="flex gap-4">
|
||||
<Link to={`/${lang}/about`} className="hover:text-white transition-colors">Sobre Nós</Link>
|
||||
<Link to={`/${lang}/contact`} className="hover:text-white transition-colors">Contato</Link>
|
||||
<Link to={`/${lang}/write`} className="hover:text-white transition-colors">Escreva para Nós</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex gap-3">
|
||||
<a href="#" className="hover:text-white transition-colors"><Twitter size={14} /></a>
|
||||
<a href="#" className="hover:text-white transition-colors"><Github size={14} /></a>
|
||||
<a href="#" className="hover:text-white transition-colors"><Linkedin size={14} /></a>
|
||||
</div>
|
||||
<div className="w-px h-3 bg-gray-700 mx-2"></div>
|
||||
<Link to={`/${lang}/login`} className="flex items-center gap-1 hover:text-white transition-colors">
|
||||
<User size={14} /> Entrar
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Header */}
|
||||
<header className="bg-white border-b border-gray-200">
|
||||
<div className="container-inner py-6 flex items-center justify-between">
|
||||
<Link to={`/${lang}`} className="flex items-center gap-2">
|
||||
<div className="w-10 h-10 bg-brand-primary text-white rounded-md flex items-center justify-center font-bold text-2xl font-heading">
|
||||
C
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold text-2xl tracking-tight text-gray-900 font-heading leading-none">CodeChronicle</span>
|
||||
<span className="text-[11px] font-medium text-gray-500 uppercase tracking-widest mt-0.5">Tech & Tutorials</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
|
||||
|
||||
<div className="lg:hidden flex items-center gap-4">
|
||||
<button onClick={() => setIsSearchOpen(true)}><Search size={22} className="text-gray-700" /></button>
|
||||
<button onClick={() => setIsMenuOpen(!isMenuOpen)}>
|
||||
{isMenuOpen ? <X size={26} className="text-gray-900" /> : <Menu size={26} className="text-gray-900" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Nav (Sticky) */}
|
||||
<div className={cn(
|
||||
"bg-white transition-all shadow-sm z-50",
|
||||
isScrolled ? "fixed top-0 left-0 w-full animate-in fade-in slide-in-from-top-4" : "border-b border-gray-100"
|
||||
)}>
|
||||
<div className="container-inner flex items-center justify-between">
|
||||
<nav className="hidden lg:flex items-center space-x-1">
|
||||
<Link to={`/${lang}`} className="px-3 py-4 text-[14px] font-bold text-gray-900 hover:text-brand-primary transition-colors flex items-center gap-1">
|
||||
{t.nav.home}
|
||||
</Link>
|
||||
<div className="relative group/nav">
|
||||
<Link to={`/${lang}/blog/category/code`} className="px-3 py-4 text-[14px] font-bold text-gray-900 hover:text-brand-primary transition-colors flex items-center gap-1">
|
||||
Dev Web <ChevronDown size={14} className="text-gray-400" />
|
||||
</Link>
|
||||
</div>
|
||||
<Link to={`/${lang}/blog/category/ai`} className="px-3 py-4 text-[14px] font-bold text-gray-900 hover:text-brand-primary transition-colors flex items-center gap-1">
|
||||
{t.blog.categories.ai}
|
||||
</Link>
|
||||
<Link to={`/${lang}/blog/category/tools`} className="px-3 py-4 text-[14px] font-bold text-gray-900 hover:text-brand-primary transition-colors flex items-center gap-1">
|
||||
Tópicos <ChevronDown size={14} className="text-gray-400" />
|
||||
</Link>
|
||||
<Link to={`/${lang}/blog`} className="px-3 py-4 text-[14px] font-bold text-gray-900 hover:text-brand-primary transition-colors flex items-center gap-1">
|
||||
Tutoriais
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="hidden lg:flex items-center gap-6">
|
||||
{/* Language Switcher */}
|
||||
<div className="flex gap-2 items-center">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={l.code}
|
||||
onClick={() => changeLanguage(l.code)}
|
||||
className={cn(
|
||||
"text-[11px] font-bold uppercase transition-colors px-1.5 py-0.5 rounded",
|
||||
lang === l.code ? "bg-brand-primary text-white" : "text-gray-500 hover:text-gray-900"
|
||||
)}
|
||||
>
|
||||
{l.code}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsSearchOpen(true)}
|
||||
className="text-gray-900 hover:text-brand-primary transition-colors py-4"
|
||||
>
|
||||
<Search size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="lg:hidden bg-white border-b border-gray-200 overflow-hidden"
|
||||
>
|
||||
<div className="px-4 py-4 space-y-2">
|
||||
<Link to={`/${lang}`} className="block py-2 text-base font-bold text-gray-900">{t.nav.home}</Link>
|
||||
<Link to={`/${lang}/blog/category/code`} className="block py-2 text-base font-bold text-gray-900">Dev Web</Link>
|
||||
<Link to={`/${lang}/blog/category/ai`} className="block py-2 text-base font-bold text-gray-900">{t.blog.categories.ai}</Link>
|
||||
<Link to={`/${lang}/blog/category/tools`} className="block py-2 text-base font-bold text-gray-900">Tópicos</Link>
|
||||
<Link to={`/${lang}/blog`} className="block py-2 text-base font-bold text-gray-900">Tutoriais</Link>
|
||||
</div>
|
||||
<div className="px-4 py-4 bg-gray-50 border-t border-gray-200 flex gap-4">
|
||||
{languages.map((l) => (
|
||||
<button
|
||||
key={`mobile-${l.code}`}
|
||||
onClick={() => { changeLanguage(l.code); setIsMenuOpen(false); }}
|
||||
className={cn(
|
||||
"text-xs font-bold uppercase px-3 py-1.5 rounded-md border",
|
||||
lang === l.code ? "border-brand-primary bg-brand-primary text-white" : "border-gray-300 text-gray-600 bg-white"
|
||||
)}
|
||||
>
|
||||
{l.code}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Search Overlay */}
|
||||
<AnimatePresence>
|
||||
{isSearchOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={() => setIsSearchOpen(false)}
|
||||
className="fixed inset-0 bg-gray-900/40 z-[65] backdrop-blur-sm"
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
className="fixed top-0 left-0 w-full z-[70] bg-white border-b border-gray-200 shadow-xl"
|
||||
>
|
||||
<div className="container-inner py-6 flex items-center justify-between gap-4">
|
||||
<form onSubmit={handleSearch} className="flex-1 flex items-center gap-3">
|
||||
<Search size={24} className="text-gray-400 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
autoFocus
|
||||
placeholder="Pesquisar artigos..."
|
||||
className="w-full text-xl md:text-2xl font-bold font-heading text-gray-900 focus:outline-none placeholder:text-gray-300"
|
||||
/>
|
||||
</form>
|
||||
<button
|
||||
onClick={() => setIsSearchOpen(false)}
|
||||
className="p-2 text-gray-500 hover:text-gray-900 bg-gray-100 rounded-full transition-colors shrink-0"
|
||||
>
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<main className="flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<footer className="bg-gray-900 text-gray-400 mt-16 pb-8 pt-16">
|
||||
<div className="container-inner mb-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||
<div>
|
||||
<Link to={`/${lang}`} className="flex items-center gap-2 mb-6">
|
||||
<div className="w-8 h-8 bg-brand-primary text-white rounded flex items-center justify-center font-bold text-lg font-heading">C</div>
|
||||
<span className="font-bold text-xl text-white font-heading tracking-tight">CodeChronicle</span>
|
||||
</Link>
|
||||
<p className="text-sm leading-relaxed mb-6 font-medium">
|
||||
Sua dose diária de notícias de tecnologia, tutoriais práticos e insights de desenvolvimento. Explore o futuro hoje.
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<a href="#" className="w-9 h-9 rounded-full bg-gray-800 flex items-center justify-center hover:bg-brand-primary hover:text-white transition-colors text-white"><Twitter size={14} /></a>
|
||||
<a href="#" className="w-9 h-9 rounded-full bg-gray-800 flex items-center justify-center hover:bg-brand-primary hover:text-white transition-colors text-white"><Github size={14} /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-white font-bold text-base mb-6 font-heading border-l-4 border-brand-primary pl-3">Links Úteis</h4>
|
||||
<ul className="space-y-3 text-sm font-medium">
|
||||
<li><Link to={`/${lang}/blog`} className="hover:text-brand-primary transition-colors">Todos os Tutoriais</Link></li>
|
||||
<li><Link to={`/${lang}/blog/category/code`} className="hover:text-brand-primary transition-colors">Dev Web</Link></li>
|
||||
<li><Link to={`/${lang}/blog/category/ai`} className="hover:text-brand-primary transition-colors">Machine Learning</Link></li>
|
||||
<li><Link to={`/${lang}/contact`} className="hover:text-brand-primary transition-colors">Fale Conosco</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-white font-bold text-base mb-6 font-heading border-l-4 border-brand-primary pl-3">Inscreva-se</h4>
|
||||
<p className="text-sm mb-4">Receba os melhores artigos de tecnologia direto na caixa de entrada.</p>
|
||||
<form className="flex flex-col gap-3">
|
||||
<input type="email" placeholder="Seu email..." className="w-full bg-gray-800 border border-gray-700 text-white rounded px-4 py-2.5 text-sm focus:outline-none focus:border-brand-primary" />
|
||||
<button className="bg-brand-primary text-white font-bold text-sm px-4 py-2.5 rounded hover:bg-opacity-90 transition-all">Inscrever-se</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container-inner border-t border-gray-800 pt-8 flex flex-col md:flex-row justify-between items-center text-xs font-medium">
|
||||
<p>© 2026 CodeChronicle Labs. Todos os direitos reservados.</p>
|
||||
<div className="flex gap-4 mt-4 md:mt-0">
|
||||
<Link to={`/${lang}/privacy`} className="hover:text-white">Privacidade</Link>
|
||||
<Link to={`/${lang}/terms`} className="hover:text-white">Termos de Serviço</Link>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
46
Template-03/src/components/Newsletter.tsx
Normal file
46
Template-03/src/components/Newsletter.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Mail } from 'lucide-react';
|
||||
|
||||
export const Newsletter = () => {
|
||||
const { lang } = useI18n();
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-brand-primary text-white">
|
||||
<div className="container-inner">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-10">
|
||||
<div className="flex-1 text-center md:text-left">
|
||||
<h2 className="text-3xl font-bold font-heading mb-4">
|
||||
Assine nossa Newsletter
|
||||
</h2>
|
||||
<p className="text-white/80 max-w-lg mx-auto md:mx-0">
|
||||
Receba as últimas novidades, tutoriais de desenvolvimento web e análises de tecnologia diretamente na sua caixa de entrada.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md">
|
||||
<form
|
||||
className="flex flex-col sm:flex-row gap-3"
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Seu melhor e-mail"
|
||||
className="flex-1 bg-white border-none rounded px-4 py-3 text-gray-900 focus:outline-none focus:ring-2 focus:ring-white/50"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
className="bg-gray-900 text-white font-bold px-6 py-3 rounded hover:bg-gray-800 transition-colors flex items-center justify-center gap-2 whitespace-nowrap"
|
||||
>
|
||||
<Mail size={18} /> Assinar
|
||||
</button>
|
||||
</form>
|
||||
<p className="text-xs text-white/60 mt-3 text-center md:text-left">
|
||||
Não enviamos spam. Cancele sua assinatura quando quiser.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
110
Template-03/src/components/PostCard.tsx
Normal file
110
Template-03/src/components/PostCard.tsx
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import React from 'react';
|
||||
import { Post } from '../types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { formatDate } from '../lib/utils';
|
||||
import { Clock } from 'lucide-react';
|
||||
|
||||
interface PostCardProps {
|
||||
post: Post;
|
||||
variant?: 'grid' | 'list' | 'overlay';
|
||||
}
|
||||
|
||||
export const PostCard: React.FC<PostCardProps> = ({ post, variant = 'grid' }) => {
|
||||
const { lang } = useI18n();
|
||||
|
||||
if (variant === 'overlay') {
|
||||
return (
|
||||
<Link to={`/${lang}/blog/${post.slug}`} className="group relative block w-full h-full overflow-hidden rounded-md">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
referrerPolicy="no-referrer"
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-gray-900 via-gray-900/40 to-transparent opacity-80" />
|
||||
<div className="absolute bottom-0 left-0 w-full p-6">
|
||||
<span className="bg-brand-primary text-white text-[10px] font-bold uppercase tracking-wider px-2 py-1 rounded inline-block mb-3">
|
||||
{post.category}
|
||||
</span>
|
||||
<h3 className="text-white text-xl md:text-2xl font-bold font-heading line-clamp-2 leading-tight mb-3 group-hover:underline">
|
||||
{post.title}
|
||||
</h3>
|
||||
<div className="flex items-center text-gray-300 text-[11px] font-medium gap-3">
|
||||
<span>{post.author}</span>
|
||||
<span>-</span>
|
||||
<span>{formatDate(post.date, lang)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === 'list') {
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row gap-5 group items-start">
|
||||
<Link to={`/${lang}/blog/${post.slug}`} className="w-full sm:w-1/3 aspect-[4/3] relative overflow-hidden rounded-md shrink-0">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
referrerPolicy="no-referrer"
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute top-2 left-2">
|
||||
<span className="bg-brand-primary text-white text-[10px] font-bold uppercase tracking-wider px-2 py-1 rounded shadow-sm">
|
||||
{post.category}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex-grow flex flex-col justify-center">
|
||||
<h3 className="text-xl font-bold font-heading text-gray-900 line-clamp-2 leading-tight mb-3 group-hover:text-brand-primary transition-colors">
|
||||
<Link to={`/${lang}/blog/${post.slug}`}>{post.title}</Link>
|
||||
</h3>
|
||||
<div className="flex items-center text-gray-500 text-[12px] font-medium gap-3 mb-3">
|
||||
<span className="text-gray-800 font-bold">{post.author}</span>
|
||||
<span>-</span>
|
||||
<span>{formatDate(post.date, lang)}</span>
|
||||
</div>
|
||||
<p className="text-gray-600 text-[14px] line-clamp-2 leading-relaxed">
|
||||
{post.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col group h-full">
|
||||
<Link to={`/${lang}/blog/${post.slug}`} className="relative aspect-[16/10] overflow-hidden rounded-md mb-4 block">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
referrerPolicy="no-referrer"
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute top-3 left-3">
|
||||
<span className="bg-brand-primary text-white text-[10px] font-bold uppercase tracking-wider px-2 py-1 rounded shadow-sm">
|
||||
{post.category}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className="flex flex-col flex-grow">
|
||||
<h3 className="text-[18px] font-bold font-heading mb-3 text-gray-900 group-hover:text-brand-primary transition-colors leading-tight line-clamp-2">
|
||||
<Link to={`/${lang}/blog/${post.slug}`}>{post.title}</Link>
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center text-gray-500 text-[11px] font-medium gap-3 mb-3">
|
||||
<span className="text-gray-800 font-bold">{post.author}</span>
|
||||
<span>-</span>
|
||||
<span>{formatDate(post.date, lang)}</span>
|
||||
<span className="flex items-center gap-1"><Clock size={12}/> {post.readingTime}</span>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 text-[14px] line-clamp-2 leading-relaxed mt-auto">
|
||||
{post.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
127
Template-03/src/components/SEO.tsx
Normal file
127
Template-03/src/components/SEO.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
interface SEOProps {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
article?: boolean;
|
||||
author?: string;
|
||||
datePublished?: string;
|
||||
category?: string;
|
||||
}
|
||||
|
||||
export const SEO: React.FC<SEOProps> = ({
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
article,
|
||||
author = 'Vanta Architecture Team',
|
||||
datePublished,
|
||||
category
|
||||
}) => {
|
||||
const siteName = 'CodeChronicle';
|
||||
const fullTitle = `${title} | ${siteName}`;
|
||||
const location = useLocation();
|
||||
const canonicalUrl = `https://codechronicle.com${location.pathname}`;
|
||||
const defaultImage = 'https://images.unsplash.com/photo-1620121692029-d088224efc74?q=80&w=2000';
|
||||
const ogImage = image || defaultImage;
|
||||
|
||||
const structuredData = article ? {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": title,
|
||||
"description": description,
|
||||
"image": ogImage,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": author
|
||||
},
|
||||
"datePublished": datePublished,
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": canonicalUrl
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": siteName,
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://codechronicle.com/logo.png"
|
||||
}
|
||||
}
|
||||
} : {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": siteName,
|
||||
"url": "https://codechronicle.com",
|
||||
"description": description
|
||||
};
|
||||
|
||||
const breadcrumbData = article ? {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": `https://codechronicle.com/${location.pathname.split('/')[1]}`
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "Blog",
|
||||
"item": `https://codechronicle.com/${location.pathname.split('/')[1]}/blog`
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 3,
|
||||
"name": title,
|
||||
"item": canonicalUrl
|
||||
}
|
||||
]
|
||||
} : null;
|
||||
|
||||
return (
|
||||
<Helmet
|
||||
htmlAttributes={{
|
||||
lang: location.pathname.split('/')[1] || 'pt'
|
||||
}}
|
||||
>
|
||||
{/* Basic Meta Tags */}
|
||||
<title>{fullTitle}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
|
||||
{/* Open Graph / Facebook */}
|
||||
<meta property="og:site_name" content={siteName} />
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
<meta property="og:title" content={fullTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:type" content={article ? 'article' : 'website'} />
|
||||
<meta property="og:image" content={ogImage} />
|
||||
{article && category && <meta property="article:section" content={category} />}
|
||||
{article && datePublished && <meta property="article:published_time" content={datePublished} />}
|
||||
|
||||
{/* Twitter */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:domain" content="codechronicle.com" />
|
||||
<meta name="twitter:url" content={canonicalUrl} />
|
||||
<meta name="twitter:title" content={fullTitle} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={ogImage} />
|
||||
|
||||
{/* Google Rich Results */}
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(structuredData)}
|
||||
</script>
|
||||
{breadcrumbData && (
|
||||
<script type="application/ld+json">
|
||||
{JSON.stringify(breadcrumbData)}
|
||||
</script>
|
||||
)}
|
||||
</Helmet>
|
||||
);
|
||||
};
|
||||
12
Template-03/src/components/ScrollToTop.tsx
Normal file
12
Template-03/src/components/ScrollToTop.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const ScrollToTop = () => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
|
||||
return null;
|
||||
};
|
||||
30
Template-03/src/components/TableOfContents.tsx
Normal file
30
Template-03/src/components/TableOfContents.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
interface TOCProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export const TableOfContents: React.FC<TOCProps> = ({ content }) => {
|
||||
const headings = content.split('\n').filter(line => line.startsWith('## ')).map(line => line.replace('## ', '').trim());
|
||||
|
||||
if (headings.length === 0) return null;
|
||||
|
||||
return (
|
||||
<nav className="flex flex-col">
|
||||
{headings.map((heading, index) => (
|
||||
<a
|
||||
key={heading}
|
||||
href={`#${heading.toLowerCase().replace(/ /g, '-')}`}
|
||||
className="group flex items-center justify-between py-3 border-b border-white/5 text-[11px] font-black uppercase italic tracking-widest text-tech-muted hover:text-tech-primary transition-all overflow-hidden"
|
||||
>
|
||||
<span className="flex items-center gap-3">
|
||||
<span className="text-[8px] text-white/20 group-hover:text-tech-primary transition-colors">0{index + 1}</span>
|
||||
{heading}
|
||||
</span>
|
||||
<ArrowRight size={12} className="opacity-0 group-hover:opacity-100 transition-all -translate-x-4 group-hover:translate-x-0" />
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
1500
Template-03/src/content/index.ts
Normal file
1500
Template-03/src/content/index.ts
Normal file
File diff suppressed because it is too large
Load diff
26
Template-03/src/hooks/useI18n.ts
Normal file
26
Template-03/src/hooks/useI18n.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { content } from '../content';
|
||||
import { Language } from '../types';
|
||||
|
||||
export function useI18n() {
|
||||
const { lang } = useParams<{ lang?: string }>();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const currentLang: Language = (lang as Language) || 'pt';
|
||||
const t = content.ui[currentLang];
|
||||
const posts = content.posts[currentLang];
|
||||
|
||||
const changeLanguage = (newLang: Language) => {
|
||||
const pathParts = location.pathname.split('/');
|
||||
pathParts[1] = newLang; // Replace the language segment
|
||||
navigate(pathParts.join('/'));
|
||||
};
|
||||
|
||||
return {
|
||||
lang: currentLang,
|
||||
t,
|
||||
posts,
|
||||
changeLanguage,
|
||||
};
|
||||
}
|
||||
71
Template-03/src/index.css
Normal file
71
Template-03/src/index.css
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700;900&family=Montserrat:wght@400;600;700;800;900&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-sans: "Roboto", system-ui, sans-serif;
|
||||
--font-heading: "Montserrat", sans-serif;
|
||||
|
||||
--color-brand-primary: #0088cc;
|
||||
--color-brand-secondary: #005580;
|
||||
--color-text-main: #333333;
|
||||
--color-text-muted: #777777;
|
||||
--color-bg-light: #f5f5f5;
|
||||
--color-border: #eeeeee;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-bg-light text-text-main antialiased selection:bg-brand-primary selection:text-white;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-heading tracking-tight font-bold text-gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.container-inner {
|
||||
@apply max-w-[1200px] mx-auto px-4;
|
||||
}
|
||||
|
||||
/* Article Content Styles */
|
||||
.prose-custom {
|
||||
@apply text-text-main leading-[1.8] text-[17px] font-sans;
|
||||
}
|
||||
|
||||
.prose-custom h2 {
|
||||
@apply text-[28px] font-bold mt-10 mb-5 relative pb-3 border-b border-gray-200;
|
||||
}
|
||||
|
||||
.prose-custom h3 {
|
||||
@apply text-[22px] font-bold mt-8 mb-4;
|
||||
}
|
||||
|
||||
.prose-custom p {
|
||||
@apply mb-6;
|
||||
}
|
||||
|
||||
.prose-custom a {
|
||||
@apply text-brand-primary font-medium hover:underline;
|
||||
}
|
||||
|
||||
.prose-custom ul {
|
||||
@apply list-disc list-inside mb-6 space-y-2;
|
||||
}
|
||||
|
||||
.prose-custom blockquote {
|
||||
@apply border-l-4 border-brand-primary pl-5 py-2 my-8 italic text-xl font-medium text-gray-700 bg-gray-50;
|
||||
}
|
||||
|
||||
.prose-custom img {
|
||||
@apply my-8 rounded-md shadow-sm;
|
||||
}
|
||||
|
||||
.prose-custom pre {
|
||||
@apply bg-gray-900 text-gray-100 p-5 rounded-md my-8 overflow-x-auto text-sm font-mono leading-relaxed;
|
||||
}
|
||||
|
||||
.prose-custom code {
|
||||
@apply bg-gray-100 text-brand-primary px-1.5 py-0.5 rounded text-[0.9em];
|
||||
}
|
||||
}
|
||||
14
Template-03/src/lib/utils.ts
Normal file
14
Template-03/src/lib/utils.ts
Normal 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(dateString: string, lang: string) {
|
||||
return new Date(dateString).toLocaleDateString(lang, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
10
Template-03/src/main.tsx
Normal file
10
Template-03/src/main.tsx
Normal 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>,
|
||||
);
|
||||
37
Template-03/src/pages/About.tsx
Normal file
37
Template-03/src/pages/About.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
|
||||
export default function About() {
|
||||
const { lang } = useI18n();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title="Sobre Nós" description="Conheça a história e a equipe por trás do CodeChronicle." />
|
||||
<div className="bg-gray-50 min-h-screen py-20">
|
||||
<div className="container-inner max-w-3xl mx-auto">
|
||||
<div className="bg-white p-8 md:p-12 border border-gray-200 rounded-md">
|
||||
<h1 className="text-3xl md:text-4xl font-bold font-heading text-gray-900 mb-8">Sobre Nós</h1>
|
||||
<div className="prose-custom">
|
||||
<p className="text-lg leading-relaxed text-gray-700 mb-6">
|
||||
Bem-vindo ao <strong>CodeChronicle</strong>, sua fonte diária das melhores notícias de tecnologia,
|
||||
tutoriais de desenvolvimento web e análises de software.
|
||||
</p>
|
||||
<h2 className="text-xl font-bold font-heading text-gray-900 mt-10 mb-4">Nossa Missão</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
Nossa missão é democratizar o conhecimento em programação e ajudar desenvolvedores de
|
||||
todos os níveis a aprimorarem suas habilidades por meio de tutoriais práticos, artigos de
|
||||
código aberto e guias aprofundados.
|
||||
</p>
|
||||
<h2 className="text-xl font-bold font-heading text-gray-900 mt-10 mb-4">Por que criamos o site?</h2>
|
||||
<p className="text-gray-700 mb-6">
|
||||
Com as rápidas mudanças no cenário tecnológico moderno, nós entendemos que a comunidade
|
||||
precisava de um espaço seguro, rápido e sem complicações para se manter atualizada e discutir as novidades em ferramentas e arquiteturas.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
156
Template-03/src/pages/BlogList.tsx
Normal file
156
Template-03/src/pages/BlogList.tsx
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import React from 'react';
|
||||
import { useParams, Link, useSearchParams } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { cn } from '../lib/utils';
|
||||
import { Facebook, Twitter, Instagram, Youtube } from 'lucide-react';
|
||||
|
||||
const SocialWidget = () => (
|
||||
<div className="bg-white p-6 border border-gray-200 rounded-md">
|
||||
<h4 className="text-sm font-bold font-heading uppercase tracking-wider mb-5 pb-3 border-b border-gray-200">Fique Conectado</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-blue-50 text-blue-600 rounded">
|
||||
<Facebook size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">128k</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Fãs</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-sky-50 text-sky-500 rounded">
|
||||
<Twitter size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">85k</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Seguidores</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-pink-50 text-pink-600 rounded">
|
||||
<Instagram size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">450k</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Seguidores</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-red-50 text-red-600 rounded">
|
||||
<Youtube size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">1.2M</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Inscritos</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function BlogList() {
|
||||
const { category } = useParams<{ category?: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { lang, posts, t } = useI18n();
|
||||
const currentLang = lang || 'pt';
|
||||
const query = searchParams.get('q');
|
||||
|
||||
const validPosts = (posts || []).filter(p => !['privacy-policy', 'terms-of-service'].includes(p.id));
|
||||
const categories = ['all', 'ai', 'code', 'startups', 'tools'];
|
||||
|
||||
let filteredPosts = category && category !== 'all'
|
||||
? validPosts.filter(p => p.category.toLowerCase() === category.toLowerCase())
|
||||
: validPosts;
|
||||
|
||||
if (query) {
|
||||
const q = query.toLowerCase();
|
||||
filteredPosts = filteredPosts.filter(p =>
|
||||
p.title.toLowerCase().includes(q) ||
|
||||
(p.description && p.description.toLowerCase().includes(q)) ||
|
||||
p.content.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
|
||||
const activeCategory = category || 'all';
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO
|
||||
title={category ? `${category.toUpperCase()} Novidades` : "Últimos Tutoriais"}
|
||||
description="Explore nossos artigos sobre tecnologia, programação e IA."
|
||||
/>
|
||||
|
||||
<div className="bg-gray-50 min-h-screen pb-24 pt-10">
|
||||
<div className="container-inner">
|
||||
<div className="flex flex-wrap gap-4 mb-10 border-b border-gray-200 pb-4">
|
||||
{categories.map((cat) => (
|
||||
<Link
|
||||
key={cat}
|
||||
to={cat === 'all' ? `/${currentLang}/blog` : `/${currentLang}/blog/category/${cat}`}
|
||||
className={cn(
|
||||
"px-4 py-2 font-bold uppercase tracking-wider text-xs rounded transition-colors",
|
||||
activeCategory === cat
|
||||
? "bg-brand-primary text-white"
|
||||
: "bg-white border border-gray-200 text-gray-600 hover:text-brand-primary hover:border-brand-primary"
|
||||
)}
|
||||
>
|
||||
{t.blog.categories[cat as keyof typeof t.blog.categories] || cat}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10">
|
||||
{/* Main Content */}
|
||||
<main className="lg:col-span-8">
|
||||
<div className="flex items-center justify-between border-b-2 border-brand-primary pb-2 mb-8">
|
||||
<h2 className="text-[20px] font-bold font-heading uppercase text-gray-900 bg-brand-primary text-white px-4 py-1.5 -mb-[10px]">
|
||||
{query ? `Busca: "${query}"` : category ? `Artigos de ${category}` : "Todos os Artigos"}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
{filteredPosts.map((post) => (
|
||||
<div key={post.id} className="pb-8 border-b border-gray-100 last:border-0 last:pb-0">
|
||||
<PostCard post={post} variant="list" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredPosts.length === 0 && (
|
||||
<div className="text-center py-20 text-gray-500 font-bold bg-white border border-gray-200 rounded-md mt-10">
|
||||
{query ? `Nenhum artigo encontrado para "${query}".` : "Nenhum artigo encontrado nesta categoria."}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredPosts.length > 0 && (
|
||||
<div className="mt-12 flex justify-center">
|
||||
<button className="bg-white border border-gray-200 text-gray-900 font-bold font-heading text-sm px-8 py-3 rounded hover:bg-brand-primary hover:text-white hover:border-brand-primary transition-colors">
|
||||
Carregar Mais Artigos
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="lg:col-span-4 space-y-10">
|
||||
{/* Social Widget */}
|
||||
<SocialWidget />
|
||||
|
||||
{/* Categories Widget */}
|
||||
<div className="bg-white p-6 border border-gray-200 rounded-md">
|
||||
<h4 className="text-[16px] font-bold font-heading uppercase border-b border-gray-200 pb-3 mb-4">
|
||||
Categorias
|
||||
</h4>
|
||||
<ul className="flex flex-col">
|
||||
<li className="border-b border-gray-100">
|
||||
<Link to={`/${currentLang}/blog/category/code`} className="flex justify-between items-center py-2.5 text-[14px] text-gray-700 hover:text-brand-primary font-medium">
|
||||
Dev Web <span className="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded">12</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="border-b border-gray-100">
|
||||
<Link to={`/${currentLang}/blog/category/ai`} className="flex justify-between items-center py-2.5 text-[14px] text-gray-700 hover:text-brand-primary font-medium">
|
||||
Machine Learning <span className="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded">8</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="border-b border-gray-100">
|
||||
<Link to={`/${currentLang}/blog/category/tools`} className="flex justify-between items-center py-2.5 text-[14px] text-gray-700 hover:text-brand-primary font-medium">
|
||||
DevOps & Cloud <span className="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded">5</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
94
Template-03/src/pages/Contact.tsx
Normal file
94
Template-03/src/pages/Contact.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { Send, MapPin, Mail, Phone } from 'lucide-react';
|
||||
|
||||
export default function Contact() {
|
||||
const { lang, t } = useI18n();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title="Contato" description="Entre em contato conosco." />
|
||||
<div className="bg-gray-50 min-h-screen py-20">
|
||||
<div className="container-inner max-w-5xl mx-auto">
|
||||
<div className="bg-white p-8 md:p-12 border border-gray-200 rounded-md">
|
||||
<h1 className="text-3xl md:text-4xl font-bold font-heading text-gray-900 mb-8">Entre em Contato</h1>
|
||||
<p className="text-gray-700 max-w-2xl text-lg mb-12">
|
||||
Você tem alguma dúvida, sugestão ou proposta de parceria? Ficaremos felizes em ouvir você. Preencha o formulário abaixo e entraremos em contato o mais rápido possível.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16">
|
||||
<div>
|
||||
<form className="space-y-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Nome</label>
|
||||
<input type="text" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="Seu nome" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">E-mail</label>
|
||||
<input type="email" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="seu@email.com" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Assunto</label>
|
||||
<input type="text" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="Como podemos ajudar?" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Mensagem</label>
|
||||
<textarea rows={5} className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="Escreva sua mensagem..."></textarea>
|
||||
</div>
|
||||
<button type="submit" className="bg-brand-primary text-white font-bold px-8 py-3 rounded hover:bg-opacity-90 transition-opacity flex items-center gap-2">
|
||||
Enviar Mensagem <Send size={16} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="space-y-10">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold font-heading text-gray-900 mb-6">Informações de Contato</h3>
|
||||
<ul className="space-y-6">
|
||||
<li className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 bg-gray-50 text-brand-primary rounded flex items-center justify-center shrink-0">
|
||||
<Mail size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-sm font-bold text-gray-900 mb-1">E-mail</span>
|
||||
<a href="mailto:hello@codechronicle.com" className="text-gray-600 hover:text-brand-primary transition-colors">hello@codechronicle.com</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 bg-gray-50 text-brand-primary rounded flex items-center justify-center shrink-0">
|
||||
<Phone size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-sm font-bold text-gray-900 mb-1">Telefone</span>
|
||||
<span className="text-gray-600">(11) 98765-4321</span>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 bg-gray-50 text-brand-primary rounded flex items-center justify-center shrink-0">
|
||||
<MapPin size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-sm font-bold text-gray-900 mb-1">Localização</span>
|
||||
<span className="text-gray-600">São Paulo, SP - Brasil<br />Trabalhamos remotamente</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-6 rounded border border-gray-200">
|
||||
<h4 className="font-bold text-gray-900 mb-2">Suporte Técnico</h4>
|
||||
<p className="text-sm text-gray-600 leading-relaxed mb-4">
|
||||
Para questões não relacionadas a negócios, por favor acesse nosso fórum comunitário ou consulte nossas seções de perguntas frequentes nos artigos.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
172
Template-03/src/pages/Home.tsx
Normal file
172
Template-03/src/pages/Home.tsx
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
import React from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import {
|
||||
ChevronRight,
|
||||
TrendingUp,
|
||||
Facebook,
|
||||
Twitter,
|
||||
Instagram,
|
||||
Youtube
|
||||
} from 'lucide-react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { Language } from '../types';
|
||||
import { PostCard } from '../components/PostCard';
|
||||
import { SEO } from '../components/SEO';
|
||||
|
||||
const SocialWidget = () => (
|
||||
<div className="bg-white p-6 border border-gray-200 rounded-md">
|
||||
<h4 className="text-sm font-bold font-heading uppercase tracking-wider mb-5 pb-3 border-b border-gray-200">Fique Conectado</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-blue-50 text-blue-600 rounded">
|
||||
<Facebook size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">128k</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Fãs</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-sky-50 text-sky-500 rounded">
|
||||
<Twitter size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">85k</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Seguidores</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-pink-50 text-pink-600 rounded">
|
||||
<Instagram size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">450k</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Seguidores</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center p-4 bg-red-50 text-red-600 rounded">
|
||||
<Youtube size={24} className="mb-2" />
|
||||
<span className="text-xs font-bold">1.2M</span>
|
||||
<span className="text-[10px] text-gray-500 uppercase">Inscritos</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function Home() {
|
||||
const { t, posts, lang } = useI18n();
|
||||
const currentLang = (lang as Language) || 'pt';
|
||||
|
||||
const validPosts = (posts || []).filter(p => !['privacy-policy', 'terms-of-service'].includes(p.id));
|
||||
|
||||
if (validPosts.length === 0) return null;
|
||||
|
||||
const heroLarge = validPosts[0];
|
||||
const heroSmall = validPosts.slice(1, 3);
|
||||
|
||||
const latestPosts = validPosts.slice(3, 9);
|
||||
const trendingPosts = validPosts.slice(0, 4);
|
||||
|
||||
return (
|
||||
<div className="bg-white pb-20">
|
||||
<SEO title={t.home.hero.title} description={t.home.hero.subtitle} />
|
||||
|
||||
|
||||
|
||||
{/* Hero Grid Section */}
|
||||
<section className="container-inner pt-4 pb-12">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4">
|
||||
{/* Main Left Feature */}
|
||||
<div className="lg:col-span-8">
|
||||
<div className="w-full h-[300px] md:h-[450px]">
|
||||
<PostCard post={heroLarge} variant="overlay" />
|
||||
</div>
|
||||
</div>
|
||||
{/* Right Stack */}
|
||||
<div className="lg:col-span-4 flex flex-col gap-4">
|
||||
{heroSmall.map(post => (
|
||||
<div key={post.id} className="h-[200px] md:h-[217px]">
|
||||
<PostCard post={post} variant="overlay" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<section className="container-inner mt-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10">
|
||||
|
||||
{/* Main Column */}
|
||||
<main className="lg:col-span-8">
|
||||
<div className="flex items-center justify-between border-b-2 border-brand-primary pb-2 mb-8">
|
||||
<h2 className="text-[20px] font-bold font-heading uppercase text-gray-900 bg-brand-primary text-white px-4 py-1.5 -mb-[10px]">
|
||||
Últimas Notícias
|
||||
</h2>
|
||||
<Link to={`/${currentLang}/blog`} className="text-[12px] font-bold text-gray-500 hover:text-brand-primary transition-colors flex items-center">
|
||||
Ver Tudo <ChevronRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
{latestPosts.map(post => (
|
||||
<div key={post.id} className="pb-8 border-b border-gray-100 last:border-0 last:pb-0">
|
||||
<PostCard post={post} variant="list" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-12 flex justify-center">
|
||||
<Link to={`/${currentLang}/blog`} className="bg-brand-primary text-white font-bold text-sm px-8 py-3 rounded hover:bg-opacity-90 transition-colors">
|
||||
Carregar Mais Artigos
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="lg:col-span-4 space-y-10">
|
||||
|
||||
{/* Social Widget */}
|
||||
<SocialWidget />
|
||||
|
||||
{/* Trending Widget */}
|
||||
<div className="bg-white">
|
||||
<h4 className="text-[16px] font-bold font-heading uppercase border-b-2 border-gray-900 pb-2 mb-6">
|
||||
<span className="bg-gray-900 text-white px-3 py-1">Em Alta</span>
|
||||
</h4>
|
||||
<div className="flex flex-col gap-6">
|
||||
{trendingPosts.map((post, idx) => (
|
||||
<div key={post.id} className="flex gap-4 group">
|
||||
<span className="text-4xl font-bold font-heading text-gray-200 mt-1">{idx + 1}</span>
|
||||
<div>
|
||||
<h5 className="font-bold text-gray-900 text-[15px] leading-tight mb-2 group-hover:text-brand-primary transition-colors">
|
||||
<Link to={`/${currentLang}/blog/${post.slug}`}>{post.title}</Link>
|
||||
</h5>
|
||||
<span className="text-[11px] text-gray-500 font-medium">{post.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Categories Widget */}
|
||||
<div className="bg-white">
|
||||
<h4 className="text-[16px] font-bold font-heading uppercase border-b-2 border-gray-900 pb-2 mb-6">
|
||||
Categorias
|
||||
</h4>
|
||||
<ul className="flex flex-col">
|
||||
<li className="border-b border-gray-100">
|
||||
<Link to={`/${currentLang}/blog/category/code`} className="flex justify-between items-center py-3 text-[14px] text-gray-700 hover:text-brand-primary font-medium">
|
||||
Dev Web <span className="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded">12</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="border-b border-gray-100">
|
||||
<Link to={`/${currentLang}/blog/category/ai`} className="flex justify-between items-center py-3 text-[14px] text-gray-700 hover:text-brand-primary font-medium">
|
||||
Machine Learning <span className="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded">8</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="border-b border-gray-100">
|
||||
<Link to={`/${currentLang}/blog/category/tools`} className="flex justify-between items-center py-3 text-[14px] text-gray-700 hover:text-brand-primary font-medium">
|
||||
DevOps & Cloud <span className="bg-gray-100 text-gray-500 text-[10px] px-2 py-0.5 rounded">5</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
53
Template-03/src/pages/Legal.tsx
Normal file
53
Template-03/src/pages/Legal.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { Shield, FileText, Scale } from 'lucide-react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export default function Legal() {
|
||||
const { t, lang } = useI18n();
|
||||
const location = useLocation();
|
||||
|
||||
const isPrivacy = location.pathname.includes('privacy');
|
||||
const isTerms = location.pathname.includes('terms');
|
||||
const isEthics = location.pathname.includes('ethics');
|
||||
|
||||
let config = t.legal.privacy;
|
||||
let Icon = Shield;
|
||||
|
||||
if (isTerms) {
|
||||
config = t.legal.terms;
|
||||
Icon = FileText;
|
||||
} else if (isEthics) {
|
||||
config = t.legal.ethics;
|
||||
Icon = Scale;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title={config.title} description={config.content} />
|
||||
<div className="bg-gray-50 min-h-screen py-20">
|
||||
<div className="container-inner max-w-4xl mx-auto">
|
||||
<div className="bg-white p-8 md:p-12 border border-gray-200 rounded-md">
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="w-12 h-12 bg-gray-50 text-brand-primary rounded flex items-center justify-center shrink-0">
|
||||
<Icon size={24} />
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-4xl font-bold font-heading text-gray-900">{config.title}</h1>
|
||||
</div>
|
||||
|
||||
<article className="prose-custom max-w-none">
|
||||
<div className="whitespace-pre-line text-gray-700 leading-relaxed text-lg">
|
||||
{config.content}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div className="mt-12 pt-8 border-t border-gray-100 text-sm text-gray-500 font-medium">
|
||||
Última atualização: {new Date().toLocaleDateString('pt-BR')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
44
Template-03/src/pages/Login.tsx
Normal file
44
Template-03/src/pages/Login.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
|
||||
export default function Login() {
|
||||
const { lang } = useI18n();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title="Entrar" description="Faça o login em sua conta." />
|
||||
<div className="bg-gray-50 min-h-[calc(100vh-200px)] py-20 flex items-center justify-center">
|
||||
<div className="container-inner w-full max-w-md mx-auto">
|
||||
<div className="bg-white p-8 md:p-10 border border-gray-200 rounded-md text-center shadow-sm">
|
||||
<h1 className="text-2xl font-bold font-heading text-gray-900 mb-2">Bem-vindo de volta!</h1>
|
||||
<p className="text-sm text-gray-500 mb-8">Faça login com a sua conta para continuar.</p>
|
||||
|
||||
<form className="space-y-5 text-left">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1.5">Endereço de E-mail</label>
|
||||
<input type="email" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="seu@email.com" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-1.5">
|
||||
<label className="block text-sm font-medium text-gray-700">Senha</label>
|
||||
<a href="#" className="text-xs text-brand-primary hover:underline">Esqueceu a senha?</a>
|
||||
</div>
|
||||
<input type="password" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="Sua senha secreta" />
|
||||
</div>
|
||||
<button type="submit" className="w-full bg-brand-primary text-white font-bold py-3.5 rounded mt-4 hover:bg-opacity-90 transition-opacity">
|
||||
Entrar
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-100 text-sm">
|
||||
<span className="text-gray-500">Não tem uma conta ainda? </span>
|
||||
<a href="#" className="font-bold text-brand-primary hover:underline">Criar agora</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
213
Template-03/src/pages/PostDetail.tsx
Normal file
213
Template-03/src/pages/PostDetail.tsx
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import Markdown from 'react-markdown';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import {
|
||||
Calendar,
|
||||
Clock,
|
||||
User,
|
||||
Share2,
|
||||
Facebook,
|
||||
Twitter,
|
||||
Linkedin,
|
||||
Folder,
|
||||
MessageSquare,
|
||||
Instagram
|
||||
} from 'lucide-react';
|
||||
import { useI18n } from '../hooks/useI18n';
|
||||
import { formatDate } from '../lib/utils';
|
||||
import { TableOfContents } from '../components/TableOfContents';
|
||||
import { Language } from '../types';
|
||||
|
||||
export default function PostDetail() {
|
||||
const { lang, slug } = useParams<{ lang: string; slug: string }>();
|
||||
const currentLang = (lang as Language) || 'pt';
|
||||
const { posts } = useI18n();
|
||||
|
||||
const post = useMemo(() =>
|
||||
posts.find(p => p.slug === slug),
|
||||
[posts, slug]
|
||||
);
|
||||
|
||||
if (!post) return (
|
||||
<div className="min-h-screen pt-40 px-6 text-center bg-gray-50">
|
||||
<h1 className="text-4xl font-bold font-heading text-gray-900 mb-8">404: Page Not Found</h1>
|
||||
<Link to={`/${currentLang}/blog`} className="bg-brand-primary text-white font-bold px-8 py-3 rounded">Return to Home</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
const relatedPosts = posts
|
||||
.filter(p => p.id !== post.id)
|
||||
.slice(0, 3);
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 min-h-screen pb-20">
|
||||
<Helmet>
|
||||
<title>{post.title} | CodeChronicle</title>
|
||||
<meta name="description" content={post.description} />
|
||||
</Helmet>
|
||||
|
||||
{/* Main Top Header */}
|
||||
<div className="bg-white border-b border-gray-200 pt-16 pb-12 mb-10">
|
||||
<div className="container-inner">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<div className="flex justify-center items-center gap-2 mb-6">
|
||||
<span className="bg-brand-primary text-white text-[10px] font-bold uppercase tracking-wider px-2 py-1 rounded">
|
||||
{post.category}
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-5xl font-bold font-heading text-gray-900 leading-tight mb-8">
|
||||
{post.title}
|
||||
</h1>
|
||||
<div className="flex items-center justify-center gap-6 text-sm text-gray-500 font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<img src="https://ui-avatars.com/api/?name=Admin&background=0088cc&color=fff" className="w-6 h-6 rounded-full" alt={post.author} />
|
||||
<span className="text-gray-900 font-bold">{post.author}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 hidden sm:flex">
|
||||
<Calendar size={14} />
|
||||
{formatDate(post.date, currentLang)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 hidden sm:flex">
|
||||
<Folder size={14} />
|
||||
Tutoriais
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<MessageSquare size={14} />
|
||||
0 Comentários
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-inner">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10">
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="lg:col-span-8 bg-white p-6 md:p-10 border border-gray-200 rounded-md">
|
||||
|
||||
<div className="mb-10 rounded-md overflow-hidden shadow-sm">
|
||||
<img src={post.image} alt={post.title} referrerPolicy="no-referrer" className="w-full h-auto" />
|
||||
</div>
|
||||
|
||||
<div className="prose-custom max-w-none">
|
||||
<Markdown
|
||||
components={{
|
||||
img: ({node, ...props}) => <img referrerPolicy="no-referrer" {...props} />
|
||||
}}
|
||||
>
|
||||
{post.content}
|
||||
</Markdown>
|
||||
</div>
|
||||
|
||||
{/* Tags and Share */}
|
||||
<div className="mt-12 pt-8 border-t border-gray-100 flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-bold text-gray-900 mr-2">Tags:</span>
|
||||
<span className="bg-gray-100 text-gray-600 px-3 py-1 text-xs font-medium rounded hover:bg-brand-primary hover:text-white transition-colors cursor-pointer">Tutorial</span>
|
||||
<span className="bg-gray-100 text-gray-600 px-3 py-1 text-xs font-medium rounded hover:bg-brand-primary hover:text-white transition-colors cursor-pointer">Design</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 border-l pl-6 border-gray-200">
|
||||
<span className="text-sm font-bold text-gray-900">Compartilhar:</span>
|
||||
<button className="w-8 h-8 rounded-full bg-blue-600 text-white flex items-center justify-center hover:opacity-80 transition-opacity"><Facebook size={14} /></button>
|
||||
<button className="w-8 h-8 rounded-full bg-sky-500 text-white flex items-center justify-center hover:opacity-80 transition-opacity"><Twitter size={14} /></button>
|
||||
<button className="w-8 h-8 rounded-full bg-blue-800 text-white flex items-center justify-center hover:opacity-80 transition-opacity"><Linkedin size={14} /></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Author Box */}
|
||||
<div className="mt-10 bg-gray-50 border border-gray-200 p-8 flex flex-col md:flex-row items-center md:items-start gap-6 rounded-md">
|
||||
<img src="https://ui-avatars.com/api/?name=Admin&background=0088cc&color=fff&size=100" className="w-20 h-20 rounded-full shadow-sm" alt={post.author} />
|
||||
<div className="text-center md:text-left">
|
||||
<h4 className="text-xl font-bold font-heading text-gray-900 mb-2">{post.author}</h4>
|
||||
<p className="text-gray-600 text-sm leading-relaxed mb-4">
|
||||
Escritor Técnico Principal cobrindo arquitetura frontend moderna, padrões de design UI/UX e frameworks fullstack. Apaixonado por criar aplicações limpas e escaláveis.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Prev/Next Post Link Mockup */}
|
||||
<div className="mt-10 grid grid-cols-2 border-t border-b border-gray-100 divide-x divide-gray-100">
|
||||
<div className="p-6 text-left hover:bg-gray-50 transition-colors cursor-pointer">
|
||||
<span className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Artigo Anterior</span>
|
||||
<h4 className="text-base font-bold font-heading text-gray-900 group-hover:text-brand-primary line-clamp-2">Como escolher a hospedagem certa para o seu blog</h4>
|
||||
</div>
|
||||
<div className="p-6 text-right hover:bg-gray-50 transition-colors cursor-pointer">
|
||||
<span className="block text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Próximo Artigo</span>
|
||||
<h4 className="text-base font-bold font-heading text-gray-900 group-hover:text-brand-primary line-clamp-2">Criando um Servidor Local</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comments Mockup */}
|
||||
<div className="mt-16">
|
||||
<h3 className="text-2xl font-bold font-heading mb-8">Deixe uma Resposta</h3>
|
||||
<p className="text-sm text-gray-500 mb-6">Seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *</p>
|
||||
<form className="space-y-4">
|
||||
<textarea placeholder="Comentário" rows={6} className="w-full bg-gray-50 border border-gray-200 rounded p-4 text-sm focus:outline-none focus:border-brand-primary focus:bg-white"></textarea>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<input type="text" placeholder="Nome *" className="w-full bg-gray-50 border border-gray-200 rounded p-4 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" />
|
||||
<input type="email" placeholder="Email *" className="w-full bg-gray-50 border border-gray-200 rounded p-4 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" />
|
||||
</div>
|
||||
<button type="button" className="bg-brand-primary text-white font-bold px-8 py-3 rounded">Publicar Comentário</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="lg:col-span-4 space-y-8 relative">
|
||||
|
||||
<div className="sticky top-24 space-y-8">
|
||||
{/* Table of Contents */}
|
||||
<div className="bg-white p-6 border border-gray-200 rounded-md">
|
||||
<TableOfContents content={post.content} />
|
||||
</div>
|
||||
|
||||
{/* Related/Trending Content */}
|
||||
<div className="bg-white p-6 border border-gray-200 rounded-md">
|
||||
<h4 className="text-sm font-bold font-heading uppercase tracking-wider mb-5 pb-3 border-b border-gray-200">Artigos Relacionados</h4>
|
||||
<div className="flex flex-col gap-5">
|
||||
{relatedPosts.map(p => (
|
||||
<Link key={p.id} to={`/${currentLang}/blog/${p.slug}`} className="flex gap-4 group">
|
||||
<img src={p.image} referrerPolicy="no-referrer" className="w-20 h-20 object-cover rounded shadow-sm" alt={p.title} />
|
||||
<div className="flex-1">
|
||||
<h5 className="text-[14px] font-bold font-heading text-gray-900 leading-tight mb-2 group-hover:text-brand-primary line-clamp-2">
|
||||
{p.title}
|
||||
</h5>
|
||||
<span className="text-[11px] text-gray-500 font-medium">
|
||||
{formatDate(p.date, currentLang)}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stay Connected */}
|
||||
<div className="bg-white p-6 border border-gray-200 rounded-md">
|
||||
<h4 className="text-sm font-bold font-heading uppercase tracking-wider mb-5 pb-3 border-b border-gray-200">Fique Conectado</h4>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button className="flex items-center justify-between px-4 py-3 bg-blue-50 text-blue-600 rounded">
|
||||
<div className="flex items-center gap-3"><Facebook size={18} /> <span className="text-sm font-bold">Facebook</span></div>
|
||||
<span className="text-xs font-medium">128k Fãs</span>
|
||||
</button>
|
||||
<button className="flex items-center justify-between px-4 py-3 bg-sky-50 text-sky-500 rounded">
|
||||
<div className="flex items-center gap-3"><Twitter size={18} /> <span className="text-sm font-bold">Twitter</span></div>
|
||||
<span className="text-xs font-medium">85k Seguidores</span>
|
||||
</button>
|
||||
<button className="flex items-center justify-between px-4 py-3 bg-pink-50 text-pink-600 rounded">
|
||||
<div className="flex items-center gap-3"><Instagram size={18} /> <span className="text-sm font-bold">Instagram</span></div>
|
||||
<span className="text-xs font-medium">450k Seguidores</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
50
Template-03/src/pages/Write.tsx
Normal file
50
Template-03/src/pages/Write.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import React from 'react';
|
||||
import { SEO } from '../components/SEO';
|
||||
|
||||
export default function Write() {
|
||||
return (
|
||||
<>
|
||||
<SEO title="Escreva para Nós" description="Envie seus artigos de tecnologia e desenvolvimento web." />
|
||||
<div className="bg-gray-50 min-h-screen py-20">
|
||||
<div className="container-inner max-w-3xl mx-auto">
|
||||
<div className="bg-white p-8 md:p-12 border border-gray-200 rounded-md">
|
||||
<h1 className="text-3xl md:text-4xl font-bold font-heading text-gray-900 mb-8">Escreva para Nós</h1>
|
||||
<div className="prose-custom mb-10">
|
||||
<p className="text-gray-700">
|
||||
Você é apaixonado por tecnologia e gosta de compartilhar seus conhecimentos?
|
||||
Estamos sempre em busca de novos talentos interessados em produzir artigos sobre desenvolvimento web, ferramentas, IA e DevOps para a comunidade.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Nome Completo</label>
|
||||
<input type="text" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="Seu nome" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">E-mail</label>
|
||||
<input type="email" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="seu@email.com" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Tópico ou Título Proposto</label>
|
||||
<input type="text" className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="Sobre o que você gostaria de escrever?" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Mensagem ou Resumo</label>
|
||||
<textarea rows={5} className="w-full bg-gray-50 border border-gray-200 rounded p-3 text-sm focus:outline-none focus:border-brand-primary focus:bg-white" placeholder="Nos dê uma breve introdução de quem você é ou coloque o resumo de seu artigo..."></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="bg-brand-primary text-white font-bold px-8 py-3 rounded hover:bg-opacity-90 transition-opacity">
|
||||
Enviar Proposta
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
97
Template-03/src/types.ts
Normal file
97
Template-03/src/types.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
export type Language = 'pt' | 'en' | 'es';
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
category: string;
|
||||
author: string;
|
||||
date: string;
|
||||
readingTime: string;
|
||||
image: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
export interface Translations {
|
||||
nav: {
|
||||
home: string;
|
||||
blog: string;
|
||||
about: string;
|
||||
categories: string;
|
||||
};
|
||||
home: {
|
||||
hero: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
cta: string;
|
||||
};
|
||||
featured: string;
|
||||
recent: string;
|
||||
newsletter: {
|
||||
title: string;
|
||||
desc: string;
|
||||
placeholder: string;
|
||||
button: string;
|
||||
};
|
||||
};
|
||||
blog: {
|
||||
categories: {
|
||||
all: string;
|
||||
ai: string;
|
||||
code: string;
|
||||
startups: string;
|
||||
tools: string;
|
||||
};
|
||||
readMore: string;
|
||||
related: string;
|
||||
back: string;
|
||||
toc: string;
|
||||
};
|
||||
footer: {
|
||||
about: string;
|
||||
connect: string;
|
||||
rights: string;
|
||||
terms: string;
|
||||
ethics: string;
|
||||
newsletter: string;
|
||||
rss: string;
|
||||
contact: string;
|
||||
privacy: string;
|
||||
};
|
||||
legal: {
|
||||
privacy: { title: string; content: string; };
|
||||
terms: { title: string; content: string; };
|
||||
ethics: { title: string; content: string; };
|
||||
};
|
||||
contact: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
info: {
|
||||
title: string;
|
||||
email: string;
|
||||
location: string;
|
||||
};
|
||||
status: {
|
||||
title: string;
|
||||
nodes: string;
|
||||
uptime: string;
|
||||
latency: string;
|
||||
};
|
||||
form: {
|
||||
name: string;
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
submit: string;
|
||||
sending: string;
|
||||
success: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface SiteContent {
|
||||
posts: Record<Language, Post[]>;
|
||||
ui: Record<Language, Translations>;
|
||||
}
|
||||
26
Template-03/tsconfig.json
Normal file
26
Template-03/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
24
Template-03/vite.config.ts
Normal file
24
Template-03/vite.config.ts
Normal 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',
|
||||
},
|
||||
};
|
||||
});
|
||||
Loading…
Reference in a new issue