Import source: app/teknav-en
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,519 @@
|
|||||||
|
// teknav-en.jsx — English landing page (mirrors Persian homepage quality)
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { TeknavData } from './teknav-data.js';
|
||||||
|
import { useNav } from './teknav-ui.jsx';
|
||||||
|
import { LazyDiagram } from './src/components/lazy-diagram.jsx';
|
||||||
|
import { iconNode } from './src/lib/icon.jsx';
|
||||||
|
import pencilIcon from './src/assets/icons/pencil.svg';
|
||||||
|
|
||||||
|
// ── English copy maps ────────────────────────────────────────────────────────
|
||||||
|
const EN_TITLES = {
|
||||||
|
'tekrag-persian-data-centric-rag-benchmark': 'TekRAG-Persian: A Data-Centric Benchmark for Persian Technical RAG',
|
||||||
|
'mcp-model-context-protocol-2026': 'Model Context Protocol in 2026: Architecture, Tools, and Agentic AI',
|
||||||
|
'ai-data-centric-2026': 'Data-Centric AI in 2026: Why Clean Data Beats Bigger Models',
|
||||||
|
'cve-2026-41940-cpanel-zero-day-analysis': 'CVE-2026-41940: Technical Analysis of a cPanel Zero-Day',
|
||||||
|
'agentic-ai-production-2026': 'Agentic AI in Production: Reliability, Tools, and Governance',
|
||||||
|
'llm-security': 'LLM Security: Prompt Injection, RAG Attacks, and Defensive Design',
|
||||||
|
'data-governance-2026': 'Data Governance in 2026: Metadata, Lineage, and AI-Ready Data',
|
||||||
|
'open-source-ai-sovereignty': 'Open-Source AI and Digital Sovereignty',
|
||||||
|
'deepseek-v4-engram': 'DeepSeek-V4 and Efficient Reasoning Architectures',
|
||||||
|
'post-quantum-cryptography-qday': 'Post-Quantum Cryptography and the Road to Q-Day',
|
||||||
|
};
|
||||||
|
|
||||||
|
const EN_SUMMARIES = {
|
||||||
|
'tekrag-persian-data-centric-rag-benchmark': 'A research-grade benchmark for Persian technical question answering, hallucination evaluation, citation grounding, and data-centric RAG — comparing raw, cleaned, and metadata-enriched corpus variants.',
|
||||||
|
'mcp-model-context-protocol-2026': 'A technical deep-dive into the Model Context Protocol as the emerging standard connecting large language models to tools, resources, prompts, and autonomous agent workflows.',
|
||||||
|
'ai-data-centric-2026': 'A practical examination of why data quality, labeling precision, provenance tracking, and pipeline hygiene matter more as frontier models become increasingly capable.',
|
||||||
|
'cve-2026-41940-cpanel-zero-day-analysis': 'A security-focused breakdown of exploit surface, privilege escalation path, operational impact timeline, patching priorities, and defensive monitoring recommendations.',
|
||||||
|
'agentic-ai-production-2026': 'A field guide to moving AI agents from controlled demos into production systems with proper telemetry, permission scoping, evaluation loops, and failure recovery.',
|
||||||
|
'llm-security': 'Threat models and defensive controls for prompt injection, indirect instruction attacks, insecure tool use, retrieval poisoning, and multi-agent trust boundaries.',
|
||||||
|
'data-governance-2026': 'How metadata management, data lineage, policy enforcement, quality gates, and semantic layers shape dependable AI systems and analytics infrastructure.',
|
||||||
|
'open-source-ai-sovereignty': 'Why open model weights, transparent architectures, and local deployment matter for strategic control over AI infrastructure and long-term technology independence.',
|
||||||
|
'deepseek-v4-engram': 'A close look at memory architecture, mixture-of-experts efficiency, inference cost pressures, and what efficient reasoning architectures reveal about the frontier.',
|
||||||
|
'post-quantum-cryptography-qday': 'A clear explanation of quantum computing risk, NIST standardization, cryptographic migration strategy, and why organizations need post-quantum planning today.',
|
||||||
|
};
|
||||||
|
|
||||||
|
const EN_CATEGORIES = {
|
||||||
|
ai: { name: 'Artificial Intelligence', desc: 'LLMs, agents, model safety, and AI infrastructure' },
|
||||||
|
data: { name: 'Data Science', desc: 'RAG, data-centric AI, governance, and benchmarks' },
|
||||||
|
security: { name: 'Cybersecurity', desc: 'Vulnerabilities, LLM security, and defense' },
|
||||||
|
hardware: { name: 'Hardware', desc: 'AI chips, semiconductors, and compute strategy' },
|
||||||
|
software: { name: 'Software Engineering', desc: 'Architecture, systems design, and tooling' },
|
||||||
|
startup: { name: 'Startups & Markets', desc: 'Technology markets, product strategy, and venture' },
|
||||||
|
future: { name: 'Future of Technology', desc: 'Long-horizon trends and societal impact' },
|
||||||
|
};
|
||||||
|
|
||||||
|
function enTitle(article) {
|
||||||
|
return EN_TITLES[article.slug] || article.seoTitle || article.ogTitle || article.title;
|
||||||
|
}
|
||||||
|
function enSummary(article) {
|
||||||
|
return EN_SUMMARIES[article.slug] || article.ogDescription || article.metaDescription || article.summary || '';
|
||||||
|
}
|
||||||
|
function enCategory(article) {
|
||||||
|
return EN_CATEGORIES[article.category]?.name || article.categoryName || 'Technology';
|
||||||
|
}
|
||||||
|
function enAuthor(article) {
|
||||||
|
const a = TeknavData.authors.find((x) => x.id === article.authorId);
|
||||||
|
return a?.nameEn || (a?.slug === 'arsam-sabbagh' ? 'Arsam Sabbagh' : article.authorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function publishedArticles() {
|
||||||
|
return TeknavData.articles
|
||||||
|
.filter((a) => a.status !== 'پیشنویس' && a.status !== 'در انتظار بررسی')
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => String(b.dateEn || '').localeCompare(String(a.dateEn || '')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Animated neural background (same as Persian hero) ───────────────────────
|
||||||
|
function HeroBg() {
|
||||||
|
const ref = useRef();
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = ref.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
let raf, t = 0;
|
||||||
|
const palette = ['rgba(15,107,115,', 'rgba(196,106,77,', 'rgba(47,143,107,'];
|
||||||
|
const nodes = Array.from({ length: 28 }, (_, i) => ({
|
||||||
|
x: Math.random() * canvas.width, y: Math.random() * canvas.height,
|
||||||
|
vx: (Math.random() - 0.5) * 0.32, vy: (Math.random() - 0.5) * 0.32,
|
||||||
|
r: Math.random() * 3 + 1.5,
|
||||||
|
color: palette[i % palette.length],
|
||||||
|
phase: Math.random() * Math.PI * 2,
|
||||||
|
}));
|
||||||
|
const resize = () => { canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; };
|
||||||
|
resize();
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
const draw = () => {
|
||||||
|
t += 0.012;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
nodes.forEach((n) => {
|
||||||
|
n.x += n.vx; n.y += n.vy;
|
||||||
|
if (n.x < 0 || n.x > canvas.width) n.vx *= -1;
|
||||||
|
if (n.y < 0 || n.y > canvas.height) n.vy *= -1;
|
||||||
|
nodes.forEach((m) => {
|
||||||
|
const d = Math.hypot(n.x - m.x, n.y - m.y);
|
||||||
|
if (d < 120) {
|
||||||
|
ctx.beginPath(); ctx.moveTo(n.x, n.y); ctx.lineTo(m.x, m.y);
|
||||||
|
ctx.strokeStyle = `rgba(15,107,115,${0.15 * (1 - d / 120)})`;
|
||||||
|
ctx.lineWidth = 0.7; ctx.stroke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const pulse = 0.45 + 0.18 * Math.sin(t + n.phase);
|
||||||
|
ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = n.color + pulse + ')'; ctx.fill();
|
||||||
|
});
|
||||||
|
raf = requestAnimationFrame(draw);
|
||||||
|
};
|
||||||
|
draw();
|
||||||
|
return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', resize); };
|
||||||
|
}, []);
|
||||||
|
return <canvas ref={ref} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── CountUp animation ────────────────────────────────────────────────────────
|
||||||
|
function CountUp({ to, suffix = '', duration = 1400 }) {
|
||||||
|
const [n, setN] = useState(0);
|
||||||
|
const ref = useRef();
|
||||||
|
const started = useRef(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const obs = new IntersectionObserver(([e]) => {
|
||||||
|
if (e.isIntersecting && !started.current) {
|
||||||
|
started.current = true;
|
||||||
|
const start = performance.now();
|
||||||
|
const tick = (now) => {
|
||||||
|
const p = Math.min((now - start) / duration, 1);
|
||||||
|
setN(Math.round(to * (1 - Math.pow(1 - p, 3))));
|
||||||
|
if (p < 1) requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
}, { threshold: 0.4 });
|
||||||
|
if (ref.current) obs.observe(ref.current);
|
||||||
|
return () => obs.disconnect();
|
||||||
|
}, [to, duration]);
|
||||||
|
return <span ref={ref}>{n.toLocaleString()}{suffix}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Article card (English) ───────────────────────────────────────────────────
|
||||||
|
function EnArticleCard({ article, delay = 0, featured = false }) {
|
||||||
|
const { navigate } = useNav();
|
||||||
|
const [vis, setVis] = useState(false);
|
||||||
|
const ref = useRef();
|
||||||
|
useEffect(() => {
|
||||||
|
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) setVis(true); }, { threshold: 0.08 });
|
||||||
|
if (ref.current) obs.observe(ref.current);
|
||||||
|
return () => obs.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
ref={ref}
|
||||||
|
onClick={() => navigate('/article/' + article.slug)}
|
||||||
|
style={{
|
||||||
|
background: '#fff', borderRadius: featured ? 16 : 12,
|
||||||
|
border: featured ? '1px solid #0F6B7340' : '1px solid #E4DDD2',
|
||||||
|
padding: featured ? 0 : 22, cursor: 'pointer',
|
||||||
|
opacity: vis ? 1 : 0, transform: vis ? 'translateY(0)' : 'translateY(20px)',
|
||||||
|
transition: `opacity 0.45s ${delay}ms, transform 0.45s ${delay}ms, box-shadow 0.2s`,
|
||||||
|
boxShadow: featured ? '0 4px 28px rgba(15,107,115,0.10)' : '0 2px 10px rgba(0,0,0,0.04)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: featured ? 'grid' : 'flex',
|
||||||
|
gridTemplateColumns: featured ? '1fr 1fr' : undefined,
|
||||||
|
flexDirection: featured ? undefined : 'column',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.boxShadow = featured ? '0 10px 40px rgba(15,107,115,0.16)' : '0 6px 24px rgba(0,0,0,0.09)'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.boxShadow = featured ? '0 4px 28px rgba(15,107,115,0.10)' : '0 2px 10px rgba(0,0,0,0.04)'}
|
||||||
|
>
|
||||||
|
{featured ? (
|
||||||
|
<>
|
||||||
|
<div style={{ padding: '32px 32px 28px' }}>
|
||||||
|
<div style={{ display: 'flex', gap: 8, marginBottom: 14, flexWrap: 'wrap' }}>
|
||||||
|
<span style={{ background: '#0F6B7315', color: '#0F6B73', borderRadius: 6, padding: '3px 10px', fontSize: 11, fontWeight: 700 }}>Featured Research</span>
|
||||||
|
<span style={{ background: '#F4EFE6', color: '#A8512E', borderRadius: 6, padding: '3px 10px', fontSize: 11, fontWeight: 700 }}>{enCategory(article)}</span>
|
||||||
|
<span style={{ background: '#F4EFE6', color: '#5F6B6D', borderRadius: 6, padding: '3px 10px', fontSize: 11 }}>{article.readTime} min read</span>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: 'clamp(18px,2.4vw,26px)', fontWeight: 800, color: '#0F2A2E', margin: '0 0 14px', lineHeight: 1.4 }}>
|
||||||
|
{enTitle(article)}
|
||||||
|
</h2>
|
||||||
|
<p style={{ fontSize: 14, color: '#4A5A5C', lineHeight: 1.85, margin: '0 0 22px' }}>
|
||||||
|
{enSummary(article).slice(0, 240)}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingTop: 16, borderTop: '1px solid #E4DDD2' }}>
|
||||||
|
<div style={{ width: 34, height: 34, borderRadius: '50%', background: '#0F6B7322', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, fontWeight: 700, color: '#0F6B73', flexShrink: 0 }}>
|
||||||
|
{enAuthor(article)?.[0] || 'A'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 13, fontWeight: 600, color: '#263238' }}>{enAuthor(article)}</div>
|
||||||
|
<div style={{ fontSize: 11, color: '#5F6B6D' }}>{article.dateEn} · Teknav</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ background: '#F4EFE6', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, minHeight: 260 }}>
|
||||||
|
<LazyDiagram type={article.diagram} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div style={{ height: 110, background: '#F4EFE6', borderRadius: 8, marginBottom: 16, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
<div style={{ transform: 'scale(0.68)', transformOrigin: 'center', width: '100%' }}>
|
||||||
|
<LazyDiagram type={article.diagram} compact />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 6, marginBottom: 10, flexWrap: 'wrap' }}>
|
||||||
|
<span style={{ background: '#F4EFE6', color: '#A8512E', borderRadius: 5, padding: '2px 8px', fontSize: 10, fontWeight: 700 }}>{enCategory(article)}</span>
|
||||||
|
<span style={{ background: '#F4EFE6', color: '#5F6B6D', borderRadius: 5, padding: '2px 8px', fontSize: 10 }}>{article.readTime} min</span>
|
||||||
|
</div>
|
||||||
|
<h3 style={{ fontSize: 14, fontWeight: 700, color: '#263238', margin: '0 0 8px', lineHeight: 1.5, flex: 1 }}>
|
||||||
|
{enTitle(article)}
|
||||||
|
</h3>
|
||||||
|
<p style={{ fontSize: 12, color: '#5F6B6D', lineHeight: 1.7, margin: '0 0 14px' }}>
|
||||||
|
{enSummary(article).slice(0, 110)}…
|
||||||
|
</p>
|
||||||
|
<div style={{ fontSize: 11, color: '#8A9A9C', marginTop: 'auto' }}>
|
||||||
|
{enAuthor(article)} · {article.dateEn}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Main page ────────────────────────────────────────────────────────────────
|
||||||
|
function EnglishHomePage() {
|
||||||
|
const { navigate } = useNav();
|
||||||
|
const articles = publishedArticles();
|
||||||
|
const featured = articles.find((a) => a.slug === 'tekrag-persian-data-centric-rag-benchmark') || articles[0];
|
||||||
|
const grid = articles.filter((a) => a.id !== featured?.id).slice(0, 6);
|
||||||
|
const popularArticles = [...articles].sort((a, b) => (b.views || 0) - (a.views || 0)).slice(0, 3);
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ label: 'Articles published', to: 180, suffix: '+', icon: '◉' },
|
||||||
|
{ label: 'Active writers', to: 3, suffix: '', icon: pencilIcon },
|
||||||
|
{ label: 'Monthly readers', to: 45000, suffix: '+', icon: '◐' },
|
||||||
|
{ label: 'Avg. read time', to: 8, suffix: ' min', icon: '◷' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const categories = TeknavData.categories.map((cat) => ({
|
||||||
|
...cat,
|
||||||
|
enName: EN_CATEGORIES[cat.id]?.name || cat.name,
|
||||||
|
enDesc: EN_CATEGORIES[cat.id]?.desc || cat.description,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const authors = TeknavData.authors.map((a) => ({
|
||||||
|
...a,
|
||||||
|
enName: a.nameEn || (a.slug === 'arsam-sabbagh' ? 'Arsam Sabbagh' : a.name),
|
||||||
|
enRole: a.specialtyEn || 'Technology writer and analyst',
|
||||||
|
enBio: a.bioEn || 'Teknav contributor covering technology, strategy, and applied engineering for Persian-speaking readers.',
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div dir="ltr" lang="en" style={{ background: '#FAF7F0', minHeight: '100vh', fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,Arial,sans-serif", color: '#132326' }}>
|
||||||
|
|
||||||
|
{/* ── Own sticky header ─────────────────────────────────────────────── */}
|
||||||
|
<header style={{ position: 'sticky', top: 0, zIndex: 50, background: 'rgba(250,247,240,0.94)', backdropFilter: 'blur(14px)', borderBottom: '1px solid #E4DDD2', padding: '0 28px', height: 60, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 20 }}>
|
||||||
|
<a onClick={(e) => { e.preventDefault(); navigate('/en'); }} href="/en" style={{ display: 'flex', alignItems: 'baseline', gap: 10, textDecoration: 'none' }}>
|
||||||
|
<span style={{ fontSize: 22, fontWeight: 900, color: '#0F2A2E', letterSpacing: '-0.02em' }}>Teknav</span>
|
||||||
|
<span style={{ fontSize: 10, color: '#C46A4D', fontWeight: 700, letterSpacing: '0.14em' }}>ENGLISH</span>
|
||||||
|
</a>
|
||||||
|
<nav style={{ display: 'flex', alignItems: 'center', gap: 20, flexWrap: 'wrap' }}>
|
||||||
|
<a href="#articles" style={{ color: '#4A5A5C', textDecoration: 'none', fontSize: 13, fontWeight: 500 }}>Articles</a>
|
||||||
|
<a href="#topics" style={{ color: '#4A5A5C', textDecoration: 'none', fontSize: 13, fontWeight: 500 }}>Topics</a>
|
||||||
|
<a href="#authors" style={{ color: '#4A5A5C', textDecoration: 'none', fontSize: 13, fontWeight: 500 }}>Writers</a>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
style={{ background: 'transparent', border: '1px solid #0F6B73', color: '#0F6B73', borderRadius: 7, padding: '6px 14px', fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}
|
||||||
|
>
|
||||||
|
← Persian site
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* ── Hero ─────────────────────────────────────────────────────────── */}
|
||||||
|
<div style={{ position: 'relative', background: '#F4F1EC', overflow: 'hidden', minHeight: 520 }}>
|
||||||
|
<div style={{ position: 'absolute', top: '15%', right: '8%', width: 480, height: 480, borderRadius: '50%', background: 'radial-gradient(circle, rgba(15,107,115,0.09) 0%, transparent 70%)', pointerEvents: 'none' }} />
|
||||||
|
<div style={{ position: 'absolute', bottom: '0%', left: '12%', width: 340, height: 340, borderRadius: '50%', background: 'radial-gradient(circle, rgba(196,106,77,0.09) 0%, transparent 70%)', pointerEvents: 'none' }} />
|
||||||
|
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(15,42,46,0.035) 1px, transparent 1px), linear-gradient(90deg, rgba(15,42,46,0.035) 1px, transparent 1px)', backgroundSize: '48px 48px', maskImage: 'radial-gradient(circle at 50% 40%, #000 30%, transparent 75%)', WebkitMaskImage: 'radial-gradient(circle at 50% 40%, #000 30%, transparent 75%)', pointerEvents: 'none' }} />
|
||||||
|
<HeroBg />
|
||||||
|
<div style={{ position: 'relative', zIndex: 2, maxWidth: 1240, margin: '0 auto', padding: '80px 28px 72px', display: 'grid', gridTemplateColumns: 'minmax(0,1.3fr) minmax(280px,0.7fr)', gap: 48, alignItems: 'center' }}>
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: 'rgba(15,107,115,0.08)', border: '1px solid rgba(15,107,115,0.22)', color: '#0F6B73', padding: '6px 16px', borderRadius: 999, fontSize: 11, fontWeight: 700, marginBottom: 26, letterSpacing: '0.07em', animation: 'enReveal 0.6s 0.1s both' }}>
|
||||||
|
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#0F6B73', display: 'inline-block', animation: 'enPulse 2.2s infinite' }} />
|
||||||
|
Deep technology narratives from Iran
|
||||||
|
</div>
|
||||||
|
<h1 style={{ fontSize: 'clamp(36px,5.5vw,62px)', fontWeight: 800, color: '#0F2A2E', margin: '0 0 20px', lineHeight: 1.12, letterSpacing: '-0.02em', animation: 'enReveal 0.7s 0.3s both' }}>
|
||||||
|
Teknav tracks AI, data,<br />security & the future<br />
|
||||||
|
<span style={{ background: 'linear-gradient(90deg, #0F6B73, #D49A2A)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>of technology.</span>
|
||||||
|
</h1>
|
||||||
|
<p style={{ fontSize: 16, color: '#4A5A5C', lineHeight: 1.85, margin: '0 0 32px', maxWidth: 580, animation: 'enReveal 0.7s 0.55s both' }}>
|
||||||
|
Independent editorial research for engineers, founders, and analysts. Persian-first publication — this page gives international readers an English overview of Teknav's analysis and research.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', animation: 'enReveal 0.7s 0.8s both' }}>
|
||||||
|
<button onClick={() => { document.getElementById('articles')?.scrollIntoView({ behavior: 'smooth' }); }} style={{ background: '#0F6B73', color: '#fff', border: 'none', padding: '13px 28px', borderRadius: 9, fontSize: 14, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', boxShadow: '0 6px 20px rgba(15,107,115,0.28)', transition: 'all 0.2s' }}
|
||||||
|
onMouseEnter={(e) => { e.currentTarget.style.transform = 'translateY(-2px)'; e.currentTarget.style.boxShadow = '0 10px 26px rgba(15,107,115,0.36)'; }}
|
||||||
|
onMouseLeave={(e) => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = '0 6px 20px rgba(15,107,115,0.28)'; }}>
|
||||||
|
Browse research →
|
||||||
|
</button>
|
||||||
|
<button onClick={() => navigate('/article/tekrag-persian-data-centric-rag-benchmark')} style={{ background: 'rgba(196,106,77,0.08)', color: '#A8512E', border: '1px solid rgba(196,106,77,0.4)', padding: '13px 26px', borderRadius: 9, fontSize: 14, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.2s' }}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(196,106,77,0.16)'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(196,106,77,0.08)'}>
|
||||||
|
Read TekRAG-Persian
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Hero card */}
|
||||||
|
{featured && (
|
||||||
|
<div onClick={() => navigate('/article/' + featured.slug)} style={{ background: '#fff', border: '1px solid #E4DDD2', borderRadius: 14, padding: '22px 24px', cursor: 'pointer', boxShadow: '0 12px 38px rgba(15,42,46,0.08)', position: 'relative', overflow: 'hidden', animation: 'enReveal 0.7s 1.05s both', transition: 'box-shadow 0.2s' }}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.boxShadow = '0 18px 50px rgba(15,42,46,0.13)'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.boxShadow = '0 12px 38px rgba(15,42,46,0.08)'}>
|
||||||
|
<div style={{ position: 'absolute', top: 0, left: 0, bottom: 0, width: 4, background: 'linear-gradient(180deg,#D49A2A,#C46A4D)' }} />
|
||||||
|
<div style={{ fontSize: 10, color: '#C46A4D', fontWeight: 700, marginBottom: 10, letterSpacing: '0.1em' }}>✦ FEATURED ARTICLE</div>
|
||||||
|
<div style={{ fontSize: 16, fontWeight: 700, color: '#0F2A2E', lineHeight: 1.55, marginBottom: 12, transition: 'color 0.15s' }}>
|
||||||
|
{enTitle(featured)}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#5F6B6D', display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||||
|
<span style={{ fontWeight: 600, color: '#0F6B73' }}>{enAuthor(featured)}</span>
|
||||||
|
<span style={{ opacity: 0.4 }}>·</span>
|
||||||
|
<span>{featured.readTime} min read</span>
|
||||||
|
<span style={{ opacity: 0.4 }}>·</span>
|
||||||
|
<span>{enCategory(featured)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<svg viewBox="0 0 1440 50" preserveAspectRatio="none" style={{ position: 'absolute', bottom: -1, left: 0, width: '100%', height: 42, display: 'block', pointerEvents: 'none' }}>
|
||||||
|
<path d="M0,34 C240,50 480,8 720,25 C960,42 1200,12 1440,28 L1440,50 L0,50 Z" fill="#ebe3d2" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Stats bar ─────────────────────────────────────────────────────── */}
|
||||||
|
<div style={{ background: '#ebe3d2', padding: '36px 24px', borderTop: '1px solid rgba(15,42,46,0.06)', borderBottom: '1px solid rgba(15,42,46,0.06)' }}>
|
||||||
|
<div style={{ maxWidth: 1240, margin: '0 auto', display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 16 }} className="en-stats-grid">
|
||||||
|
{stats.map((s, i) => (
|
||||||
|
<div key={s.label} style={{ textAlign: 'center', padding: '10px 8px', position: 'relative' }}>
|
||||||
|
<div style={{ fontSize: 13, color: '#0F6B73', marginBottom: 6, opacity: 0.7 }}>{iconNode(s.icon)}</div>
|
||||||
|
<div style={{ fontSize: 28, fontWeight: 900, color: '#A8761B', marginBottom: 4, letterSpacing: '-0.01em' }}>
|
||||||
|
<CountUp to={s.to} suffix={s.suffix} />
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#4A5A5C', fontWeight: 500 }}>{s.label}</div>
|
||||||
|
{i < stats.length - 1 && <div style={{ position: 'absolute', right: 0, top: '20%', height: '60%', width: 1, background: 'rgba(15,42,46,0.08)' }} />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ maxWidth: 1240, margin: '0 auto', padding: '0 24px' }}>
|
||||||
|
|
||||||
|
{/* ── Featured article ───────────────────────────────────────────── */}
|
||||||
|
{featured && (
|
||||||
|
<section id="articles" style={{ padding: '64px 0 32px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24, flexWrap: 'wrap', gap: 12 }}>
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 4px', color: '#A8512E', fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>Featured</p>
|
||||||
|
<h2 style={{ margin: 0, fontSize: 'clamp(20px,2.5vw,28px)', fontWeight: 800, color: '#263238' }}>Latest research</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<EnArticleCard article={featured} featured />
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ── Article grid ───────────────────────────────────────────────── */}
|
||||||
|
<section style={{ padding: '20px 0 48px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24, flexWrap: 'wrap', gap: 12 }}>
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 4px', color: '#A8512E', fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>Analysis</p>
|
||||||
|
<h2 style={{ margin: 0, fontSize: 'clamp(20px,2.5vw,28px)', fontWeight: 800, color: '#263238' }}>Recent articles</h2>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => navigate('/articles')} style={{ background: 'none', border: '1px solid #E4DDD2', color: '#0F6B73', padding: '8px 18px', borderRadius: 8, fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
|
||||||
|
All articles →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0,1fr))', gap: 18 }} className="en-article-grid">
|
||||||
|
{grid.map((a, i) => <EnArticleCard key={a.id} article={a} delay={i * 70} />)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Most read ─────────────────────────────────────────────────── */}
|
||||||
|
<section style={{ padding: '0 0 48px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24, flexWrap: 'wrap', gap: 12 }}>
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 4px', color: '#A8512E', fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>Popular</p>
|
||||||
|
<h2 style={{ margin: 0, fontSize: 'clamp(20px,2.5vw,28px)', fontWeight: 800, color: '#263238' }}>Most read</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
|
{popularArticles.map((a, i) => (
|
||||||
|
<div key={a.id} onClick={() => navigate('/article/' + a.slug)} style={{ display: 'flex', gap: 20, padding: '18px 0', borderBottom: '1px solid #E4DDD2', cursor: 'pointer', alignItems: 'flex-start', transition: 'background 0.15s', borderRadius: 4 }}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.background = '#F4EFE630'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
|
||||||
|
<div style={{ fontSize: 32, fontWeight: 900, color: '#E4DDD2', minWidth: 40, lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>0{i + 1}</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{ display: 'flex', gap: 6, marginBottom: 6, flexWrap: 'wrap' }}>
|
||||||
|
<span style={{ background: '#F4EFE6', color: '#A8512E', borderRadius: 5, padding: '2px 8px', fontSize: 10, fontWeight: 700 }}>{enCategory(a)}</span>
|
||||||
|
<span style={{ background: '#F4EFE6', color: '#5F6B6D', borderRadius: 5, padding: '2px 8px', fontSize: 10 }}>{a.readTime} min</span>
|
||||||
|
</div>
|
||||||
|
<h3 style={{ fontSize: 15, fontWeight: 700, color: '#263238', margin: '0 0 6px', lineHeight: 1.45 }}>{enTitle(a)}</h3>
|
||||||
|
<div style={{ fontSize: 11, color: '#8A9A9C' }}>{enAuthor(a)} · {(a.views || 0).toLocaleString()} views</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: 72, height: 72, background: '#F4EFE6', borderRadius: 8, overflow: 'hidden', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
<div style={{ transform: 'scale(0.52)', transformOrigin: 'center' }}>
|
||||||
|
<LazyDiagram type={a.diagram} compact />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Categories ────────────────────────────────────────────────── */}
|
||||||
|
<section id="topics" style={{ padding: '0 0 48px' }}>
|
||||||
|
<div style={{ marginBottom: 24 }}>
|
||||||
|
<p style={{ margin: '0 0 4px', color: '#A8512E', fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>Site map</p>
|
||||||
|
<h2 style={{ margin: 0, fontSize: 'clamp(20px,2.5vw,28px)', fontWeight: 800, color: '#263238' }}>Topics we cover</h2>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px,1fr))', gap: 12 }} className="en-cat-grid">
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<div key={cat.id} onClick={() => navigate('/category/' + cat.slug)} style={{ background: '#fff', border: `1px solid ${cat.color}30`, borderLeft: `3px solid ${cat.color}`, borderRadius: 10, padding: '18px 16px', cursor: 'pointer', transition: 'all 0.2s' }}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.background = cat.color + '0A'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.background = '#fff'}>
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 700, color: '#263238', marginBottom: 5 }}>{cat.enName}</div>
|
||||||
|
<div style={{ fontSize: 11, color: '#5F6B6D', marginBottom: 10, lineHeight: 1.5 }}>{cat.enDesc}</div>
|
||||||
|
<div style={{ fontSize: 11, color: cat.color, fontWeight: 600 }}>{cat.articleCount} articles →</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Authors ───────────────────────────────────────────────────── */}
|
||||||
|
<section id="authors" style={{ padding: '0 0 48px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24, flexWrap: 'wrap', gap: 12 }}>
|
||||||
|
<div>
|
||||||
|
<p style={{ margin: '0 0 4px', color: '#A8512E', fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>Team</p>
|
||||||
|
<h2 style={{ margin: 0, fontSize: 'clamp(20px,2.5vw,28px)', fontWeight: 800, color: '#263238' }}>Writers & analysts</h2>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => navigate('/authors')} style={{ background: 'none', border: '1px solid #E4DDD2', color: '#0F6B73', padding: '8px 18px', borderRadius: 8, fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>
|
||||||
|
All writers →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px,1fr))', gap: 16 }}>
|
||||||
|
{authors.map((a) => (
|
||||||
|
<div key={a.id} onClick={() => navigate(a.username ? `/profile/@${a.username}` : `/author/${a.slug}`)} style={{ background: '#fff', border: '1px solid #E4DDD2', borderRadius: 12, padding: '22px', cursor: 'pointer', transition: 'all 0.2s' }}
|
||||||
|
onMouseEnter={(e) => { e.currentTarget.style.transform = 'translateY(-3px)'; e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.07)'; }}
|
||||||
|
onMouseLeave={(e) => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = 'none'; }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
|
||||||
|
<div style={{ width: 48, height: 48, borderRadius: '50%', background: '#0F6B7322', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20, fontWeight: 700, color: '#0F6B73', flexShrink: 0 }}>
|
||||||
|
{a.enName?.[0] || 'A'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 15, fontWeight: 700, color: '#263238', marginBottom: 2 }}>{a.enName}</div>
|
||||||
|
<div style={{ fontSize: 11, color: '#0F6B73', fontWeight: 600 }}>{a.enRole}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: 12, color: '#5F6B6D', lineHeight: 1.75, margin: '0 0 12px' }}>
|
||||||
|
{a.enBio?.slice(0, 160)}…
|
||||||
|
</p>
|
||||||
|
<div style={{ fontSize: 11, color: '#8A9A9C' }}>{a.articleCount} articles published</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Newsletter CTA ─────────────────────────────────────────────── */}
|
||||||
|
<section style={{ padding: '0 0 72px' }}>
|
||||||
|
<div style={{ background: '#ebe3d2', borderRadius: 18, padding: '52px 40px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 32, flexWrap: 'wrap', position: 'relative', overflow: 'hidden', border: '1px solid rgba(15,42,46,0.06)' }}>
|
||||||
|
<div style={{ position: 'absolute', top: -60, right: -60, width: 240, height: 240, borderRadius: '50%', background: 'radial-gradient(circle, rgba(15,107,115,0.10) 0%, transparent 70%)', pointerEvents: 'none' }} />
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<div style={{ fontSize: 11, color: '#A8512E', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 10 }}>Newsletter</div>
|
||||||
|
<h2 style={{ fontSize: 'clamp(20px,2.5vw,30px)', fontWeight: 800, color: '#0F2A2E', margin: '0 0 10px', lineHeight: 1.3 }}>Weekly technology signals from Teknav</h2>
|
||||||
|
<p style={{ color: '#4A5A5C', lineHeight: 1.8, maxWidth: 520, margin: 0, fontSize: 14 }}>
|
||||||
|
The Persian newsletter covers AI, cybersecurity, software, hardware, startups, and data — published weekly for Iranian engineering teams.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', flexShrink: 0 }}>
|
||||||
|
<button onClick={() => navigate('/newsletter')} style={{ background: '#0F6B73', color: '#fff', border: 'none', padding: '13px 26px', borderRadius: 9, fontSize: 14, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', transition: 'all 0.2s' }}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.background = '#0d5c63'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.background = '#0F6B73'}>
|
||||||
|
Open archive →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Footer ───────────────────────────────────────────────────────── */}
|
||||||
|
<footer style={{ background: '#0F2A2E', color: '#C8D4D5', padding: '40px 28px', marginTop: 'auto' }}>
|
||||||
|
<div style={{ maxWidth: 1240, margin: '0 auto', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24, flexWrap: 'wrap' }}>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 22, fontWeight: 900, color: '#FAF7F0', marginBottom: 4 }}>Teknav</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#7A9A9C' }}>Deep technology narratives from Iran's Persian-language tech media.</div>
|
||||||
|
</div>
|
||||||
|
<nav style={{ display: 'flex', gap: 18, flexWrap: 'wrap' }}>
|
||||||
|
{[['English Home', '/en'], ['All Articles', '/articles'], ['Authors', '/authors'], ['Newsletter', '/newsletter']].map(([label, path]) => (
|
||||||
|
<button key={path} onClick={() => navigate(path)} style={{ background: 'none', border: 'none', color: '#A8C0C2', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit', padding: 0, transition: 'color 0.15s' }}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.color = '#FAF7F0'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.color = '#A8C0C2'}>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
<div style={{ fontSize: 12, color: '#4A6A6C' }}>© 2025 Teknav · teknav.ir</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
@keyframes enReveal { from { opacity:0; filter:blur(5px); transform:translateY(16px); } to { opacity:1; filter:blur(0); transform:translateY(0); } }
|
||||||
|
@keyframes enPulse { 0%,100%{opacity:1;transform:scale(1);} 50%{opacity:0.5;transform:scale(1.4);} }
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.en-stats-grid { grid-template-columns: repeat(2,1fr) !important; }
|
||||||
|
.en-article-grid { grid-template-columns: 1fr 1fr !important; }
|
||||||
|
}
|
||||||
|
@media (max-width: 620px) {
|
||||||
|
.en-article-grid { grid-template-columns: 1fr !important; }
|
||||||
|
.en-cat-grid { grid-template-columns: 1fr 1fr !important; }
|
||||||
|
.en-stats-grid { grid-template-columns: 1fr 1fr !important; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { EnglishHomePage };
|
||||||
Reference in New Issue
Block a user