diff --git a/global/src/global-articles.jsx b/global/src/global-articles.jsx
new file mode 100644
index 0000000..21568ea
--- /dev/null
+++ b/global/src/global-articles.jsx
@@ -0,0 +1,341 @@
+// global-articles.jsx — Article detail + article list for Teknav Global (English, LTR)
+import { useState, useEffect, useRef, useMemo } from 'react';
+import { articles as allArticles, authors, categories } from '../global-data.js';
+import {
+ useNav, useToast, CategoryBadge, TypeBadge, AuthorAvatar, EmptyState,
+ ReadingProgress, SkeletonLoader, NewsletterForm,
+ inputStyle, pillBtn, iconBtnStyle,
+} from './global-ui.jsx';
+import { ArticleCard, sectionTitle, sectionSubtitle } from './global-home.jsx';
+
+// ── Article List Row ─────────────────────────────────────────────────────────
+function ArticleRow({ article, idx }) {
+ const { navigate } = useNav();
+ const [vis, setVis] = useState(false);
+ const ref = useRef();
+ const cat = categories.find(c => c.id === article.category);
+ const author = authors.find(a => a.id === article.author);
+
+ useEffect(() => {
+ const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) setVis(true); }, { threshold: 0.05 });
+ if (ref.current) obs.observe(ref.current);
+ return () => obs.disconnect();
+ }, []);
+
+ return (
+
navigate('/article/' + article.slug)}
+ onMouseEnter={e => e.currentTarget.style.opacity = '0.85'}
+ onMouseLeave={e => e.currentTarget.style.opacity = '1'}>
+
+
+ {article.date}
+ ·
+ {article.type && }
+
+
+
{article.title}
+ {article.subtitle &&
{article.subtitle}
}
+
+ {author?.name}
+ {author?.specialty && · }
+ {author?.specialty && {author.specialty}}
+
+
+ {article.type}
+ ·
+ {article.readTime} min read
+ {article.tags?.slice(0, 2).map(t => (
+ {t}
+ ))}
+
+
+
+ {article.photo && (
+

{ e.target.style.display = 'none'; }} />
+ )}
+
+
+ );
+}
+
+// ── Filter Bar ───────────────────────────────────────────────────────────────
+function FilterBar({ filters, onChange }) {
+ const cats = [{ id: '', name: 'All' }, ...categories];
+ const sorts = [{ v: 'latest', l: 'Latest' }, { v: 'popular', l: 'Popular' }, { v: 'readtime', l: 'Read time' }];
+
+ const chip = (active) => ({
+ border: active ? '1.5px solid #0F6B73' : '1px solid #E4DDD2',
+ background: active ? '#0F6B7312' : '#fff',
+ color: active ? '#0F6B73' : '#5F6B6D',
+ padding: '5px 12px', borderRadius: 20, fontSize: 12, cursor: 'pointer',
+ fontFamily: 'Inter,sans-serif', fontWeight: active ? 700 : 400,
+ transition: 'all 0.15s', whiteSpace: 'nowrap',
+ });
+
+ return (
+
+
+ Topic:
+ {cats.map(c => (
+
+ ))}
+
+
+
+ Sort:
+ {sorts.map(s => (
+
+ ))}
+
+ {filters.q && (
+
+ Search: "{filters.q}"
+
+
+ )}
+
+
+ );
+}
+
+// ── Article List Page ────────────────────────────────────────────────────────
+export function ArticlesListPage({ initQ = '' }) {
+ const [filters, setFilters] = useState({ cat: '', sort: 'latest', q: initQ });
+ const [searchInput, setSearchInput] = useState(initQ);
+ const [page, setPage] = useState(1);
+ const PER = 8;
+
+ const filtered = useMemo(() => {
+ let list = allArticles;
+ if (filters.q) {
+ const q = filters.q.toLowerCase();
+ list = list.filter(a =>
+ a.title.toLowerCase().includes(q) ||
+ (a.subtitle || '').toLowerCase().includes(q) ||
+ authors.find(au => au.id === a.author)?.name.toLowerCase().includes(q) ||
+ categories.find(c => c.id === a.category)?.name.toLowerCase().includes(q)
+ );
+ }
+ if (filters.cat) list = list.filter(a => a.category === filters.cat);
+ if (filters.sort === 'popular') list = [...list].sort((a, b) => (b.views || 0) - (a.views || 0));
+ else if (filters.sort === 'readtime') list = [...list].sort((a, b) => b.readTime - a.readTime);
+ else list = [...list].sort((a, b) => (b.dateEn || b.date || '').localeCompare(a.dateEn || a.date || ''));
+ return list;
+ }, [filters]);
+
+ const visible = filtered.slice(0, page * PER);
+ const hasMore = visible.length < filtered.length;
+ const doSearch = e => { e.preventDefault(); setFilters(f => ({ ...f, q: searchInput })); setPage(1); };
+
+ return (
+
+
+
+
All Articles
+
{filtered.length} articles found
+
+
+
{ setFilters(f); setPage(1); }} />
+
+ {visible.length === 0 ? (
+
+ ) : (
+
+ {visible.map((a, i) =>
)}
+
+ )}
+
+ {hasMore && (
+
+
+
+ )}
+
+
+ );
+}
+
+// ── Article Detail Page ──────────────────────────────────────────────────────
+export default function ArticlePage({ slug }) {
+ const { navigate } = useNav();
+
+ const article = allArticles.find(a => a.slug === slug);
+
+ if (!article) {
+ return (
+
+
+
404
+
Article not found
+
This article may be available in the Persian edition.
+
+
+
+ );
+ }
+
+ const author = authors.find(a => a.id === article.author);
+ const cat = categories.find(c => c.id === article.category);
+ const related = allArticles.filter(a => a.id !== article.id && (a.category === article.category || (a.tags || []).some(t => (article.tags || []).includes(t)))).slice(0, 3);
+
+ return (
+
+
+
+ {/* Breadcrumb */}
+
+
+
+ ›
+
+ ›
+ {article.title.slice(0, 50)}{article.title.length > 50 ? '…' : ''}
+
+
+
+ {/* Layout */}
+
+
+ {/* Main content */}
+
+ {/* Header */}
+
+
+ {article.type &&
}
+
+ {article.persianUrl && (
+
+ فارسی ↗
+
+ )}
+
+
{article.title}
+ {article.subtitle &&
{article.subtitle}
}
+
+ {/* Byline */}
+
+ {author &&
}
+
+
{author?.name}
+
{author?.role}
+
+
+ {article.date}
+ ·
+ {article.readTime} min read
+ {article.tags?.slice(0, 2).map(t => (
+ {t}
+ ))}
+
+
+
+
+ {/* Hero image */}
+ {article.photo && (
+
+

{ e.target.parentElement.style.display = 'none'; }} />
+
+ )}
+
+ {/* Article body */}
+
+
+ {/* Author box */}
+ {author && (
+
+
+
+
{author.name}
+
{author.role}
+
{author.bio}
+
+
+ )}
+
+ {/* Related articles */}
+ {related.length > 0 && (
+
+ )}
+
+ {/* Newsletter */}
+
+
Subscribe to Teknav
+
Deep tech analysis in your inbox, every week
+
+
+
+
+ {/* Sidebar */}
+
+
+
+
+
+ );
+}