// 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 && ( {article.title} { 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

setSearchInput(e.target.value)} placeholder="Search by title, author, topic..." style={{ flex: 1, ...inputStyle }} />
{ 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.

فارسی (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 && (
{article.title} { e.target.parentElement.style.display = 'none'; }} />
)} {/* Article body */}
{/* Author box */} {author && (
{author.name}
{author.role}
{author.bio}
)} {/* Related articles */} {related.length > 0 && (

Related Articles

{related.map(a => )}
)} {/* Newsletter */}

Subscribe to Teknav

Deep tech analysis in your inbox, every week

{/* Sidebar */}
); }