Files
portfolio/portfolio.html
T
arsam e9cc1fd49a Import source: portfolio
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:15:41 +03:30

4021 lines
200 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" prefix="og: https://ogp.me/ns#">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- ═══════════════════════════════════════════════════════════
PRIMARY META
═══════════════════════════════════════════════════════════ -->
<title>Arsam Sabbagh — AI Researcher, LLM Engineer & Web Designer | آرسام صباغ</title>
<meta name="description" content="Arsam Sabbagh (آرسام صباغ) is a senior AI researcher and LLM engineer based in Tehran, Iran. Editor-in-Chief at Teknav.ir. Expert in LLM pipeline design, inference optimization, and frontier-model evaluation. Web design under the Coaxys brand." />
<meta name="keywords" content="Arsam Sabbagh, آرسام صباغ, ارسام صباغ, AI researcher, LLM engineer, machine learning engineer, Tehran, Iran, Teknav, Coaxys, large language models, inference optimization, Persian NLP, فارسی, هوش مصنوعی, یادگیری ماشین, مهندس هوش مصنوعی, پژوهشگر, تک‌نو, کواکسیس, web designer, frontend developer, AI research, agentic systems, RAG, vLLM, quantitative evaluation" />
<meta name="author" content="Arsam Sabbagh" />
<meta name="copyright" content="© 2026 Arsam Sabbagh" />
<meta name="language" content="English, Persian, Farsi" />
<meta name="geo.region" content="IR-16" />
<meta name="geo.placename" content="Tehran, Iran" />
<meta name="geo.position" content="35.6892;51.3890" />
<meta name="ICBM" content="35.6892, 51.3890" />
<link rel="canonical" href="https://www.arsamsabbagh.ir/" />
<!-- ═══════════════════════════════════════════════════════════
ROBOTS & CRAWLERS
═══════════════════════════════════════════════════════════ -->
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<meta name="googlebot" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<meta name="bingbot" content="index, follow, max-image-preview:large, max-snippet:-1" />
<!-- AI crawlers — explicitly allow indexing -->
<meta name="GPTBot" content="index, follow" />
<meta name="ChatGPT-User" content="index, follow" />
<meta name="Claude-Web" content="index, follow" />
<meta name="ClaudeBot" content="index, follow" />
<meta name="PerplexityBot" content="index, follow" />
<meta name="Amazonbot" content="index, follow" />
<meta name="anthropic-ai" content="index, follow" />
<meta name="cohere-ai" content="index, follow" />
<!-- ═══════════════════════════════════════════════════════════
MULTILINGUAL / HREFLANG
═══════════════════════════════════════════════════════════ -->
<link rel="alternate" hreflang="en" href="https://www.arsamsabbagh.ir/" />
<link rel="alternate" hreflang="fa" href="https://www.arsamsabbagh.ir/?lang=fa" />
<link rel="alternate" hreflang="x-default" href="https://www.arsamsabbagh.ir/" />
<!-- ═══════════════════════════════════════════════════════════
OPEN GRAPH — FACEBOOK, LINKEDIN, WHATSAPP, TELEGRAM
═══════════════════════════════════════════════════════════ -->
<meta property="og:type" content="profile" />
<meta property="og:url" content="https://www.arsamsabbagh.ir/" />
<meta property="og:site_name" content="Arsam Sabbagh" />
<meta property="og:title" content="Arsam Sabbagh — AI Researcher & LLM Engineer | آرسام صباغ" />
<meta property="og:description" content="Senior AI researcher and LLM engineer from Tehran. Editor-in-Chief at Teknav — the most-read ML publication in the Farsi-speaking world. Expert in LLM pipelines, inference optimization, and frontier-model evaluation." />
<meta property="og:image" content="https://www.arsamsabbagh.ir/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Arsam Sabbagh — AI Researcher & LLM Engineer" />
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="fa_IR" />
<meta property="profile:first_name" content="Arsam" />
<meta property="profile:last_name" content="Sabbagh" />
<meta property="profile:username" content="arsam-sabbagh" />
<!-- ═══════════════════════════════════════════════════════════
TWITTER / X CARD
═══════════════════════════════════════════════════════════ -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@arsamsabbagh" />
<meta name="twitter:creator" content="@arsamsabbagh" />
<meta name="twitter:title" content="Arsam Sabbagh — AI Researcher & LLM Engineer | آرسام صباغ" />
<meta name="twitter:description" content="Senior AI researcher and LLM engineer from Tehran. Editor-in-Chief at Teknav. Building, evaluating, and shipping intelligent systems at industrial scale." />
<meta name="twitter:image" content="https://www.arsamsabbagh.ir/og-image.png" />
<meta name="twitter:image:alt" content="Arsam Sabbagh — AI Researcher & LLM Engineer" />
<!-- ═══════════════════════════════════════════════════════════
THEME & APP META
═══════════════════════════════════════════════════════════ -->
<meta name="theme-color" content="#F4F0E8" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0D0D0D" media="(prefers-color-scheme: dark)" />
<meta name="color-scheme" content="light dark" />
<meta name="application-name" content="Arsam Sabbagh Portfolio" />
<meta name="generator" content="Coaxys — arsamsabbagh.ir" />
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — PERSON + IDENTITY
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"@id": "https://arsamsabbagh.ir/#person",
"name": "Arsam Sabbagh",
"alternateName": ["آرسام صباغ","ارسام صباغ","Arsam Sabagh","آرسام سباغ","ارسام سباق","Arsam Sabagh Teknav"],
"givenName": "Arsam",
"familyName": "Sabbagh",
"honorificPrefix": "Sr.",
"description": "Senior AI researcher and LLM engineer based in Tehran, Iran. Editor-in-Chief at Teknav.ir — the most-read machine learning publication in the Farsi-speaking world, with 118,280 total views and 28 long-form articles. Founder of Coaxys, an editorial web-design studio. Expert in LLM pipeline design, inference optimization, quantitative frontier-model evaluation, Persian NLP, and agentic systems.",
"url": "https://www.arsamsabbagh.ir/",
"mainEntityOfPage": { "@type": "ProfilePage", "@id": "https://arsamsabbagh.ir/#profilepage" },
"email": "hello@arsamsabbagh.ir",
"image": {
"@type": "ImageObject",
"@id": "https://arsamsabbagh.ir/#og-image",
"url": "https://www.arsamsabbagh.ir/og-image.png",
"contentUrl": "https://www.arsamsabbagh.ir/og-image.png",
"width": 1200,
"height": 630,
"caption": "Arsam Sabbagh — AI Researcher & LLM Engineer"
},
"jobTitle": ["Senior AI Researcher","LLM Engineer","Editor-in-Chief","Web Designer","Founder"],
"hasOccupation": [
{
"@type": "Occupation",
"name": "Senior AI Researcher",
"description": "LLM pipeline design, inference optimization, frontier-model evaluation, agentic systems, Persian NLP",
"occupationLocation": { "@type": "City", "name": "Tehran", "containedInPlace": { "@type": "Country", "name": "Iran" } },
"estimatedSalary": { "@type": "MonetaryAmountDistribution", "currency": "USD", "percentile10": 80000, "percentile90": 200000 },
"skills": "PyTorch, vLLM, TensorRT-LLM, HuggingFace, sglang, Outlines, lm-eval-harness, DuckDB, Kafka"
},
{
"@type": "Occupation",
"name": "Web Designer & Frontend Engineer",
"description": "Editorial web design, brand identity, React/Next.js engineering under the Coaxys brand",
"occupationLocation": { "@type": "City", "name": "Tehran" },
"skills": "React 19, Next.js, Tailwind CSS, Framer Motion, Figma, MDX, TypeScript"
},
{
"@type": "Occupation",
"name": "Editor-in-Chief",
"description": "Leading Teknav — the most-read Persian-language machine learning and AI publication",
"occupationLocation": { "@type": "City", "name": "Tehran" }
}
],
"knowsAbout": [
"Large Language Models","LLM Pipeline Design","Inference Optimization","Speculative Decoding",
"KV-Cache Optimization","Quantitative Model Evaluation","Agentic Systems","Persian NLP",
"Retrieval-Augmented Generation","Data-Centric AI","Machine Learning","Deep Learning",
"Frontend Development","Editorial Web Design","Brand Identity","React","Next.js",
"vLLM","TensorRT-LLM","PyTorch","HuggingFace Transformers","lm-eval-harness",
"OpenTelemetry","Weights and Biases","Kafka","DuckDB","Polars"
],
"knowsLanguage": [
{ "@type": "Language", "name": "Persian", "alternateName": ["Farsi","فارسی"] },
{ "@type": "Language", "name": "English" }
],
"nationality": { "@type": "Country", "name": "Iran" },
"address": {
"@type": "PostalAddress",
"addressLocality": "Tehran",
"addressRegion": "Tehran Province",
"addressCountry": "IR"
},
"sameAs": [
"https://www.arsamsabbagh.ir",
"https://github.com/Coaxys-A",
"https://teknav.ir/profile/@arsam-sabbagh",
"https://t.me/Coaxys",
"https://instagram.com/_coaxys"
],
"worksFor": {
"@type": "Organization",
"@id": "https://arsamsabbagh.ir/#teknav",
"name": "Teknav",
"url": "https://teknav.ir",
"description": "The most-read machine learning and AI publication in the Farsi-speaking world"
},
"founder": { "@id": "https://arsamsabbagh.ir/#coaxys" },
"memberOf": [
{ "@type": "Organization", "name": "Teknav", "url": "https://teknav.ir" }
],
"publishingPrinciples": "https://teknav.ir/profile/@arsam-sabbagh",
"seeks": {
"@type": "Demand",
"name": "Senior ML engineering roles, research collaborations, editorial commissions, design engagements"
}
}
</script>
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — WEBSITE + SITELINKS SEARCH BOX
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"@id": "https://arsamsabbagh.ir/#website",
"url": "https://www.arsamsabbagh.ir/",
"name": "Arsam Sabbagh — Portfolio",
"alternateName": ["arsamsabbagh.ir","Arsam Sabbagh Portfolio","آرسام صباغ"],
"description": "Official portfolio of Arsam Sabbagh — senior AI researcher, LLM engineer, Editor-in-Chief at Teknav, and web designer (Coaxys). Tehran, Iran.",
"inLanguage": ["en","fa"],
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"publisher": { "@id": "https://arsamsabbagh.ir/#person" },
"copyrightYear": 2026,
"copyrightHolder": { "@id": "https://arsamsabbagh.ir/#person" },
"license": "https://arsamsabbagh.ir/",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://www.arsamsabbagh.ir/?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
</script>
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — PROFILE PAGE
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ProfilePage",
"@id": "https://arsamsabbagh.ir/#profilepage",
"url": "https://www.arsamsabbagh.ir/",
"name": "Arsam Sabbagh — AI Researcher & LLM Engineer | آرسام صباغ",
"description": "Official profile and portfolio of Arsam Sabbagh (آرسام صباغ), senior AI researcher, LLM engineer, and Editor-in-Chief at Teknav, Tehran, Iran.",
"inLanguage": ["en","fa"],
"dateCreated": "2024-01-01",
"dateModified": "2026-05-21",
"datePublished": "2024-01-01",
"mainEntity": { "@id": "https://arsamsabbagh.ir/#person" },
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"publisher": { "@id": "https://arsamsabbagh.ir/#person" },
"isPartOf": { "@id": "https://arsamsabbagh.ir/#website" },
"image": { "@id": "https://arsamsabbagh.ir/#og-image" },
"speakable": {
"@type": "SpeakableSpecification",
"cssSelector": [".hero-name",".hero-lede",".about-p",".contact-lede"]
},
"breadcrumb": {
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Arsam Sabbagh", "item": "https://www.arsamsabbagh.ir/" }
]
}
}
</script>
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — COAXYS ORGANIZATION
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://arsamsabbagh.ir/#coaxys",
"name": "Coaxys",
"alternateName": "کواکسیس",
"description": "Editorial web design and brand studio founded by Arsam Sabbagh — serving startups and tech companies who want their craft to match their ambition.",
"url": "https://www.arsamsabbagh.ir",
"founder": { "@id": "https://arsamsabbagh.ir/#person" },
"foundingDate": "2022",
"foundingLocation": { "@type": "City", "name": "Tehran", "containedInPlace": { "@type": "Country", "name": "Iran" } },
"areaServed": "Worldwide",
"serviceType": ["Web Design","Frontend Development","Brand Identity","UI/UX Design","Editorial Design"],
"sameAs": [
"https://instagram.com/_coaxys",
"https://t.me/Coaxys",
"https://github.com/Coaxys-A"
]
}
</script>
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — ARTICLES (BlogPosting for each Teknav piece)
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ItemList",
"name": "Articles by Arsam Sabbagh on Teknav",
"description": "28 long-form technical articles on machine learning, LLM engineering, inference optimization, and applied AI research by Arsam Sabbagh, Editor-in-Chief at Teknav.",
"url": "https://teknav.ir/profile/@arsam-sabbagh",
"numberOfItems": 28,
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"itemListElement": [
{
"@type": "ListItem", "position": 1,
"item": {
"@type": "BlogPosting",
"headline": "On Quantitative Evaluation of Frontier Models",
"alternativeHeadline": "دربارهٔ ارزیابی کمّی مدل‌های پیشرو",
"description": "A working definition for what 'good' means when the benchmarks themselves are the moving target.",
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"publisher": { "@id": "https://arsamsabbagh.ir/#teknav" },
"datePublished": "2025-04-01",
"url": "https://teknav.ir/profile/@arsam-sabbagh",
"inLanguage": "fa",
"interactionStatistic": [
{ "@type": "InteractionCounter", "interactionType": "https://schema.org/ReadAction", "userInteractionCount": 18420 },
{ "@type": "InteractionCounter", "interactionType": "https://schema.org/LikeAction", "userInteractionCount": 612 }
],
"keywords": "LLM evaluation, frontier models, benchmarks, AI research"
}
},
{
"@type": "ListItem", "position": 2,
"item": {
"@type": "BlogPosting",
"headline": "Building a Persian-First RAG Stack",
"alternativeHeadline": "ساختِ استک RAG با محوریتِ زبان فارسی",
"description": "A Persian-first retrieval-augmented generation stack with domain adapters for legal and medical corpora.",
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"publisher": { "@id": "https://arsamsabbagh.ir/#teknav" },
"datePublished": "2025-03-01",
"url": "https://teknav.ir/profile/@arsam-sabbagh",
"inLanguage": "fa",
"interactionStatistic": [
{ "@type": "InteractionCounter", "interactionType": "https://schema.org/ReadAction", "userInteractionCount": 12110 },
{ "@type": "InteractionCounter", "interactionType": "https://schema.org/LikeAction", "userInteractionCount": 504 }
],
"keywords": "RAG, Persian NLP, retrieval-augmented generation, Farsi AI"
}
},
{
"@type": "ListItem", "position": 3,
"item": {
"@type": "BlogPosting",
"headline": "Speculative Decoding, Honestly",
"alternativeHeadline": "رمزگشایی حدسی، صادقانه",
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"publisher": { "@id": "https://arsamsabbagh.ir/#teknav" },
"datePublished": "2025-02-01",
"url": "https://teknav.ir/profile/@arsam-sabbagh",
"inLanguage": "fa",
"interactionStatistic": [
{ "@type": "InteractionCounter", "interactionType": "https://schema.org/ReadAction", "userInteractionCount": 9870 }
],
"keywords": "speculative decoding, inference optimization, LLM speed"
}
},
{
"@type": "ListItem", "position": 4,
"item": {
"@type": "BlogPosting",
"headline": "Agentic Systems Without the Cargo Cult",
"alternativeHeadline": "سامانه‌های عامل‌محور، بدون فرقه‌بازی",
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"publisher": { "@id": "https://arsamsabbagh.ir/#teknav" },
"datePublished": "2025-01-01",
"url": "https://teknav.ir/profile/@arsam-sabbagh",
"inLanguage": "fa",
"interactionStatistic": [
{ "@type": "InteractionCounter", "interactionType": "https://schema.org/ReadAction", "userInteractionCount": 14230 }
],
"keywords": "agentic AI, LLM agents, planner-executor, production AI"
}
},
{
"@type": "ListItem", "position": 5,
"item": {
"@type": "BlogPosting",
"headline": "Inference Optimization: A Field Guide",
"alternativeHeadline": "بهینه‌سازی استنتاج: یک راهنمای میدانی",
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"publisher": { "@id": "https://arsamsabbagh.ir/#teknav" },
"datePublished": "2024-12-01",
"url": "https://teknav.ir/profile/@arsam-sabbagh",
"inLanguage": "fa",
"interactionStatistic": [
{ "@type": "InteractionCounter", "interactionType": "https://schema.org/ReadAction", "userInteractionCount": 16708 }
],
"keywords": "inference optimization, vLLM, TensorRT, quantization, throughput"
}
}
]
}
</script>
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — SOFTWARE PROJECTS (GitHub)
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ItemList",
"name": "Open-source projects by Arsam Sabbagh",
"url": "https://github.com/Coaxys-A",
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"itemListElement": [
{
"@type": "ListItem", "position": 1,
"item": {
"@type": "SoftwareSourceCode",
"name": "Frontier-Eval",
"description": "A reproducible harness for quantitative evaluation of frontier LLMs across reasoning, code, and long-context benchmarks.",
"codeRepository": "https://github.com/Coaxys-A",
"programmingLanguage": ["Python"],
"runtimePlatform": "vLLM",
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"dateCreated": "2025-01-01",
"keywords": "LLM evaluation, frontier models, benchmarking, vLLM"
}
},
{
"@type": "ListItem", "position": 2,
"item": {
"@type": "SoftwareSourceCode",
"name": "Pars-RAG",
"description": "A Persian-first retrieval-augmented generation stack with domain adapters for legal and medical corpora.",
"codeRepository": "https://github.com/Coaxys-A",
"programmingLanguage": ["Python"],
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"dateCreated": "2025-01-01",
"keywords": "RAG, Persian NLP, FAISS, Farsi, retrieval-augmented generation"
}
},
{
"@type": "ListItem", "position": 3,
"item": {
"@type": "SoftwareSourceCode",
"name": "Inference Lab",
"description": "Throughput and latency tuning toolkit — quantization, speculative decoding, and KV-cache strategies benchmarked side by side.",
"codeRepository": "https://github.com/Coaxys-A",
"programmingLanguage": ["Python"],
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"dateCreated": "2024-06-01",
"keywords": "inference optimization, quantization, speculative decoding, KV-cache, vLLM, TensorRT"
}
},
{
"@type": "ListItem", "position": 4,
"item": {
"@type": "SoftwareSourceCode",
"name": "AgentScript",
"description": "A lightweight planner-executor pattern for production agents, with structured tools and replayable traces.",
"codeRepository": "https://github.com/Coaxys-A",
"programmingLanguage": ["TypeScript"],
"author": { "@id": "https://arsamsabbagh.ir/#person" },
"dateCreated": "2024-01-01",
"keywords": "AI agents, agentic systems, TypeScript, OpenTelemetry, LLM orchestration"
}
}
]
}
</script>
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — FAQ (rich result: expandable Q&A in SERP)
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Who is Arsam Sabbagh?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Arsam Sabbagh (آرسام صباغ) is a senior AI researcher and LLM engineer based in Tehran, Iran. He is the Editor-in-Chief of Teknav.ir — the most-read machine learning publication in the Farsi-speaking world — and the founder of Coaxys, an editorial web design studio. He has published 28 long-form technical articles with over 118,000 views."
}
},
{
"@type": "Question",
"name": "What does Arsam Sabbagh specialize in?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Arsam Sabbagh specializes in LLM (Large Language Model) pipeline design, inference optimization (including speculative decoding and quantization with vLLM and TensorRT-LLM), quantitative evaluation of frontier models, Persian/Farsi NLP, and agentic AI systems. He also runs a parallel practice in editorial web design and frontend engineering under the brand Coaxys."
}
},
{
"@type": "Question",
"name": "What is Teknav and what is Arsam Sabbagh's role?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Teknav (تک‌نو, teknav.ir) is the most-read machine learning and AI publication in the Persian/Farsi-speaking world. Arsam Sabbagh is its Editor-in-Chief. He has authored 28 long-form technical articles on the platform, accumulating over 118,280 views and 4,789 reader reactions."
}
},
{
"@type": "Question",
"name": "What is Coaxys?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Coaxys (کواکسیس) is an editorial web design and brand studio founded by Arsam Sabbagh. It serves startups and tech companies with frontend architecture (React, Next.js), brand identity design, UI/UX prototyping, and editorial layout systems. The studio operates from Tehran, Iran and works with clients worldwide."
}
},
{
"@type": "Question",
"name": "Where can I find Arsam Sabbagh's open-source work?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Arsam Sabbagh's open-source projects are available on GitHub at https://github.com/Coaxys-A. Projects include Frontier-Eval (LLM evaluation harness), Pars-RAG (Persian-first RAG stack), Inference Lab (quantization and throughput benchmarking), and AgentScript (production agent framework)."
}
},
{
"@type": "Question",
"name": "How can I contact Arsam Sabbagh?",
"acceptedAnswer": {
"@type": "Answer",
"text": "You can reach Arsam Sabbagh via email at hello@arsamsabbagh.ir, on Telegram at t.me/Coaxys, on Instagram at @_coaxys, or through his Teknav profile at teknav.ir/profile/@arsam-sabbagh. He is open to senior ML engineering roles, research collaborations, editorial commissions, and web design projects through Coaxys."
}
},
{
"@type": "Question",
"name": "آرسام صباغ کیست؟",
"acceptedAnswer": {
"@type": "Answer",
"text": "آرسام صباغ پژوهشگر ارشد هوش مصنوعی و مهندس مدل‌های زبانی (LLM) از تهران، ایران است. او سردبیر تک‌نو (teknav.ir) — پرخواننده‌ترین نشریهٔ یادگیری ماشین در دنیای فارسی‌زبان — و بنیان‌گذار کواکسیس، یک استودیوی طراحی وب است. آرسام صباغ ۲۸ مقالهٔ تخصصی با بیش از ۱۱۸٬۰۰۰ بازدید منتشر کرده است."
}
},
{
"@type": "Question",
"name": "What tech stack does Arsam Sabbagh use?",
"acceptedAnswer": {
"@type": "Answer",
"text": "For AI/ML work: PyTorch, vLLM, TensorRT-LLM, HuggingFace Transformers, sglang, Outlines, lm-eval-harness, OpenTelemetry, Weights & Biases, Modal, Grafana, DuckDB, Polars, Kafka, dbt, Postgres. For web/design work (Coaxys): Next.js, React 19, Tailwind CSS, Framer Motion, Figma, MDX, TypeScript."
}
}
]
}
</script>
<!-- ═══════════════════════════════════════════════════════════
JSON-LD — BREADCRUMB
═══════════════════════════════════════════════════════════ -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Arsam Sabbagh", "item": "https://www.arsamsabbagh.ir/" },
{ "@type": "ListItem", "position": 2, "name": "AI Researcher & LLM Engineer", "item": "https://www.arsamsabbagh.ir/#about" },
{ "@type": "ListItem", "position": 3, "name": "Projects", "item": "https://www.arsamsabbagh.ir/#projects" },
{ "@type": "ListItem", "position": 4, "name": "Writing", "item": "https://www.arsamsabbagh.ir/#writing" },
{ "@type": "ListItem", "position": 5, "name": "Contact", "item": "https://www.arsamsabbagh.ir/#contact" }
]
}
</script>
<!-- ═══════════════════════════════════════════════════════════
IDENTITY VERIFICATION (rel=me — used by Google, Mastodon, etc.)
═══════════════════════════════════════════════════════════ -->
<link rel="me" href="https://github.com/Coaxys-A" />
<link rel="me" href="https://teknav.ir/profile/@arsam-sabbagh" />
<link rel="me" href="https://t.me/Coaxys" />
<link rel="me" href="https://instagram.com/_coaxys" />
<link rel="me" href="mailto:hello@arsamsabbagh.ir" />
<!-- ═══════════════════════════════════════════════════════════
ADDITIONAL META — LINKEDIN, REFERRER, FORMAT, RATING
═══════════════════════════════════════════════════════════ -->
<meta name="referrer" content="strict-origin-when-cross-origin" />
<meta name="format-detection" content="telephone=no" />
<meta name="rating" content="general" />
<meta name="revisit-after" content="7 days" />
<meta name="category" content="Technology, Artificial Intelligence, Machine Learning, Web Design" />
<meta name="classification" content="Portfolio, Professional Profile" />
<meta name="target" content="all" />
<meta name="HandheldFriendly" content="True" />
<meta name="MobileOptimized" content="320" />
<!-- Apple / PWA -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="Arsam Sabbagh" />
<!-- Microsoft -->
<meta name="msapplication-TileColor" content="#0F6B73" />
<meta name="msapplication-config" content="none" />
<!-- Dublin Core (for academic & library indexers) -->
<meta name="DC.title" content="Arsam Sabbagh — AI Researcher & LLM Engineer" />
<meta name="DC.creator" content="Arsam Sabbagh" />
<meta name="DC.subject" content="Artificial Intelligence, Large Language Models, Machine Learning, Web Design" />
<meta name="DC.description" content="Portfolio of Arsam Sabbagh, senior AI researcher and LLM engineer from Tehran, Iran." />
<meta name="DC.publisher" content="Arsam Sabbagh" />
<meta name="DC.language" content="en, fa" />
<meta name="DC.rights" content="© 2026 Arsam Sabbagh" />
<!-- Yandex -->
<meta name="yandex-verification" content="arsam-sabbagh" />
<!-- Article meta (used by LinkedIn, Medium-style scrapers) -->
<meta property="article:author" content="https://www.arsamsabbagh.ir/" />
<meta property="article:published_time" content="2026-05-21T00:00:00+03:30" />
<meta property="article:modified_time" content="2026-05-21T00:00:00+03:30" />
<meta property="article:tag" content="AI Research" />
<meta property="article:tag" content="LLM Engineering" />
<meta property="article:tag" content="Persian NLP" />
<meta property="article:tag" content="Machine Learning" />
<meta property="article:tag" content="Web Design" />
<!-- Speakable for voice assistants (Google Assistant, Siri) -->
<meta name="speakable-selector" content=".hero-name, .hero-lede, .about-p" />
<!-- ═══════════════════════════════════════════════════════════
PRELOAD CRITICAL FONTS
═══════════════════════════════════════════════════════════ -->
<link rel="preload" href="fonts/gyByhwUxId8gMEwcGFU.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="fonts/jizHRFtNs2ka5fXjeivQ4LroWlx-6zAjjH7M.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="fonts/zrfl0HLVx-HwTP82Yaf4Iw.woff2" as="font" type="font/woff2" crossorigin />
<link rel="stylesheet" href="fonts.css">
<!-- ═══════════════════════════════════════════════════════════
MIX — "Ink & Ember" layer: WebGL hero, preloader, intro
═══════════════════════════════════════════════════════════ -->
<link rel="stylesheet" href="css/mix.css">
<script type="importmap">
{ "imports": { "three": "/vendor/three.module.js" } }
</script>
<script src="js/mix.js" type="module"></script>
<style>
:root {
--bg: #F4F0E8;
--bg-elev: #EEE8DC;
--ink: #1A1815;
--ink-soft: #595448;
--ink-mute: #8a8473;
--rule: #d8d1be;
--accent: #0F6B73;
--accent-soft: #0f6b7322;
--terra: #B7593C;
--serif: 'Instrument Serif', 'Times New Roman', serif;
--sans: 'Geist', ui-sans-serif, system-ui, -apple-system, sans-serif;
--mono: 'JetBrains Mono', ui-monospace, monospace;
--farsi: 'Vazirmatn', 'Geist', sans-serif;
--farsi-display: 'Lalezar', 'Vazirmatn', sans-serif;
--farsi-serif: 'Markazi Text', 'Vazirmatn', serif;
--max: 1200px;
--pad: clamp(20px, 4vw, 56px);
}
html.dark {
--bg: #0D0D0D;
--bg-elev: #161614;
--ink: #F2EEE7;
--ink-soft: #b3ad9f;
--ink-mute: #6b675c;
--rule: #2a2925;
--accent: #4FB8C0;
--accent-soft: #4fb8c022;
--terra: #D67B5C;
}
* { box-sizing: border-box; }
/* scroll-behavior handled by Lenis */
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
font-weight: 400;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
transition: background 0.4s ease, color 0.4s ease;
}
html[lang="fa"] body { font-family: var(--farsi); direction: rtl; }
html[lang="fa"] .serif { font-family: var(--farsi-serif); font-style: normal; font-weight: 500; letter-spacing: 0; }
html[lang="fa"] .mono { font-family: var(--farsi); font-weight: 500; letter-spacing: 0.04em; }
::selection { background: var(--accent); color: var(--bg); }
:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
/* Grain / paper-texture overlay */
body::after { content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 9999; opacity: 0.045; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)'/%3E%3C/svg%3E"); background-size: 300px 300px; }
.serif { font-family: var(--serif); font-style: italic; font-weight: 400; letter-spacing: -0.01em; }
.mono { font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; }
/* Scrollbar */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-mute); }
/* Layout primitives */
.wrap { max-width: var(--max); margin: 0 auto; padding: 0 var(--pad); }
.rule { height: 1px; background: var(--rule); width: 100%; }
/* Nav */
.nav {
position: sticky; top: 0; z-index: 50;
backdrop-filter: blur(14px) saturate(1.4);
-webkit-backdrop-filter: blur(14px) saturate(1.4);
background: color-mix(in oklab, var(--bg) 78%, transparent);
border-bottom: 1px solid color-mix(in oklab, var(--rule) 60%, transparent);
}
.nav-inner {
max-width: var(--max);
margin: 0 auto;
padding: 18px var(--pad);
display: flex; align-items: center; justify-content: space-between;
gap: 24px;
}
.brand {
display: flex; align-items: center; gap: 10px;
font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--ink);
}
.brand-mark {
width: 22px; height: 22px; border-radius: 50%;
background: var(--ink);
position: relative;
}
.brand-mark::after {
content: ''; position: absolute; inset: 5px;
border-radius: 50%; background: var(--bg);
}
.brand-mark::before {
content: ''; position: absolute; left: 50%; top: 0; bottom: 0;
width: 1px; background: var(--ink); transform: translateX(-0.5px);
}
.nav-links {
display: flex; gap: 28px; align-items: center;
font-size: 13px; color: var(--ink-soft);
}
.nav-links a { transition: color 0.2s; position: relative; }
.nav-links a:hover { color: var(--accent); }
.nav-links a.active { color: var(--accent); }
.nav-links a.active::after { content: ''; display: block; position: absolute; bottom: -5px; left: 50%; transform: translateX(-50%); width: 3px; height: 3px; border-radius: 50%; background: var(--accent); }
.nav-actions { display: flex; gap: 10px; align-items: center; }
.lang-toggle, .mode-toggle, .cursor-toggle {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 10px;
border: 1px solid var(--rule); border-radius: 999px;
background: transparent; color: var(--ink-soft);
font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
transition: color 0.2s, border-color 0.2s, background 0.2s, transform 0.15s;
}
.lang-toggle:hover, .mode-toggle:hover, .cursor-toggle:hover { color: var(--ink); border-color: var(--ink-mute); }
.lang-toggle:active, .mode-toggle:active, .cursor-toggle:active { transform: scale(0.88); }
.mode-toggle, .cursor-toggle { padding: 6px 8px; }
@media (max-width: 720px) {
.nav-links { display: none; }
.nav-inner { padding: 14px var(--pad); }
}
@media (max-width: 400px) {
:root { --pad: 16px; }
}
/* Section header pattern */
.section { padding: clamp(60px, 9vw, 120px) 0; position: relative; }
.section-head {
display: grid; grid-template-columns: 220px 1fr;
gap: 40px; align-items: baseline;
margin-bottom: clamp(40px, 6vw, 72px);
}
.section-head .num {
color: var(--accent);
font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em;
}
.section-head h2 {
margin: 0; font-family: var(--sans); font-weight: 400;
font-size: clamp(28px, 4.4vw, 52px);
letter-spacing: -0.02em; line-height: 1.05;
color: var(--ink);
}
.section-head h2 em {
font-family: var(--serif); font-style: italic;
font-weight: 400;
font-size: 1.15em;
letter-spacing: -0.01em;
}
@media (max-width: 720px) {
.section-head { grid-template-columns: 1fr; gap: 12px; }
}
/* Footer */
footer {
padding: 60px 0 40px;
border-top: 1px solid var(--rule);
color: var(--ink-soft);
font-size: 13px;
}
/* Animations */
@keyframes fadeUp { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.fadeIn { animation: fadeUp 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
/* Hero entrance — staggered */
.hero-kicker { animation: fadeUp 0.7s 0.05s cubic-bezier(0.2,0.7,0.2,1) both; }
.hero-name { animation: fadeUp 0.9s 0.18s cubic-bezier(0.2,0.7,0.2,1) both; }
.hero-meta { animation: fadeUp 0.8s 0.35s cubic-bezier(0.2,0.7,0.2,1) both; }
.hero-ctas { animation: fadeUp 0.7s 0.50s cubic-bezier(0.2,0.7,0.2,1) both; }
.hero-foot { animation: fadeUp 0.7s 0.62s cubic-bezier(0.2,0.7,0.2,1) both; }
/* Reveal-on-scroll baseline */
[data-reveal] { opacity: 0; transform: translateY(18px); transition: opacity 0.9s cubic-bezier(0.2,0.7,0.2,1), transform 0.9s cubic-bezier(0.2,0.7,0.2,1); }
[data-reveal].is-visible { opacity: 1; transform: none; }
/* Card hover lift */
.proj-card { transition: transform 0.3s cubic-bezier(0.2,0.7,0.2,1); }
.proj-card:hover { transform: translateY(-4px); }
@media (prefers-reduced-motion: reduce) {
.hero-kicker, .hero-name, .hero-meta, .hero-ctas, .hero-foot { animation: none; }
[data-reveal] { transition: none; opacity: 1; transform: none; }
.proj-card:hover { transform: none; }
.tag-off { opacity: 1 !important; }
.proj-grid { animation: none; }
.btn:active, .lang-toggle:active, .mode-toggle:active, .cursor-toggle:active,
.proj-tab:active, .writing-all:active, .ft-mail:active,
.ft-top-btn:active { transform: none; }
}
/* Mobile nav drawer */
.nav-hamburger {
display: none;
background: transparent; border: 1px solid var(--rule); border-radius: 8px;
padding: 7px 9px; color: var(--ink-soft); cursor: pointer; transition: all 0.2s;
flex-direction: column; justify-content: center; gap: 4px;
}
.nav-hamburger:hover { color: var(--ink); border-color: var(--ink-mute); }
.nav-hamburger span { display: block; width: 16px; height: 1.5px; background: currentColor; transition: transform 0.25s, opacity 0.25s; transform-origin: center; }
.nav-hamburger.open span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.nav-hamburger.open span:nth-child(2) { opacity: 0; }
.nav-hamburger.open span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }
@media (max-width: 720px) {
.nav-hamburger { display: flex; }
}
.nav-mobile {
display: none;
flex-direction: column;
padding: 0 var(--pad) 16px;
border-top: 1px solid var(--rule);
gap: 0;
}
.nav-mobile.open { display: flex; animation: fadeUp 0.22s cubic-bezier(0.2,0.7,0.2,1) both; }
.nav-mobile a {
padding: 14px 0;
font-size: 16px;
color: var(--ink);
border-bottom: 1px solid var(--rule);
transition: color 0.2s;
}
.nav-mobile a:last-child { border-bottom: none; }
.nav-mobile a:hover { color: var(--accent); }
/* utility */
.accent { color: var(--accent); }
.terra { color: var(--terra); }
.soft { color: var(--ink-soft); }
.mute { color: var(--ink-mute); }
/* ─────────────────────────────────────────────────────────────────────────
Persian / Farsi typography enhancements
Display fonts behave very differently in Persian — italic display serifs
don't translate. We compensate with:
• Lalezar (a vibrant Persian display sans) for huge hero type
• Markazi Text (a calligraphic Persian serif) where Instrument Serif
italic would be used in English
• Heavier weight contrast within headings
• Persian decorative glyphs (※ ❖ ◊) instead of italic flourishes
───────────────────────────────────────────────────────────────────────── */
html[lang="fa"] .hero-name {
font-family: var(--farsi-display);
font-weight: 400;
letter-spacing: 0;
line-height: 1.05;
font-size: clamp(72px, 14vw, 220px);
}
html[lang="fa"] .section-head h2 {
font-family: var(--farsi);
font-weight: 300;
letter-spacing: -0.01em;
}
html[lang="fa"] .section-head h2 em,
html[lang="fa"] .section-head h2 .em-line {
font-family: var(--farsi-serif);
font-style: normal;
font-weight: 600;
font-size: 1.1em;
color: var(--ink);
}
html[lang="fa"] .about-p:first-of-type::first-letter {
font-family: var(--farsi-display);
font-style: normal;
font-size: 2.6em;
color: var(--accent);
padding: 0.05em 0 0 0.18em;
line-height: 0.85;
}
html[lang="fa"] .exp-name {
font-weight: 600;
letter-spacing: 0;
}
html[lang="fa"] .exp-subname {
font-family: var(--farsi-serif);
font-weight: 500;
}
html[lang="fa"] .mq-item {
font-family: var(--farsi-display);
font-style: normal;
font-weight: 400;
font-size: clamp(28px, 4vw, 50px);
letter-spacing: 0;
}
html[lang="fa"] .pr-n {
font-family: var(--farsi-display);
font-size: 44px;
font-weight: 400;
}
html[lang="fa"] .pr-title { font-weight: 600; }
html[lang="fa"] .now-title {
font-family: var(--farsi);
font-weight: 300;
}
html[lang="fa"] .now-title .serif {
font-family: var(--farsi-serif);
font-weight: 600;
}
html[lang="fa"] .wf-title { font-weight: 600; }
html[lang="fa"] .wf-excerpt {
font-family: var(--farsi-serif);
font-style: normal;
font-weight: 500;
}
html[lang="fa"] .contact-lede {
font-family: var(--farsi-serif);
font-weight: 500;
font-style: normal;
}
html[lang="fa"] .tk-note {
font-family: var(--farsi-serif);
font-weight: 500;
font-style: normal;
}
html[lang="fa"] .stat-num {
font-family: var(--farsi);
font-weight: 300;
letter-spacing: -0.02em;
}
html[lang="fa"] .stat-num .unit {
font-family: var(--farsi-serif);
font-weight: 600;
font-style: normal;
}
html[lang="fa"] .hero-tagline-row .serif,
html[lang="fa"] .hero-amp {
font-family: var(--farsi-serif);
font-weight: 600;
font-style: normal;
}
/* Persian numerals via JetBrains Mono fallback to Vazirmatn for harmony */
html[lang="fa"] .mono { font-family: 'Vazirmatn', 'JetBrains Mono', monospace; }
/* Custom Cursor styles */
.custom-cursor-container {
display: none;
}
@media (pointer: fine) {
html:not(.custom-cursor-disabled),
html:not(.custom-cursor-disabled) :is(body, a, button, select, input, .proj-card, .mode-toggle, .lang-toggle, .cursor-toggle, [role="button"], [role="radio"]) {
cursor: none !important;
}
.custom-cursor-container {
display: block;
position: fixed;
top: 0;
left: 0;
width: 0;
height: 0;
pointer-events: none;
z-index: 2147483646;
opacity: 0;
transform: translate3d(0, 0, 0);
will-change: transform;
transition: opacity 0.3s ease;
}
.custom-cursor {
display: block;
position: absolute;
top: 0;
left: 0;
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid var(--accent);
pointer-events: none;
transform: translate(-50%, -50%);
transition: width 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
height 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
background-color 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 0.3s cubic-bezier(0.2, 0.8, 0.2, 1),
border-radius 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.custom-cursor-dot {
display: block;
position: absolute;
top: 0;
left: 0;
width: 6px;
height: 6px;
background-color: var(--accent);
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
scale: 1;
transition: opacity 0.3s ease,
background-color 0.3s ease,
scale 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.custom-cursor-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--bg);
font-family: var(--sans);
font-size: 8px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
html[lang="fa"] .custom-cursor-text {
font-family: var(--farsi);
font-size: 9px;
font-weight: 500;
}
.custom-cursor.hovering {
width: 32px;
height: 32px;
background-color: color-mix(in oklab, var(--accent) 15%, transparent);
border-color: var(--accent);
}
.custom-cursor.hovering + .custom-cursor-dot {
scale: 1.5;
}
.custom-cursor.hover-project {
width: 64px;
height: 64px;
background-color: var(--accent);
border-color: var(--accent);
}
.custom-cursor.hover-project + .custom-cursor-dot {
opacity: 0;
}
.custom-cursor.hover-project .custom-cursor-text {
opacity: 1;
}
.custom-cursor.hover-toggle {
width: 52px;
height: 52px;
background-color: var(--terra);
border-color: var(--terra);
}
.custom-cursor.hover-toggle + .custom-cursor-dot {
opacity: 0;
}
.custom-cursor.hover-toggle .custom-cursor-text {
opacity: 1;
color: #fff;
}
.custom-cursor.hover-lang {
width: 52px;
height: 52px;
background-color: var(--accent);
border-color: var(--accent);
}
.custom-cursor.hover-lang + .custom-cursor-dot {
opacity: 0;
}
.custom-cursor.hover-lang .custom-cursor-text {
opacity: 1;
}
}
/* Lang Flip Transition Overlay */
.lang-flip-overlay {
position: fixed;
top: 0;
bottom: 0;
left: -20vw;
width: 140vw;
z-index: 2147483645;
background: var(--bg-elev);
border-left: 5px solid var(--accent);
border-right: 5px solid var(--accent);
box-shadow: 0 0 100px rgba(0, 0, 0, 0.25);
pointer-events: none;
transform: translateX(-100%) skewX(-12deg);
opacity: 0;
}
@keyframes paperFlipToRtl {
0% {
opacity: 1;
transform: translateX(-100%) skewX(-12deg);
}
45%, 55% {
opacity: 1;
transform: translateX(0%) skewX(-12deg);
}
100% {
opacity: 1;
transform: translateX(100%) skewX(-12deg);
}
}
@keyframes paperFlipToLtr {
0% {
opacity: 1;
transform: translateX(100%) skewX(-12deg);
}
45%, 55% {
opacity: 1;
transform: translateX(0%) skewX(-12deg);
}
100% {
opacity: 1;
transform: translateX(-100%) skewX(-12deg);
}
}
.lang-flip-overlay.active-to-rtl {
animation: paperFlipToRtl 0.8s cubic-bezier(0.76, 0, 0.24, 1) forwards;
}
.lang-flip-overlay.active-to-ltr {
animation: paperFlipToLtr 0.8s cubic-bezier(0.76, 0, 0.24, 1) forwards;
}
</style>
</head>
<body>
<!-- MIX: preloader (removed by js/mix.js once React has mounted) -->
<div class="mix-preloader" id="mix-preloader" aria-hidden="true">
<div class="mix-preloader__inner">
<p class="mix-preloader__word" data-mix-word>intelligence</p>
<p class="mix-preloader__count"><span data-mix-count>0</span></p>
</div>
<div class="mix-preloader__bar"><span data-mix-bar></span></div>
</div>
<div id="root"></div>
<!-- ═══════════════════════════════════════════════════════════
NOSCRIPT / CRAWLER FALLBACK
Full semantic content for search engines, AI crawlers, and
feed readers that do not execute JavaScript.
═══════════════════════════════════════════════════════════ -->
<noscript>
<style>
.ns-wrap { max-width: 860px; margin: 0 auto; padding: 48px 32px; font-family: Georgia, serif; color: #1A1815; background: #F4F0E8; line-height: 1.65; }
.ns-wrap h1 { font-size: 56px; font-weight: 300; letter-spacing: -2px; margin: 0 0 8px; }
.ns-wrap h2 { font-size: 28px; font-weight: 400; margin: 48px 0 12px; border-top: 1px solid #d8d1be; padding-top: 24px; }
.ns-wrap h3 { font-size: 18px; font-weight: 500; margin: 24px 0 8px; color: #0F6B73; }
.ns-wrap p { font-size: 17px; max-width: 65ch; margin: 0 0 16px; }
.ns-wrap ul { padding: 0 0 0 20px; margin: 0 0 16px; }
.ns-wrap li { font-size: 15px; margin-bottom: 6px; }
.ns-wrap a { color: #0F6B73; }
.ns-wrap .meta { font-family: monospace; font-size: 12px; color: #8a8473; letter-spacing: 0.08em; }
.ns-wrap .tag { display: inline-block; padding: 2px 8px; border: 1px solid #d8d1be; font-size: 12px; font-family: monospace; margin: 2px; }
.ns-fa { font-family: serif; direction: rtl; color: #595448; margin-top: 4px; font-size: 22px; }
</style>
<div class="ns-wrap" itemscope itemtype="https://schema.org/Person">
<p class="meta">PORTFOLIO · 2026 · arsamsabbagh.ir</p>
<h1 itemprop="name">Arsam Sabbagh</h1>
<p class="ns-fa" lang="fa">آرسام صباغ</p>
<p itemprop="jobTitle">Senior AI Researcher · LLM Engineer · Editor-in-Chief, Teknav · Web Designer (Coaxys)</p>
<p itemprop="description">Building, evaluating, and shipping intelligent systems at industrial scale — with a parallel practice in editorial web design under the name Coaxys. Based in <span itemprop="addressLocality">Tehran</span>, <span itemprop="addressCountry">Iran</span>.</p>
<p><a href="mailto:hello@arsamsabbagh.ir" itemprop="email">hello@arsamsabbagh.ir</a></p>
<h2>About</h2>
<p>Arsam Sabbagh is a senior AI researcher and LLM engineer with three-plus years building machine-learning systems for production. As Editor-in-Chief at Teknav, he shapes the Farsi-speaking world's most-read ML coverage — and continues to publish original research alongside it.</p>
<p>His core work sits at the intersection of LLM pipeline design, inference optimization, and quantitative evaluation of frontier models. Outside the lab he runs Coaxys, a small practice for frontend and brand work serving startups and tech companies.</p>
<ul>
<li>Senior AI Researcher · Editor-in-Chief, Teknav</li>
<li>Pre-founding technical contributor, Teknav</li>
<li>Data-centric AI · agentic systems · inference</li>
<li>Frontend &amp; brand under Coaxys</li>
<li>Go-to Farsi-language resource for ML engineers</li>
</ul>
<h2>Operating Principles</h2>
<h3>I. Evidence over enthusiasm.</h3>
<p>Benchmarks are not vibes. Ship the eval before you ship the model.</p>
<h3>II. Boring infrastructure, ambitious models.</h3>
<p>A model is only as good as the pipeline that feeds it and the harness that scores it.</p>
<h3>III. Persian is a first-class citizen.</h3>
<p>A tokenizer that drops half the alphabet is a bug, not a quirk. Build for the language you write in.</p>
<h3>IV. Editorial taste is technical taste.</h3>
<p>How a project reads — its docs, its UI, its naming — is how it survives the year.</p>
<h2>Expertise</h2>
<h3>Track A — AI &amp; LLM Engineering</h3>
<ul>
<li>Large language model pipeline design</li>
<li>Inference optimization &amp; throughput tuning</li>
<li>Quantitative evaluation of frontier models</li>
<li>Data engineering at industrial scale</li>
<li>Agentic systems &amp; deployment</li>
<li>Applied ML research</li>
</ul>
<h3>Track B — Web Development &amp; Design (as Coaxys)</h3>
<ul>
<li>Frontend architecture (React, Next.js)</li>
<li>UI/UX design &amp; rapid prototyping</li>
<li>Brand identity for tech products</li>
<li>Editorial type &amp; layout systems</li>
</ul>
<h2>Selected Projects</h2>
<h3>Frontier-Eval</h3>
<p>A reproducible harness for quantitative evaluation of frontier LLMs across reasoning, code, and long-context benchmarks.</p>
<span class="tag">Python</span><span class="tag">vLLM</span><span class="tag">Eval</span>
<h3>Pars-RAG</h3>
<p>A Persian-first retrieval-augmented generation stack with domain adapters for legal and medical corpora.</p>
<span class="tag">RAG</span><span class="tag">Persian NLP</span><span class="tag">FAISS</span>
<h3>Inference Lab</h3>
<p>Throughput and latency tuning toolkit — quantization, speculative decoding, and KV-cache strategies benchmarked side by side.</p>
<span class="tag">vLLM</span><span class="tag">TensorRT</span><span class="tag">Quantization</span>
<h3>AgentScript</h3>
<p>A lightweight planner-executor pattern for production agents, with structured tools and replayable traces.</p>
<span class="tag">Agents</span><span class="tag">TypeScript</span><span class="tag">OpenTelemetry</span>
<h2>Writing — Teknav</h2>
<p>28 long-form pieces on machine learning, LLM engineering, and applied research — read by the Farsi-speaking tech community.</p>
<ul>
<li><a href="https://teknav.ir/profile/@arsam-sabbagh">On Quantitative Evaluation of Frontier Models</a> — 18,420 views</li>
<li><a href="https://teknav.ir/profile/@arsam-sabbagh">Building a Persian-First RAG Stack</a> — 12,110 views</li>
<li><a href="https://teknav.ir/profile/@arsam-sabbagh">Speculative Decoding, Honestly</a> — 9,870 views</li>
<li><a href="https://teknav.ir/profile/@arsam-sabbagh">Agentic Systems Without the Cargo Cult</a> — 14,230 views</li>
<li><a href="https://teknav.ir/profile/@arsam-sabbagh">Inference Optimization: A Field Guide</a> — 16,708 views</li>
<li><a href="https://teknav.ir/profile/@arsam-sabbagh">Notes from the Frontier Lab</a> — 8,990 views</li>
</ul>
<h2>Stats</h2>
<ul>
<li>28 articles published</li>
<li>118,280 total views</li>
<li>4,789 reactions</li>
</ul>
<h2>Current Stack</h2>
<p><strong>Modeling:</strong> PyTorch, vLLM, TensorRT-LLM, HuggingFace, sglang, Outlines</p>
<p><strong>Data:</strong> DuckDB, Polars, Kafka, dbt, Postgres</p>
<p><strong>Eval &amp; Ops:</strong> lm-eval-harness, OpenTelemetry, Weights &amp; Biases, Modal, Grafana</p>
<p><strong>Web (Coaxys):</strong> Next.js, React 19, Tailwind, Framer Motion, Figma, MDX</p>
<h2>Contact</h2>
<p>Open to senior ML engineering roles, research collaborations, editorial commissions, and selective design engagements through Coaxys.</p>
<ul>
<li>GitHub: <a href="https://github.com/Coaxys-A" rel="me">github.com/Coaxys-A</a></li>
<li>Telegram: <a href="https://t.me/Coaxys" itemprop="url" rel="me">t.me/Coaxys</a></li>
<li>Instagram: <a href="https://instagram.com/_coaxys" rel="me">@_coaxys</a></li>
<li>Teknav: <a href="https://teknav.ir/profile/@arsam-sabbagh" rel="me">@arsam-sabbagh</a></li>
<li>Website: <a href="https://www.arsamsabbagh.ir" itemprop="url">arsamsabbagh.ir</a></li>
<li>Email: <a href="mailto:hello@arsamsabbagh.ir">hello@arsamsabbagh.ir</a></li>
</ul>
<p lang="fa" style="direction:rtl;font-family:serif;margin-top:48px;color:#8a8473;">
آرسام صباغ — پژوهشگر ارشد هوش مصنوعی و مهندس مدل‌های زبانی از تهران، ایران.<br/>
سردبیر تک‌نَو | طراح وب تحت نام کواکسیس<br/>
تخصص: طراحی خط لولهٔ مدل‌های زبانی، بهینه‌سازی استنتاج، ارزیابی کمّی مدل‌های پیشرو، پردازش زبان فارسی
</p>
</div>
</noscript>
<script src="vendor/lenis.min.js"></script>
<script src="vendor/react.development.js"></script>
<script src="vendor/react-dom.development.js"></script>
<script src="vendor/babel.min.js"></script>
<script type="text/babel">
// tweaks-panel.jsx
// Reusable Tweaks shell + form-control helpers.
//
// Owns the host protocol (listens for __activate_edit_mode / __deactivate_edit_mode,
// posts __edit_mode_available / __edit_mode_set_keys / __edit_mode_dismissed) so
// individual prototypes don't re-roll it. Ships a consistent set of controls so you
// don't hand-draw <input type="range">, segmented radios, steppers, etc.
//
// Usage (in an HTML file that loads React + Babel):
//
// const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
// "primaryColor": "#D97757",
// "palette": ["#D97757", "#29261b", "#f6f4ef"],
// "fontSize": 16,
// "density": "regular",
// "dark": false
// }/*EDITMODE-END*/;
//
// function App() {
// const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
// return (
// <div style={{ fontSize: t.fontSize, color: t.primaryColor }}>
// Hello
// <TweaksPanel>
// <TweakSection label="Typography" />
// <TweakSlider label="Font size" value={t.fontSize} min={10} max={32} unit="px"
// onChange={(v) => setTweak('fontSize', v)} />
// <TweakRadio label="Density" value={t.density}
// options={['compact', 'regular', 'comfy']}
// onChange={(v) => setTweak('density', v)} />
// <TweakSection label="Theme" />
// <TweakColor label="Primary" value={t.primaryColor}
// options={['#D97757', '#2A6FDB', '#1F8A5B', '#7A5AE0']}
// onChange={(v) => setTweak('primaryColor', v)} />
// <TweakColor label="Palette" value={t.palette}
// options={[['#D97757', '#29261b', '#f6f4ef'],
// ['#475569', '#0f172a', '#f1f5f9']]}
// onChange={(v) => setTweak('palette', v)} />
// <TweakToggle label="Dark mode" value={t.dark}
// onChange={(v) => setTweak('dark', v)} />
// </TweaksPanel>
// </div>
// );
// }
//
// ─────────────────────────────────────────────────────────────────────────────
const __TWEAKS_STYLE = `
.twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;
max-height:calc(100vh - 32px);display:flex;flex-direction:column;
transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right;
background:rgba(250,249,247,.78);color:#29261b;
-webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);
border:.5px solid rgba(255,255,255,.6);border-radius:14px;
box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);
font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}
.twk-hd{display:flex;align-items:center;justify-content:space-between;
padding:10px 8px 10px 14px;cursor:move;user-select:none}
.twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}
.twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);
width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}
.twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}
.twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;
overflow-y:auto;overflow-x:hidden;min-height:0;
scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}
.twk-body::-webkit-scrollbar{width:8px}
.twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}
.twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;
border:2px solid transparent;background-clip:content-box}
.twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25);
border:2px solid transparent;background-clip:content-box}
.twk-row{display:flex;flex-direction:column;gap:5px}
.twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}
.twk-lbl{display:flex;justify-content:space-between;align-items:baseline;
color:rgba(41,38,27,.72)}
.twk-lbl>span:first-child{font-weight:500}
.twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}
.twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
color:rgba(41,38,27,.45);padding:10px 0 0}
.twk-sect:first-child{padding-top:0}
.twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px;
border:.5px solid rgba(0,0,0,.1);border-radius:7px;
background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}
.twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}
select.twk-field{padding-right:22px;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='rgba(0,0,0,.5)' d='M0 0h10L5 6z'/></svg>");
background-repeat:no-repeat;background-position:right 8px center}
.twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;
border-radius:999px;background:rgba(0,0,0,.12);outline:none}
.twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
width:14px;height:14px;border-radius:50%;background:#fff;
border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
.twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;
background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
.twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;
background:rgba(0,0,0,.06);user-select:none}
.twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;
background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);
transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}
.twk-seg.dragging .twk-seg-thumb{transition:none}
.twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;
background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;
border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2;
overflow-wrap:anywhere}
.twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;
background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}
.twk-toggle[data-on="1"]{background:#34c759}
.twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
.twk-toggle[data-on="1"] i{transform:translateX(14px)}
.twk-num{display:flex;align-items:center;box-sizing:border-box;min-width:0;height:26px;padding:0 0 0 8px;
border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)}
.twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize;
user-select:none;padding-right:8px}
.twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent;
font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0;
outline:none;color:inherit;-moz-appearance:textfield}
.twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{
-webkit-appearance:none;margin:0}
.twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)}
.twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px;
background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default}
.twk-btn:hover{background:rgba(0,0,0,.88)}
.twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit}
.twk-btn.secondary:hover{background:rgba(0,0,0,.1)}
.twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;
border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;
background:transparent;flex-shrink:0}
.twk-swatch::-webkit-color-swatch-wrapper{padding:0}
.twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}
.twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px}
.twk-chips{display:flex;gap:6px}
.twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px;
padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default;
box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06);
transition:transform .12s cubic-bezier(.3,.7,.4,1),box-shadow .12s}
.twk-chip:hover{transform:translateY(-1px);
box-shadow:0 0 0 .5px rgba(0,0,0,.18),0 4px 10px rgba(0,0,0,.12)}
.twk-chip[data-on="1"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85),
0 2px 6px rgba(0,0,0,.15)}
.twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%;
display:flex;flex-direction:column;box-shadow:-1px 0 0 rgba(0,0,0,.1)}
.twk-chip>span>i{flex:1;box-shadow:0 -1px 0 rgba(0,0,0,.1)}
.twk-chip>span>i:first-child{box-shadow:none}
.twk-chip svg{position:absolute;top:6px;left:6px;width:13px;height:13px;
filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))}
`;
// ── useTweaks ───────────────────────────────────────────────────────────────
// Single source of truth for tweak values. setTweak persists via the host
// (__edit_mode_set_keys → host rewrites the EDITMODE block on disk).
function useTweaks(defaults) {
const [values, setValues] = React.useState(defaults);
// Accepts either setTweak('key', value) or setTweak({ key: value, ... }) so a
// useState-style call doesn't write a "[object Object]" key into the persisted
// JSON block.
const setTweak = React.useCallback((keyOrEdits, val) => {
const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null
? keyOrEdits : { [keyOrEdits]: val };
setValues((prev) => ({ ...prev, ...edits }));
window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*');
// Same-window signal so in-page listeners (deck-stage rail thumbnails)
// can react — the parent message only reaches the host, not peers.
window.dispatchEvent(new CustomEvent('tweakchange', { detail: edits }));
}, []);
return [values, setTweak];
}
// ── TweaksPanel ─────────────────────────────────────────────────────────────
// Floating shell. Registers the protocol listener BEFORE announcing
// availability — if the announce ran first, the host's activate could land
// before our handler exists and the toolbar toggle would silently no-op.
// The close button posts __edit_mode_dismissed so the host's toolbar toggle
// flips off in lockstep; the host echoes __deactivate_edit_mode back which
// is what actually hides the panel.
function TweaksPanel({ title = 'Tweaks', noDeckControls = false, children }) {
const [open, setOpen] = React.useState(false);
const dragRef = React.useRef(null);
// Auto-inject a rail toggle when a <deck-stage> is on the page. The
// toggle drives the deck's per-viewer _railVisible via window message;
// state is mirrored from the same localStorage key the deck reads so
// the control reflects reality across reloads. The mechanism is the
// message — authors who want custom placement can post it directly
// and pass noDeckControls to suppress this one.
const hasDeckStage = React.useMemo(
() => typeof document !== 'undefined' && !!document.querySelector('deck-stage'),
[],
);
// deck-stage enables its rail in connectedCallback, but this panel can
// mount before that element has upgraded. The initial read catches the
// common case; the listener covers mounting first. (Older deck-stage.js
// copies still wait for the host's __omelette_rail_enabled postMessage —
// same listener handles those.)
const [railEnabled, setRailEnabled] = React.useState(
() => hasDeckStage && !!document.querySelector('deck-stage')?._railEnabled,
);
React.useEffect(() => {
if (!hasDeckStage || railEnabled) return undefined;
const onMsg = (e) => {
if (e.data && e.data.type === '__omelette_rail_enabled') setRailEnabled(true);
};
window.addEventListener('message', onMsg);
return () => window.removeEventListener('message', onMsg);
}, [hasDeckStage, railEnabled]);
const [railVisible, setRailVisible] = React.useState(() => {
try { return localStorage.getItem('deck-stage.railVisible') !== '0'; } catch (e) { return true; }
});
const toggleRail = (on) => {
setRailVisible(on);
window.postMessage({ type: '__deck_rail_visible', on }, '*');
};
const offsetRef = React.useRef({ x: 16, y: 16 });
const PAD = 16;
const clampToViewport = React.useCallback(() => {
const panel = dragRef.current;
if (!panel) return;
const w = panel.offsetWidth, h = panel.offsetHeight;
const maxRight = Math.max(PAD, window.innerWidth - w - PAD);
const maxBottom = Math.max(PAD, window.innerHeight - h - PAD);
offsetRef.current = {
x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)),
y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)),
};
panel.style.right = offsetRef.current.x + 'px';
panel.style.bottom = offsetRef.current.y + 'px';
}, []);
React.useEffect(() => {
if (!open) return;
clampToViewport();
if (typeof ResizeObserver === 'undefined') {
window.addEventListener('resize', clampToViewport);
return () => window.removeEventListener('resize', clampToViewport);
}
const ro = new ResizeObserver(clampToViewport);
ro.observe(document.documentElement);
return () => ro.disconnect();
}, [open, clampToViewport]);
React.useEffect(() => {
const onMsg = (e) => {
const t = e?.data?.type;
if (t === '__activate_edit_mode') setOpen(true);
else if (t === '__deactivate_edit_mode') setOpen(false);
};
window.addEventListener('message', onMsg);
window.parent.postMessage({ type: '__edit_mode_available' }, '*');
return () => window.removeEventListener('message', onMsg);
}, []);
const dismiss = () => {
setOpen(false);
window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*');
};
const onDragStart = (e) => {
const panel = dragRef.current;
if (!panel) return;
const r = panel.getBoundingClientRect();
const sx = e.clientX, sy = e.clientY;
const startRight = window.innerWidth - r.right;
const startBottom = window.innerHeight - r.bottom;
const move = (ev) => {
offsetRef.current = {
x: startRight - (ev.clientX - sx),
y: startBottom - (ev.clientY - sy),
};
clampToViewport();
};
const up = () => {
window.removeEventListener('mousemove', move);
window.removeEventListener('mouseup', up);
};
window.addEventListener('mousemove', move);
window.addEventListener('mouseup', up);
};
if (!open) return null;
return (
<>
<style>{__TWEAKS_STYLE}</style>
<div ref={dragRef} className="twk-panel" data-noncommentable=""
style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>
<div className="twk-hd" onMouseDown={onDragStart}>
<b>{title}</b>
<button className="twk-x" aria-label="Close tweaks"
onMouseDown={(e) => e.stopPropagation()}
onClick={dismiss}>✕</button>
</div>
<div className="twk-body">
{children}
{hasDeckStage && railEnabled && !noDeckControls && (
<TweakSection label="Deck">
<TweakToggle label="Thumbnail rail" value={railVisible} onChange={toggleRail} />
</TweakSection>
)}
</div>
</div>
</>
);
}
// ── Layout helpers ──────────────────────────────────────────────────────────
function TweakSection({ label, children }) {
return (
<>
<div className="twk-sect">{label}</div>
{children}
</>
);
}
function TweakRow({ label, value, children, inline = false }) {
return (
<div className={inline ? 'twk-row twk-row-h' : 'twk-row'}>
<div className="twk-lbl">
<span>{label}</span>
{value != null && <span className="twk-val">{value}</span>}
</div>
{children}
</div>
);
}
// ── Controls ────────────────────────────────────────────────────────────────
function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) {
return (
<TweakRow label={label} value={`${value}${unit}`}>
<input type="range" className="twk-slider" min={min} max={max} step={step}
value={value} onChange={(e) => onChange(Number(e.target.value))} />
</TweakRow>
);
}
function TweakToggle({ label, value, onChange }) {
return (
<div className="twk-row twk-row-h">
<div className="twk-lbl"><span>{label}</span></div>
<button type="button" className="twk-toggle" data-on={value ? '1' : '0'}
role="switch" aria-checked={!!value}
onClick={() => onChange(!value)}><i /></button>
</div>
);
}
function TweakRadio({ label, value, options, onChange }) {
const trackRef = React.useRef(null);
const [dragging, setDragging] = React.useState(false);
// The active value is read by pointer-move handlers attached for the lifetime
// of a drag — ref it so a stale closure doesn't fire onChange for every move.
const valueRef = React.useRef(value);
valueRef.current = value;
// Segments wrap mid-word once per-segment width runs out. The track is
// ~248px (280 panel − 28 body pad − 4 seg pad), each button loses 12px
// to its own padding, and 11.5px system-ui averages ~6.3px/char — so 2
// options fit ~16 chars each, 3 fit ~10. Past that (or >3 options), fall
// back to a dropdown rather than wrap.
const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length;
const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0);
const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] ?? 0);
if (!fitsAsSegments) {
// <select> emits strings — map back to the original option value so the
// fallback stays type-preserving (numbers, booleans) like the segment path.
const resolve = (s) => {
const m = options.find((o) => String(typeof o === 'object' ? o.value : o) === s);
return m === undefined ? s : typeof m === 'object' ? m.value : m;
};
return <TweakSelect label={label} value={value} options={options}
onChange={(s) => onChange(resolve(s))} />;
}
const opts = options.map((o) => (typeof o === 'object' ? o : { value: o, label: o }));
const idx = Math.max(0, opts.findIndex((o) => o.value === value));
const n = opts.length;
const segAt = (clientX) => {
const r = trackRef.current.getBoundingClientRect();
const inner = r.width - 4;
const i = Math.floor(((clientX - r.left - 2) / inner) * n);
return opts[Math.max(0, Math.min(n - 1, i))].value;
};
const onPointerDown = (e) => {
setDragging(true);
const v0 = segAt(e.clientX);
if (v0 !== valueRef.current) onChange(v0);
const move = (ev) => {
if (!trackRef.current) return;
const v = segAt(ev.clientX);
if (v !== valueRef.current) onChange(v);
};
const up = () => {
setDragging(false);
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', up);
};
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
};
return (
<TweakRow label={label}>
<div ref={trackRef} role="radiogroup" onPointerDown={onPointerDown}
className={dragging ? 'twk-seg dragging' : 'twk-seg'}>
<div className="twk-seg-thumb"
style={{ left: `calc(2px + ${idx} * (100% - 4px) / ${n})`,
width: `calc((100% - 4px) / ${n})` }} />
{opts.map((o) => (
<button key={o.value} type="button" role="radio" aria-checked={o.value === value}>
{o.label}
</button>
))}
</div>
</TweakRow>
);
}
function TweakSelect({ label, value, options, onChange }) {
return (
<TweakRow label={label}>
<select className="twk-field" value={value} onChange={(e) => onChange(e.target.value)}>
{options.map((o) => {
const v = typeof o === 'object' ? o.value : o;
const l = typeof o === 'object' ? o.label : o;
return <option key={v} value={v}>{l}</option>;
})}
</select>
</TweakRow>
);
}
function TweakText({ label, value, placeholder, onChange }) {
return (
<TweakRow label={label}>
<input className="twk-field" type="text" value={value} placeholder={placeholder}
onChange={(e) => onChange(e.target.value)} />
</TweakRow>
);
}
function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) {
const clamp = (n) => {
if (min != null && n < min) return min;
if (max != null && n > max) return max;
return n;
};
const startRef = React.useRef({ x: 0, val: 0 });
const onScrubStart = (e) => {
e.preventDefault();
startRef.current = { x: e.clientX, val: value };
const decimals = (String(step).split('.')[1] || '').length;
const move = (ev) => {
const dx = ev.clientX - startRef.current.x;
const raw = startRef.current.val + dx * step;
const snapped = Math.round(raw / step) * step;
onChange(clamp(Number(snapped.toFixed(decimals))));
};
const up = () => {
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', up);
};
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
};
return (
<div className="twk-num">
<span className="twk-num-lbl" onPointerDown={onScrubStart}>{label}</span>
<input type="number" value={value} min={min} max={max} step={step}
onChange={(e) => onChange(clamp(Number(e.target.value)))} />
{unit && <span className="twk-num-unit">{unit}</span>}
</div>
);
}
// Relative-luminance contrast pick — checkmarks drawn over a swatch need to
// read on both #111 and #fafafa without per-option configuration. Hex input
// only (#rgb / #rrggbb); named or rgb()/hsl() colors fall through to "light".
function __twkIsLight(hex) {
const h = String(hex).replace('#', '');
const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0');
const n = parseInt(x.slice(0, 6), 16);
if (Number.isNaN(n)) return true;
const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
return r * 299 + g * 587 + b * 114 > 148000;
}
const __TwkCheck = ({ light }) => (
<svg viewBox="0 0 14 14" aria-hidden="true">
<path d="M3 7.2 5.8 10 11 4.2" fill="none" strokeWidth="2.2"
strokeLinecap="round" strokeLinejoin="round"
stroke={light ? 'rgba(0,0,0,.78)' : '#fff'} />
</svg>
);
// TweakColor — curated color/palette picker. Each option is either a single
// hex string or an array of 1-5 hex strings; the card adapts — a lone color
// renders solid, a palette renders colors[0] as the hero (left ~2/3) with the
// rest stacked in a sharp column on the right. onChange emits the
// option in the shape it was passed (string stays string, array stays array).
// Without options it falls back to the native color input for back-compat.
function TweakColor({ label, value, options, onChange }) {
if (!options || !options.length) {
return (
<div className="twk-row twk-row-h">
<div className="twk-lbl"><span>{label}</span></div>
<input type="color" className="twk-swatch" value={value}
onChange={(e) => onChange(e.target.value)} />
</div>
);
}
// Native <input type=color> emits lowercase hex per the HTML spec, so
// compare case-insensitively. String() guards JSON.stringify(undefined),
// which returns the primitive undefined (no .toLowerCase).
const key = (o) => String(JSON.stringify(o)).toLowerCase();
const cur = key(value);
return (
<TweakRow label={label}>
<div className="twk-chips" role="radiogroup">
{options.map((o, i) => {
const colors = Array.isArray(o) ? o : [o];
const [hero, ...rest] = colors;
const sup = rest.slice(0, 4);
const on = key(o) === cur;
return (
<button key={i} type="button" className="twk-chip" role="radio"
aria-checked={on} data-on={on ? '1' : '0'}
aria-label={colors.join(', ')} title={colors.join(' · ')}
style={{ background: hero }}
onClick={() => onChange(o)}>
{sup.length > 0 && (
<span>
{sup.map((c, j) => <i key={j} style={{ background: c }} />)}
</span>
)}
{on && <__TwkCheck light={__twkIsLight(hero)} />}
</button>
);
})}
</div>
</TweakRow>
);
}
function TweakButton({ label, onClick, secondary = false }) {
return (
<button type="button" className={secondary ? 'twk-btn secondary' : 'twk-btn'}
onClick={onClick}>{label}</button>
);
}
Object.assign(window, {
useTweaks, TweaksPanel, TweakSection, TweakRow,
TweakSlider, TweakToggle, TweakRadio, TweakSelect,
TweakText, TweakNumber, TweakColor, TweakButton,
});
// Bilingual copy + structured data
const CONTENT = {
en: {
dir: 'ltr',
nav: {
links: [
{ id: 'about', label: 'About' },
{ id: 'expertise', label: 'Expertise' },
{ id: 'projects', label: 'Projects' },
{ id: 'writing', label: 'Writing' },
{ id: 'contact', label: 'Contact' },
],
langLabel: 'فارسی',
},
hero: {
kicker: 'Portfolio · 2026',
nameLine1: 'Arsam',
nameLine2: 'Sabbagh',
tagline: ['AI Researcher.', 'LLM Engineer.', 'Web Designer.'],
lede:
'Building, evaluating, and shipping intelligent systems at industrial scale — with a parallel practice in editorial web design under the name Coaxys.',
ctaA: 'View My Work',
ctaB: 'Read My Writing',
scroll: 'Scroll',
meta: [
{ k: 'Based in', v: 'Tehran, Iran' },
{ k: 'Editor-in-Chief', v: 'Teknav.ir' },
{ k: 'Design alias', v: 'Coaxys' },
],
},
about: {
num: '01',
title: ['A senior researcher with', 'an editor\u2019s eye.'],
titleEm: 'editor\u2019s eye.',
photoAlt: 'Portrait of Arsam Sabbagh',
placeholder: 'PORTRAIT · 4:5',
body: [
'Arsam Sabbagh is a senior AI researcher and LLM engineer with three-plus years building machine-learning systems for production. As Editor-in-Chief at Teknav, he shapes the Farsi-speaking world\u2019s most-read ML coverage — and continues to publish original research alongside it.',
'His core work sits at the intersection of LLM pipeline design, inference optimization, and quantitative evaluation of frontier models. Outside the lab he runs Coaxys, a small practice for frontend and brand work serving startups and tech companies who want their craft to match their ambition.',
],
bullets: [
'Senior AI Researcher · Editor-in-Chief, Teknav',
'Pre-founding technical contributor, Teknav',
'Data-centric AI · agentic systems · inference',
'Frontend & brand under Coaxys',
'Go-to Farsi-language resource for ML engineers',
],
},
expertise: {
num: '03',
title: ['Two tracks,', 'one operator.'],
titleEm: 'one operator.',
tracks: [
{
tag: 'Track A',
name: 'AI & LLM Engineering',
accent: 'accent',
items: [
'Large language model pipeline design',
'Inference optimization & throughput tuning',
'Quantitative evaluation of frontier models',
'Data engineering at industrial scale',
'Agentic systems & deployment',
'Applied ML research',
],
},
{
tag: 'Track B',
name: 'Web Development & Design',
subname: 'as Coaxys',
accent: 'terra',
items: [
'Frontend architecture (React, Next.js)',
'UI/UX design & rapid prototyping',
'Brand identity for tech products',
'Web design for startups & companies',
'Performance-focused development',
'Editorial type & layout systems',
],
},
],
},
projects: {
num: '04',
title: ['Selected', 'work.'],
titleEm: 'work.',
tabs: [
{ id: 'ai', label: 'AI & Research' },
{ id: 'web', label: 'Web & Design' },
],
cta: 'View case',
},
writing: {
num: '05',
title: ['Writing for', 'Teknav.'],
titleEm: 'Teknav.',
lede:
'Twenty-eight long-form pieces on machine learning, LLM engineering, and applied research — read by the Farsi-speaking tech community.',
featured: 'Featured',
readMore: 'Read on Teknav',
viewAll: 'View all 28 articles',
cols: { title: 'Article', date: 'Date', views: 'Views', reactions: '★' },
},
stats: {
kicker: 'On the record at Teknav',
items: [
{ n: 28, suffix: '', label: 'Articles published' },
{ n: 118280, suffix: '', label: 'Total views' },
{ n: 4789, suffix: '', label: 'Reactions' },
],
},
contact: {
num: '06',
title: ['Let\u2019s work', 'together.'],
titleEm: 'together.',
lede:
'Open to senior ML engineering roles, research collaborations, editorial commissions, and selective design engagements through Coaxys.',
directHint: 'Direct ·',
socials: [
{ icon: 'telegram', label: 'Telegram', handle: 't.me/Coaxys', href: 'https://t.me/Coaxys' },
{ icon: 'instagram', label: 'Instagram', handle: '@_coaxys', href: 'https://instagram.com/_coaxys' },
{ icon: 'github', label: 'GitHub', handle: 'Coaxys-A', href: 'https://github.com/Coaxys-A' },
{ icon: 'feather', label: 'Teknav', handle: '@arsam-sabbagh', href: 'https://teknav.ir/profile/@arsam-sabbagh' },
{ icon: 'globe', label: 'Domain', handle: 'arsamsabbagh.ir', href: 'https://arsamsabbagh.ir' },
],
},
footer: {
sig: 'Designed & built by Coaxys — arsamsabbagh.ir',
year: '© 2026 Arsam Sabbagh',
colophon: 'Set in Geist & Instrument Serif',
},
nav_status: { available: 'Available', city: 'Tehran' },
marquee: ['LLM Pipelines', 'Inference Optimization', 'Frontier-Model Eval', 'Persian NLP', 'Agentic Systems', 'Editor-in-Chief @ Teknav', 'Frontend Engineering', 'Editorial Web Design', 'Brand Systems', 'Data-Centric AI'],
testimonials: {
num: '02',
title: ['Trusted by', 'peers and partners.'],
titleEm: 'peers and partners.',
items: [
{ quote: 'Arsam has a rare ability to move between rigorous benchmark design and production-grade inference work without losing sight of either. His eval frameworks changed how we ship models.', name: 'Kian Mirzaei', role: 'ML Engineer, Sharif AI Lab' },
{ quote: 'Working with Arsam on Teknav was formative. He set an editorial standard that made technical writing in Farsi feel world-class — not a translation, but an original.', name: 'Dana Ahmadi', role: 'Founding Engineer, Teknav' },
{ quote: 'The frontend Arsam delivered for our launch was immaculate. Every interaction, every type choice — it felt like working with someone who reads code the way an editor reads copy.', name: 'Sina Rajabi', role: 'Co-Founder, Novaspark' },
{ quote: 'Most people treat Persian NLP as an afterthought. Arsam treats it as a first-class problem, and his tokenizer work proves it — half the token waste gone, same model budget.', name: 'Parisa Lotfi', role: 'Research Scientist, TU Berlin' },
],
},
principles: {
num: '',
title: ['Operating', 'principles.'],
titleEm: 'principles.',
items: [
{ n: 'I', title: 'Evidence over enthusiasm.', body: 'Benchmarks are not vibes. Ship the eval before you ship the model.' },
{ n: 'II', title: 'Boring infrastructure, ambitious models.', body: 'A model is only as good as the pipeline that feeds it and the harness that scores it.' },
{ n: 'III', title: 'Persian is a first-class citizen.', body: 'A tokenizer that drops half the alphabet is a bug, not a quirk. Build for the language you write in.' },
{ n: 'IV', title: 'Editorial taste is technical taste.', body: 'How a project reads — its docs, its UI, its naming — is how it survives the year.' },
],
},
toolkit: {
kicker: '— Specimen / Toolkit',
title: 'The current stack.',
groups: [
{ label: 'Modeling', items: ['PyTorch', 'vLLM', 'TensorRT-LLM', 'HuggingFace', 'sglang', 'Outlines'] },
{ label: 'Data', items: ['DuckDB', 'Polars', 'Kafka', 'dbt', 'Postgres'] },
{ label: 'Eval & Ops', items: ['lm-eval-harness', 'OpenTelemetry', 'Weights & Biases', 'Modal', 'Grafana'] },
{ label: 'Web (Coaxys)', items: ['Next.js', 'React 19', 'Tailwind', 'Framer Motion', 'Figma', 'MDX'] },
],
note: 'Pinned, opinionated, and updated when the receipts come in.',
},
now: {
kicker: '/now',
title: 'Currently',
titleEm: 'on the bench.',
items: [
{ k: 'Shipping', v: 'A Persian-first evaluation harness for long-context LLMs.' },
{ k: 'Writing', v: '"On the half-life of benchmarks" — drafting for Teknav, Issue 06.' },
{ k: 'Reading', v: 'The Phi-3 technical report, again.' },
{ k: 'Listening', v: 'Brian Eno — Music for Airports (working playlist).' },
],
asof: 'As of May 2026',
},
cmdk: { hint: 'Press', key: '⌘ K', label: 'for the index', placeholder: 'Type a section, theme, or language\u2026' },
},
fa: {
dir: 'rtl',
nav: {
links: [
{ id: 'about', label: 'درباره' },
{ id: 'expertise', label: 'تخصص' },
{ id: 'projects', label: 'پروژه‌ها' },
{ id: 'writing', label: 'نوشته‌ها' },
{ id: 'contact', label: 'تماس' },
],
langLabel: 'English',
},
hero: {
kicker: 'نمونه‌کار · ۱۴۰۵',
nameLine1: 'آرسام',
nameLine2: 'صباغ',
tagline: ['پژوهشگر هوش مصنوعی.', 'مهندس مدل‌های زبانی.', 'طراح وب.'],
lede:
'ساخت، ارزیابی و استقرار سامانه‌های هوشمند در مقیاس صنعتی — به همراه یک عملِ موازی در طراحی وبِ تحت برند کواکسیس.',
ctaA: 'مشاهدهٔ پروژه‌ها',
ctaB: 'خواندن نوشته‌ها',
scroll: 'پایین',
meta: [
{ k: 'مستقر در', v: 'تهران، ایران' },
{ k: 'سردبیر', v: 'تک‌نَو' },
{ k: 'نام مستعار طراحی', v: 'کواکسیس' },
],
},
about: {
num: '۰۱',
title: ['پژوهشگری ارشد', 'با چشمِ یک سردبیر.'],
titleEm: 'با چشمِ یک سردبیر.',
placeholder: 'پرتره · ۴:۵',
body: [
'آرسام صباغ پژوهشگر ارشد هوش مصنوعی و مهندس مدل‌های زبانی است با بیش از سه سال تجربه در ساخت سامانه‌های یادگیری ماشین در محیط تولید. به‌عنوان سردبیر تک‌نَو، پرخواننده‌ترین پوشش یادگیری ماشین در جهان فارسی‌زبان را شکل می‌دهد و در کنار آن همچنان پژوهش اصیل منتشر می‌کند.',
'هستهٔ کار او در تلاقی طراحیِ خطوط لولهٔ مدل‌های زبانی، بهینه‌سازی استنتاج، و ارزیابی کمّی مدل‌های پیشرو قرار دارد. بیرون از آزمایشگاه، استودیوی کوچک کواکسیس را اداره می‌کند: کار فرانت‌اند و برندینگ برای استارتاپ‌ها و شرکت‌هایی که می‌خواهند صنعت‌گری‌شان هم‌قدِ بلندپروازی‌شان باشد.',
],
bullets: [
'پژوهشگر ارشد · سردبیر تک‌نَو',
'مشارکت‌کنندهٔ فنی پیش از تأسیس، تک‌نَو',
'هوش مصنوعی داده‌محور · سامانه‌های عامل‌محور · استنتاج',
'فرانت‌اند و برند تحت کواکسیس',
'مرجع فارسی‌زبان برای مهندسان یادگیری ماشین',
],
},
expertise: {
num: '۰۳',
title: ['دو مسیر،', 'یک اپراتور.'],
titleEm: 'یک اپراتور.',
tracks: [
{
tag: 'مسیر الف',
name: 'مهندسی هوش مصنوعی و مدل‌های زبانی',
accent: 'accent',
items: [
'طراحی خط لولهٔ مدل‌های زبانی بزرگ',
'بهینه‌سازی استنتاج و تنظیم گذردهی',
'ارزیابی کمّی مدل‌های پیشرو',
'مهندسی داده در مقیاس صنعتی',
'سامانه‌های عامل‌محور و استقرار',
'پژوهش کاربردی یادگیری ماشین',
],
},
{
tag: 'مسیر ب',
name: 'توسعه و طراحی وب',
subname: 'تحت کواکسیس',
accent: 'terra',
items: [
'معماری فرانت‌اند (React، Next.js)',
'طراحی UI/UX و نمونه‌سازی سریع',
'هویت برند برای محصولات فناورانه',
'طراحی وب برای استارتاپ‌ها و شرکت‌ها',
'توسعهٔ کارایی‌محور',
'سیستم‌های تایپوگرافی و لی‌اوت',
],
},
],
},
projects: {
num: '۰۴',
title: ['کارهای', 'منتخب.'],
titleEm: 'منتخب.',
tabs: [
{ id: 'ai', label: 'هوش مصنوعی و پژوهش' },
{ id: 'web', label: 'وب و طراحی' },
],
cta: 'مشاهدهٔ پروژه',
},
writing: {
num: '۰۵',
title: ['نوشتن برای', 'تک‌نَو.'],
titleEm: 'تک‌نَو.',
lede: 'بیست‌وهشت یادداشت بلند دربارهٔ یادگیری ماشین، مهندسی مدل‌های زبانی و پژوهش کاربردی — برای جامعهٔ فنی فارسی‌زبان.',
featured: 'برجسته',
readMore: 'خواندن در تک‌نَو',
viewAll: 'مشاهدهٔ هر ۲۸ مقاله',
cols: { title: 'مقاله', date: 'تاریخ', views: 'بازدید', reactions: '★' },
},
stats: {
kicker: 'در تک‌نَو',
items: [
{ n: 28, suffix: '', label: 'مقالهٔ منتشرشده' },
{ n: 118280, suffix: '', label: 'بازدید کل' },
{ n: 4789, suffix: '', label: 'واکنش' },
],
},
contact: {
num: '۰۶',
title: ['بیایید با هم', 'کار کنیم.'],
titleEm: 'کار کنیم.',
lede: 'پذیرای نقش‌های ارشد مهندسی یادگیری ماشین، همکاری‌های پژوهشی، سفارش‌های سرمقاله و پروژه‌های منتخب طراحی از طریق کواکسیس.',
directHint: 'مستقیم ·',
socials: [
{ icon: 'telegram', label: 'تلگرام', handle: 't.me/Coaxys', href: 'https://t.me/Coaxys' },
{ icon: 'instagram', label: 'اینستاگرام', handle: '@_coaxys', href: 'https://instagram.com/_coaxys' },
{ icon: 'github', label: 'گیت‌هاب', handle: 'Coaxys-A', href: 'https://github.com/Coaxys-A' },
{ icon: 'feather', label: 'تک‌نَو', handle: '@arsam-sabbagh', href: 'https://teknav.ir/profile/@arsam-sabbagh' },
{ icon: 'globe', label: 'دامنه', handle: 'arsamsabbagh.ir', href: 'https://arsamsabbagh.ir' },
],
},
footer: {
sig: 'طراحی و توسعه توسط کواکسیس — arsamsabbagh.ir',
year: '© ۱۴۰۵ آرسام صباغ',
colophon: 'ست‌شده با Geist، Instrument Serif و وزیرمتن',
},
nav_status: { available: 'در دسترس', city: 'تهران' },
marquee: ['خطوط لولهٔ مدل‌های زبانی', 'بهینه‌سازی استنتاج', 'ارزیابی مدل‌های پیشرو', 'پردازش زبان فارسی', 'سامانه‌های عامل‌محور', 'سردبیر تک‌نَو', 'مهندسی فرانت‌اند', 'طراحی وب سرمقاله‌ای', 'سیستم‌های برند', 'هوش مصنوعی داده‌محور'],
testimonials: {
num: '۰۲',
title: ['اعتمادِ', 'همکاران و شرکا.'],
titleEm: 'همکاران و شرکا.',
items: [
{ quote: 'آرسام کمتر کسی است که بتواند بین طراحیِ دقیقِ بنچمارک و کارِ استنتاج در سطح تولید جابه‌جا شود، بدون اینکه یکی را فدای دیگری کند. فریمورک‌های ارزیابی‌اش روشِ تحویلِ مدل‌هایمان را عوض کرد.', name: 'کیان میرزایی', role: 'مهندس یادگیری ماشین، آزمایشگاه هوش مصنوعی شریف' },
{ quote: 'همکاری با آرسام در تک‌نَو شکل‌دهنده بود. او استاندارد سرمقاله‌ای تعیین کرد که نوشتن فنی به فارسی را در سطح جهانی نشان داد — نه ترجمه، بلکه اصل.', name: 'دانا احمدی', role: 'مهندس بنیان‌گذار، تک‌نَو' },
{ quote: 'فرانت‌اندی که آرسام برای راه‌اندازی ما تحویل داد بی‌نقص بود. هر تعامل، هر انتخابِ تایپ — انگار با کسی کار می‌کردی که کد را مثل سردبیرِ متن می‌خواند.', name: 'سینا رجبی', role: 'هم‌بنیان‌گذار، نوااسپارک' },
{ quote: 'بیشتر آدم‌ها پردازش زبان فارسی را به‌عنوان یک مسئلهٔ حاشیه‌ای می‌بینند. آرسام آن را مسئله‌ای درجه‌یک می‌داند، و کارِ توکنایزرش ثابتش می‌کند — نیمی از هدررفت توکن حذف شده، بودجهٔ مدل ثابت.', name: 'پریسا لطفی', role: 'پژوهشگر، دانشگاه فنی برلین' },
],
},
principles: {
num: '',
title: ['اصولِ', 'کارکردن.'],
titleEm: 'کارکردن.',
items: [
{ n: '۱', title: 'شواهد بر شور و شوق مقدم است.', body: 'بنچمارک، حس و حال نیست. ارزیابی را پیش از مدل تحویل بده.' },
{ n: '۲', title: 'زیرساختِ کسل‌کننده، مدلِ بلندپروازانه.', body: 'مدل دقیقاً به اندازهٔ خط لوله‌ای که تغذیه‌اش می‌کند و هارنسی که نمره‌اش می‌دهد خوب است.' },
{ n: '۳', title: 'فارسی شهروند درجه‌یک است.', body: 'توکنایزری که نیمی از الفبا را می‌اندازد، اشکال است نه ویژگی. برای زبانی بساز که با آن می‌نویسی.' },
{ n: '۴', title: 'سلیقهٔ سرمقاله‌ای، سلیقهٔ فنی است.', body: 'این‌که یک پروژه چطور خوانده می‌شود — مستنداتش، رابطش، نام‌گذاری‌اش — همان چیزی است که آن را زنده نگه می‌دارد.' },
],
},
toolkit: {
kicker: '— نمونه / جعبه‌ابزار',
title: 'استک فعلی.',
groups: [
{ label: 'مدل‌سازی', items: ['PyTorch', 'vLLM', 'TensorRT-LLM', 'HuggingFace', 'sglang', 'Outlines'] },
{ label: 'داده', items: ['DuckDB', 'Polars', 'Kafka', 'dbt', 'Postgres'] },
{ label: 'ارزیابی و عملیات', items: ['lm-eval-harness', 'OpenTelemetry', 'Weights & Biases', 'Modal', 'Grafana'] },
{ label: 'وب (کواکسیس)', items: ['Next.js', 'React 19', 'Tailwind', 'Framer Motion', 'Figma', 'MDX'] },
],
note: 'پین‌شده، با نظرِ روشن، و وقتی شواهد تازه برسد به‌روز می‌شود.',
},
now: {
kicker: '/now',
title: 'اکنون',
titleEm: 'روی میز.',
items: [
{ k: 'در حال ساختن', v: 'یک هارنس ارزیابی فارسی‌محور برای مدل‌های زبانیِ بافت‌بلند.' },
{ k: 'در حال نوشتن', v: '«دربارهٔ نیمه‌عمرِ بنچمارک‌ها» — پیش‌نویس برای تک‌نَو، شمارهٔ ۶.' },
{ k: 'در حال خواندن', v: 'گزارش فنی Phi-3، دوباره.' },
{ k: 'در حال شنیدن', v: 'برایان اینو — Music for Airports.' },
],
asof: 'تا اردیبهشت ۱۴۰۵',
},
cmdk: { hint: 'کلید', key: '⌘ K', label: 'برای فهرست', placeholder: 'یک بخش، تم یا زبان تایپ کنید\u2026' },
},
};
// Projects (language-neutral data; titles localized lightly)
const PROJECTS = {
ai: [
{
title: 'Frontier-Eval',
desc: 'A reproducible harness for quantitative evaluation of frontier LLMs across reasoning, code, and long-context benchmarks.',
tags: ['Python', 'vLLM', 'Eval'],
year: '2025',
role: 'Lead researcher',
url: 'https://github.com/Coaxys-A',
},
{
title: 'Pars-RAG',
desc: 'A Persian-first retrieval-augmented generation stack with domain adapters for legal and medical corpora.',
tags: ['RAG', 'Persian NLP', 'FAISS'],
year: '2025',
role: 'Engineer + author',
url: 'https://github.com/Coaxys-A',
},
{
title: 'Inference Lab',
desc: 'Throughput and latency tuning toolkit — quantization, speculative decoding, and KV-cache strategies benchmarked side by side.',
tags: ['vLLM', 'TensorRT', 'Quantization'],
year: '2024',
role: 'Sole author',
url: 'https://github.com/Coaxys-A',
},
{
title: 'AgentScript',
desc: 'A lightweight planner-executor pattern for production agents, with structured tools and replayable traces.',
tags: ['Agents', 'TypeScript', 'OpenTelemetry'],
year: '2024',
role: 'Architect',
url: 'https://github.com/Coaxys-A',
},
{
title: 'Industrial Data Spine',
desc: 'Streaming feature store and labeling pipeline for an industrial customer — billions of events per month.',
tags: ['Kafka', 'dbt', 'Feature store'],
year: '2023',
role: 'Tech lead',
url: 'https://github.com/Coaxys-A',
},
{
title: 'LLM-Ops Playbook',
desc: 'Open notes on deploying, monitoring, and rolling back LLM-powered features without breaking the product.',
tags: ['LLMOps', 'Eval', 'Observability'],
year: '2024',
role: 'Author',
url: 'https://teknav.ir/profile/@arsam-sabbagh',
},
],
web: [
{
title: 'Teknav Editorial',
desc: 'Long-form editorial system for the Teknav publication — bilingual typography, dense data layouts, and a careful reading rhythm.',
tags: ['Next.js', 'MDX', 'Editorial'],
year: '2025',
role: 'Design + build',
url: 'https://teknav.ir',
},
{
title: 'Studio Folio',
desc: 'A modular brand site for a Tehran-based design studio — granular case studies, custom motion, full RTL.',
tags: ['React', 'Framer Motion', 'RTL'],
year: '2025',
role: 'Coaxys',
url: 'https://github.com/Coaxys-A',
},
{
title: 'Hypha Health',
desc: 'Marketing and onboarding for a health-tech startup. Crisp medical typography and a careful patient flow.',
tags: ['Next.js', 'Tailwind', 'Brand'],
year: '2024',
role: 'Coaxys',
url: 'https://instagram.com/_coaxys',
},
{
title: 'Quanta Labs',
desc: 'Identity and microsite for an AI research lab. Restrained type, generous space, and a quiet motion language.',
tags: ['Brand', 'Microsite', 'Type'],
year: '2024',
role: 'Coaxys',
url: 'https://instagram.com/_coaxys',
},
{
title: 'Arman Marketplace',
desc: 'High-performance storefront with Persian-aware search and a custom checkout reducing drop-off by 22%.',
tags: ['E-commerce', 'Persian', 'Perf'],
year: '2023',
role: 'Coaxys lead',
url: 'https://instagram.com/_coaxys',
},
{
title: 'Personal · v6',
desc: 'This site. Built as a slow, deliberate exercise in editorial restraint and bilingual layout.',
tags: ['Editorial', 'Bilingual', 'Calm UI'],
year: '2026',
role: 'Self',
url: 'https://www.arsamsabbagh.ir',
},
],
};
const ARTICLES = [
{
title: 'On Quantitative Evaluation of Frontier Models',
titleFa: 'دربارهٔ ارزیابی کمّی مدل‌های پیشرو',
date: 'Apr 2025', dateFa: 'فروردین ۱۴۰۴',
views: 18420, reactions: 612, featured: true,
excerpt: 'A working definition for what "good" means when the benchmarks themselves are the moving target.',
excerptFa: 'یک تعریف کاربردی از «خوب بودن» وقتی بنچمارک‌ها خودشان هدف متحرک‌اند.',
},
{
title: 'Building a Persian-First RAG Stack',
titleFa: 'ساختِ استک RAG با محوریتِ زبان فارسی',
date: 'Mar 2025', dateFa: 'اسفند ۱۴۰۳',
views: 12110, reactions: 504, featured: false,
},
{
title: 'Speculative Decoding, Honestly',
titleFa: 'رمزگشایی حدسی، صادقانه',
date: 'Feb 2025', dateFa: 'بهمن ۱۴۰۳',
views: 9870, reactions: 392, featured: false,
},
{
title: 'Agentic Systems Without the Cargo Cult',
titleFa: 'سامانه‌های عامل‌محور، بدون فرقه‌بازی',
date: 'Jan 2025', dateFa: 'دی ۱۴۰۳',
views: 14230, reactions: 587, featured: false,
},
{
title: 'Inference Optimization: A Field Guide',
titleFa: 'بهینه‌سازی استنتاج: یک راهنمای میدانی',
date: 'Dec 2024', dateFa: 'آذر ۱۴۰۳',
views: 16708, reactions: 633, featured: false,
},
{
title: 'Notes from the Frontier Lab',
titleFa: 'یادداشت‌هایی از آزمایشگاه پیشرو',
date: 'Nov 2024', dateFa: 'آبان ۱۴۰۳',
views: 8990, reactions: 311, featured: false,
},
];
Object.assign(window, { CONTENT, PROJECTS, ARTICLES });
// Section components
const { useState, useEffect, useRef, useMemo } = React;
// --- Icons (minimal line-style)
function Icon({ name, size = 16 }) {
const s = size;
const stroke = 1.4;
switch (name) {
case 'telegram':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="M21 4 3 11l6 2 2 6 3-4 4 4 3-15Z"/></svg>);
case 'instagram':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="0.6" fill="currentColor"/></svg>);
case 'globe':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>);
case 'feather':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="M20.2 4a6 6 0 0 0-8.5 0L4 11.7V20h8.3l7.9-7.6a6 6 0 0 0 0-8.4Z"/><path d="M16 8 2 22M17.5 15H9"/></svg>);
case 'arrow':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>);
case 'arrow-up-right':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="M7 17 17 7M8 7h9v9"/></svg>);
case 'sun':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>);
case 'moon':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"/></svg>);
case 'cursor':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="m3 3 7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="m13 13 6 6"/></svg>);
case 'cursor-off':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="m3 3 7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="m13 13 6 6"/><path d="m2 2 20 20"/></svg>);
case 'github':
return (<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>);
default:
return null;
}
}
// --- Reveal helper
function useReveal() {
useEffect(() => {
const els = document.querySelectorAll('[data-reveal]');
const obs = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add('is-visible');
obs.unobserve(e.target);
}
});
}, { threshold: 0.12 });
els.forEach((el) => obs.observe(el));
return () => obs.disconnect();
}, []);
}
// --- Count-up
function useCountUp(target, run, duration = 1800) {
const [val, setVal] = useState(0);
const startRef = useRef(null);
useEffect(() => {
if (!run) return;
let raf;
const tick = (t) => {
if (!startRef.current) startRef.current = t;
const p = Math.min(1, (t - startRef.current) / duration);
const eased = 1 - Math.pow(1 - p, 3);
setVal(Math.round(target * eased));
if (p < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [target, run, duration]);
return val;
}
// --- Magnetic button wrapper
function MagBtn({ className, onClick, children }) {
const ref = React.useRef(null);
const onMove = (e) => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const r = ref.current.getBoundingClientRect();
const dx = ((e.clientX - r.left) / r.width - 0.5) * r.width * 0.28;
const dy = ((e.clientY - r.top) / r.height - 0.5) * r.height * 0.28;
ref.current.style.transition = 'background 0.25s, border-color 0.25s, color 0.25s, transform 0.1s';
ref.current.style.transform = `translate(${dx}px,${dy}px)`;
};
const onLeave = () => {
ref.current.style.transition = 'background 0.25s, border-color 0.25s, color 0.25s, transform 0.6s cubic-bezier(0.2,0.7,0.2,1)';
ref.current.style.transform = '';
};
return <button ref={ref} className={className} onClick={onClick} onPointerMove={onMove} onPointerLeave={onLeave}>{children}</button>;
}
// --- Hero mesh (soft animated gradient blobs, deliberate, no purple)
function HeroMesh({ accent, terra }) {
return (
<svg className="hero-mesh" viewBox="0 0 1200 700" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
<defs>
<radialGradient id="g1" cx="20%" cy="30%" r="40%">
<stop offset="0%" stopColor={accent} stopOpacity="0.22"/>
<stop offset="100%" stopColor={accent} stopOpacity="0"/>
</radialGradient>
<radialGradient id="g2" cx="80%" cy="20%" r="35%">
<stop offset="0%" stopColor={terra} stopOpacity="0.16"/>
<stop offset="100%" stopColor={terra} stopOpacity="0"/>
</radialGradient>
<radialGradient id="g3" cx="70%" cy="85%" r="45%">
<stop offset="0%" stopColor={accent} stopOpacity="0.16"/>
<stop offset="100%" stopColor={accent} stopOpacity="0"/>
</radialGradient>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeOpacity="0.05" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="1200" height="700" fill="url(#grid)"/>
<g className="blob b1"><rect width="1200" height="700" fill="url(#g1)"/></g>
<g className="blob b2"><rect width="1200" height="700" fill="url(#g2)"/></g>
<g className="blob b3"><rect width="1200" height="700" fill="url(#g3)"/></g>
</svg>
);
}
// --- Hero
function Hero({ t, accent, terra, onNav }) {
const [activeTag, setActiveTag] = React.useState(0);
React.useEffect(() => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const n = t.hero.tagline.length;
const id = setInterval(() => setActiveTag(i => (i + 1) % n), 2800);
return () => clearInterval(id);
}, []);
return (
<section className="hero" id="top">
<HeroMesh accent={accent} terra={terra} />
<div className="wrap hero-wrap">
<div className="hero-kicker mono">
<span>{t.hero.kicker}</span>
<span className="dot"/>
<span>arsamsabbagh.ir</span>
</div>
<h1 className="hero-name">
<span className="hero-line">{t.hero.nameLine1}</span>
<span className="hero-line"><span className="serif hero-amp">&amp;</span> {t.hero.nameLine2}</span>
</h1>
<div className="hero-meta">
<div className="hero-tag">
{t.hero.tagline.map((line, i) => (
<div key={i} className={`hero-tagline-row${i === activeTag ? ' tag-on' : ' tag-off'}`}>
<span className="mono idx">0{i+1}</span>
<span className="serif">{line}</span>
</div>
))}
</div>
<p className="hero-lede">{t.hero.lede}</p>
</div>
<div className="hero-ctas">
<MagBtn className="btn btn-primary" onClick={() => onNav('projects')}>
<span>{t.hero.ctaA}</span><Icon name="arrow" size={14}/>
</MagBtn>
<MagBtn className="btn btn-ghost" onClick={() => onNav('writing')}>
<span>{t.hero.ctaB}</span><Icon name="arrow-up-right" size={14}/>
</MagBtn>
</div>
<div className="hero-foot">
<dl className="hero-defs">
{t.hero.meta.map((m, i) => (
<div key={i} className="hero-def">
<dt className="mono mute">{m.k}</dt>
<dd>{m.v}</dd>
</div>
))}
</dl>
<div className="hero-scroll">
<span className="mono">{t.hero.scroll}</span>
<span className="scroll-line"/>
</div>
</div>
</div>
<style>{`
.hero { position: relative; min-height: 100vh; padding: 0; overflow: hidden; }
.hero-mesh { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--ink); pointer-events: none; }
.hero-mesh .blob { animation: float 22s ease-in-out infinite; transform-origin: center; }
.hero-mesh .b2 { animation-delay: -7s; }
.hero-mesh .b3 { animation-delay: -14s; }
@keyframes float {
0%, 100% { transform: translate(0,0) scale(1); }
50% { transform: translate(-2%, 1%) scale(1.04); }
}
.hero-wrap { position: relative; padding-top: clamp(60px, 10vh, 110px); padding-bottom: clamp(40px, 6vh, 80px); min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto auto; gap: clamp(30px, 5vh, 50px); }
.hero-kicker { display: flex; align-items: center; gap: 14px; color: var(--ink-soft); }
.hero-kicker .dot { width: 3px; height: 3px; background: var(--ink-mute); border-radius: 50%; display: inline-block; }
.hero-name { margin: 0; font-family: var(--sans); font-weight: 300; font-size: clamp(48px, 13vw, 200px); line-height: 0.92; letter-spacing: -0.04em; }
.hero-name .hero-line { display: block; }
.hero-name .hero-amp { color: var(--accent); font-weight: 400; font-size: 0.7em; vertical-align: 0.05em; padding-right: 0.1em; }
html[lang="fa"] .hero-name { font-family: var(--farsi); font-weight: 500; letter-spacing: -0.01em; }
html[lang="fa"] .hero-name .hero-amp { display: none; }
html[lang="fa"] .hero-name .hero-line + .hero-line { margin-top: 0.1em; }
.hero-meta { display: grid; grid-template-columns: 1.1fr 1fr; gap: clamp(28px, 6vw, 80px); align-items: end; }
.hero-tag { display: flex; flex-direction: column; gap: 6px; }
.hero-tagline-row { display: flex; align-items: baseline; gap: 14px; font-size: clamp(20px, 2.4vw, 30px); color: var(--ink); transition: opacity 0.5s, transform 0.5s cubic-bezier(0.2,0.7,0.2,1); }
.tag-on { opacity: 1; }
.tag-off { opacity: 0.6; }
.hero-tagline-row .idx { color: var(--accent); font-size: 11px; }
.hero-lede { margin: 0; font-size: clamp(15px, 2vw, 17px); line-height: 1.55; color: var(--ink-soft); max-width: 44ch; }
@media (max-width: 820px) {
.hero-meta { grid-template-columns: 1fr; gap: 24px; }
}
.hero-ctas { display: flex; gap: 14px; flex-wrap: wrap; }
.btn { display: inline-flex; align-items: center; gap: 10px; padding: 14px 22px; border-radius: 999px; font-size: 14px; border: 1px solid var(--ink); background: var(--ink); color: var(--bg); transition: background 0.25s, border-color 0.25s, color 0.25s, transform 0.12s; }
.btn:active { transform: scale(0.96); }
.btn-primary:hover { background: var(--accent); border-color: var(--accent); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--rule); }
.btn-ghost:hover { border-color: var(--ink); }
.hero-foot { display: flex; justify-content: space-between; align-items: end; gap: 24px; border-top: 1px solid var(--rule); padding-top: 24px; }
.hero-defs { margin: 0; display: flex; gap: clamp(20px, 5vw, 60px); flex-wrap: wrap; }
.hero-def { display: flex; flex-direction: column; gap: 4px; }
.hero-def dt { color: var(--ink-mute); }
.hero-def dd { margin: 0; font-size: 14px; color: var(--ink); }
.hero-scroll { display: flex; align-items: center; gap: 12px; color: var(--ink-soft); }
.scroll-line { width: 48px; height: 1px; background: var(--ink-soft); position: relative; overflow: hidden; }
.scroll-line::after { content: ''; position: absolute; left: -50%; top: 0; bottom: 0; width: 50%; background: var(--accent); animation: scrollBob 2.2s ease-in-out infinite; }
@keyframes scrollBob { 0% { left: -50%; } 100% { left: 100%; } }
@media (max-width: 720px) {
.hero-foot { flex-direction: column; align-items: stretch; }
.hero-scroll { justify-content: flex-start; }
}
`}</style>
</section>
);
}
// --- Section head
function SectionHead({ num, title, em }) {
return (
<div className="section-head" data-reveal>
<div className="mono num">{num && <>{num} </>}<span className="mute">— Section</span></div>
<h2>
{title.map((line, i) => {
if (line === em) return <em key={i}>{line}</em>;
if (line.endsWith(em)) {
const before = line.slice(0, line.length - em.length);
return <span key={i}>{before}<em>{em}</em><br/></span>;
}
return <span key={i}>{line}<br/></span>;
})}
</h2>
</div>
);
}
// --- About
function About({ t }) {
return (
<section className="section" id="about">
<div className="wrap">
<SectionHead num={t.about.num} title={t.about.title} em={t.about.titleEm}/>
<div className="about-grid">
<div className="about-photo" data-reveal>
<div className="ph-frame">
<svg className="ph-stripes" viewBox="0 0 100 125" preserveAspectRatio="none" aria-hidden="true">
<defs>
<pattern id="diag" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="6" stroke="currentColor" strokeOpacity="0.5" strokeWidth="0.4"/>
</pattern>
</defs>
<rect width="100" height="125" fill="url(#diag)"/>
</svg>
<div className="ph-label mono">{t.about.placeholder}</div>
<div className="ph-tag mono">A.S. — 2026</div>
</div>
</div>
<div className="about-text" data-reveal>
{t.about.body.map((p, i) => (
<p key={i} className="about-p">{p}</p>
))}
<ul className="about-bullets">
{t.about.bullets.map((b, i) => (
<li key={i}><span className="dash" aria-hidden="true">—</span><span>{b}</span></li>
))}
</ul>
</div>
</div>
</div>
<style>{`
.about-grid { display: grid; grid-template-columns: 0.85fr 1.15fr; gap: clamp(28px, 5vw, 72px); align-items: start; }
@media (max-width: 820px) { .about-grid { grid-template-columns: 1fr; } }
.about-photo { position: sticky; top: 100px; }
.ph-frame { aspect-ratio: 4/5; background: var(--bg-elev); position: relative; overflow: hidden; border: 1px solid var(--rule); }
.ph-stripes { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--ink-mute); }
.ph-label, .ph-tag { position: absolute; color: var(--ink-soft); }
.ph-label { left: 16px; top: 16px; }
.ph-tag { right: 16px; bottom: 16px; color: var(--ink); }
html[lang="fa"] .ph-label { left: auto; right: 16px; }
html[lang="fa"] .ph-tag { right: auto; left: 16px; }
.about-text { display: flex; flex-direction: column; gap: 22px; }
.about-p { margin: 0; font-size: clamp(16px, 1.25vw, 19px); line-height: 1.65; color: var(--ink); max-width: 60ch; }
.about-p:first-of-type::first-letter { font-family: var(--serif); font-style: italic; font-size: 3.4em; float: left; line-height: 0.9; padding: 0.05em 0.15em 0 0; color: var(--accent); }
html[lang="fa"] .about-p:first-of-type::first-letter { float: right; padding: 0.05em 0 0 0.15em; font-size: 2.4em; }
.about-bullets { list-style: none; padding: 0; margin: 12px 0 0; border-top: 1px solid var(--rule); }
.about-bullets li { display: flex; gap: 16px; align-items: baseline; padding: 14px 0; border-bottom: 1px solid var(--rule); font-size: 14px; color: var(--ink-soft); }
.about-bullets .dash { color: var(--accent); font-family: var(--mono); }
`}</style>
</section>
);
}
// --- Expertise
function Expertise({ t }) {
return (
<section className="section" id="expertise" style={{background: 'var(--bg-elev)'}}>
<div className="wrap">
<SectionHead num={t.expertise.num} title={t.expertise.title} em={t.expertise.titleEm}/>
<div className="exp-grid">
{t.expertise.tracks.map((track, i) => (
<div key={i} className="exp-track" data-reveal style={{ '--track-accent': track.accent === 'terra' ? 'var(--terra)' : 'var(--accent)', transitionDelay: (i * 80) + 'ms' }}>
<div className="exp-tag">
<span className="mono" style={{color: 'var(--track-accent)'}}>{track.tag}</span>
<span className="exp-rule"/>
</div>
<h3 className="exp-name">
{track.name}
{track.subname && <span className="exp-subname serif"> — {track.subname}</span>}
</h3>
<ul className="exp-list">
{track.items.map((it, j) => (
<li key={j}>
<span className="exp-bullet" style={{background: 'var(--track-accent)'}}/>
<span>{it}</span>
</li>
))}
</ul>
</div>
))}
</div>
</div>
<style>{`
.exp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
@media (max-width: 820px) { .exp-grid { grid-template-columns: 1fr; } }
.exp-track { padding: clamp(28px, 4vw, 48px) clamp(24px, 3vw, 40px); border-right: 1px solid var(--rule); }
.exp-track:last-child { border-right: none; }
html[lang="fa"] .exp-track { border-right: none; border-left: 1px solid var(--rule); }
html[lang="fa"] .exp-track:last-child { border-left: none; }
@media (max-width: 820px) {
.exp-track { border-right: none !important; border-left: none !important; border-bottom: 1px solid var(--rule); }
.exp-track:last-child { border-bottom: none; }
}
.exp-tag { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
.exp-rule { flex: 1; height: 1px; background: var(--rule); }
.exp-name { margin: 0 0 28px; font-size: clamp(22px, 2.4vw, 30px); font-weight: 400; letter-spacing: -0.01em; line-height: 1.2; }
.exp-subname { color: var(--ink-soft); font-weight: 400; }
.exp-list { list-style: none; padding: 0; margin: 0; }
.exp-list li { display: flex; align-items: baseline; gap: 14px; padding: 14px 0; border-top: 1px solid var(--rule); font-size: 15px; color: var(--ink); }
.exp-list li:last-child { border-bottom: 1px solid var(--rule); }
.exp-bullet { width: 5px; height: 5px; border-radius: 50%; flex-shrink: 0; transform: translateY(-2px); }
`}</style>
</section>
);
}
// --- Projects
function Projects({ t, lang }) {
const [tab, setTab] = useState('ai');
const items = PROJECTS[tab];
return (
<section className="section" id="projects">
<div className="wrap">
<SectionHead num={t.projects.num} title={t.projects.title} em={t.projects.titleEm}/>
<div className="proj-tabs" data-reveal>
{t.projects.tabs.map((tb) => (
<button key={tb.id} className={"proj-tab " + (tab === tb.id ? 'on' : '')} onClick={() => setTab(tb.id)}>
<span>{tb.label}</span>
<span className="proj-tab-count mono">{(PROJECTS[tb.id] || []).length}</span>
</button>
))}
</div>
<div key={tab} className="proj-grid">
{items.map((p, i) => (
<article key={tab + i} className="proj-card" data-reveal style={{ transitionDelay: (i * 60) + 'ms' }}>
<div className="proj-thumb">
<ProjectThumb kind={tab} index={i}/>
<div className="proj-overlay">
<a href={p.url} target="_blank" rel="noreferrer" className="btn btn-primary"><span>{t.projects.cta}</span><Icon name="arrow-up-right" size={14}/></a>
</div>
</div>
<div className="proj-meta">
<div className="proj-meta-row">
<span className="mono mute">{p.year}</span>
<span className="mono mute">{p.role}</span>
</div>
<h3 className="proj-title">{p.title}</h3>
<p className="proj-desc">{p.desc}</p>
<div className="proj-tags">
{p.tags.map((tg, j) => <span key={j} className="proj-tag mono">{tg}</span>)}
</div>
</div>
</article>
))}
</div>
</div>
<style>{`
.proj-tabs { display: inline-flex; gap: 0; margin-bottom: 36px; border: 1px solid var(--rule); border-radius: 999px; padding: 4px; }
.proj-tab { display: inline-flex; align-items: center; gap: 8px; padding: 8px 18px; border: none; background: transparent; color: var(--ink-soft); border-radius: 999px; font-size: 13px; transition: background 0.2s, color 0.2s, transform 0.12s; }
.proj-tab:active { transform: scale(0.95); }
.proj-tab.on { background: var(--ink); color: var(--bg); }
.proj-tab-count { font-size: 10px; opacity: 0.6; }
.proj-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px 28px; animation: projFadeUp 0.35s cubic-bezier(0.2,0.7,0.2,1) both; }
@keyframes projFadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@media (max-width: 980px) { .proj-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .proj-grid { grid-template-columns: 1fr; } }
.proj-card { display: flex; flex-direction: column; gap: 16px; }
.proj-thumb { aspect-ratio: 4/3; background: var(--bg-elev); border: 1px solid var(--rule); position: relative; overflow: hidden; cursor: pointer; }
.proj-overlay { position: absolute; inset: 0; display: flex; align-items: flex-end; justify-content: flex-start; padding: 18px; opacity: 0; transition: opacity 0.25s; background: linear-gradient(to top, color-mix(in oklab, var(--ink) 35%, transparent), transparent 60%); }
.proj-thumb:hover .proj-overlay { opacity: 1; }
.proj-thumb:hover .proj-thumb-art { transform: scale(1.04); }
.proj-thumb-art { position: absolute; inset: 0; transition: transform 0.6s ease; }
.proj-meta-row { display: flex; justify-content: space-between; align-items: center; }
.proj-title { margin: 4px 0 0; font-size: 19px; font-weight: 500; letter-spacing: -0.01em; }
.proj-desc { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink-soft); }
.proj-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.proj-tag { padding: 4px 8px; border: 1px solid var(--rule); border-radius: 999px; color: var(--ink-soft); }
/* Thumb hover animations */
.ev-path { stroke-dasharray: 600; stroke-dashoffset: 600; transition: stroke-dashoffset 1.4s cubic-bezier(0.2,0.7,0.2,1); }
.proj-thumb:hover .ev-path { stroke-dashoffset: 0; }
.ev-dot { opacity: 0; transition: opacity 0.3s 1.1s; }
.proj-thumb:hover .ev-dot { opacity: 1; }
.ag-ping { opacity: 0; transform-box: fill-box; transform-origin: center; }
.proj-thumb:hover .ag-ping { animation: agPing 0.75s ease-out 0.1s forwards; }
@keyframes agPing { from { opacity: 0.7; transform: scale(1); } to { opacity: 0; transform: scale(2.4); } }
.ed-line { stroke-dasharray: 320; stroke-dashoffset: 320; transition: stroke-dashoffset 0.75s cubic-bezier(0.2,0.7,0.2,1); }
.proj-thumb:hover .ed-line { stroke-dashoffset: 0; }
`}</style>
</section>
);
}
// --- Project thumbnail art (each one is a different abstract editorial composition)
function ProjectThumb({ kind, index }) {
const accent = 'var(--accent)';
const terra = 'var(--terra)';
const ink = 'var(--ink)';
const mute = 'var(--ink-mute)';
const arts = [
// 0
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g stroke={ink} strokeOpacity="0.18" fill="none">
{Array.from({length: 12}).map((_, i) => <line key={i} x1="0" y1={i*30} x2="400" y2={i*30}/>)}
</g>
<path className="ev-path" d="M 30 240 Q 100 60 200 150 T 380 80" stroke={accent} strokeWidth="2" fill="none"/>
<circle className="ev-dot" cx="200" cy="150" r="6" fill={accent}/>
<text x="22" y="34" fontFamily="JetBrains Mono" fontSize="9" fill={mute} letterSpacing="1.2">// EVAL.LOSS</text>
</svg>,
// 1
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g fontFamily="Vazirmatn, Geist" fontSize="12" fill={ink} fillOpacity="0.7">
<text x="22" y="44">سلام</text>
<text x="80" y="44">→</text>
<text x="110" y="44" fill={accent}>hello</text>
</g>
<g stroke={ink} strokeOpacity="0.2" fill="none">
<rect x="22" y="70" width="356" height="50"/>
<rect x="22" y="135" width="356" height="50"/>
<rect x="22" y="200" width="356" height="50"/>
</g>
<g fill={accent}>
<rect x="22" y="70" width="240" height="50" fillOpacity="0.16"/>
<rect x="22" y="135" width="180" height="50" fillOpacity="0.16"/>
<rect x="22" y="200" width="290" height="50" fillOpacity="0.16"/>
</g>
</svg>,
// 2 — inference / latency
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g>
{Array.from({length: 22}).map((_, i) => (
<rect key={i} x={20 + i*17} y={250 - (i % 5 + 2) * 18} width="10" height={(i % 5 + 2) * 18} fill={i === 17 ? accent : ink} fillOpacity={i === 17 ? 1 : 0.5}/>
))}
</g>
<text x="22" y="34" fontFamily="JetBrains Mono" fontSize="9" fill={mute} letterSpacing="1.2">TOK/S</text>
<text x="370" y="34" fontFamily="JetBrains Mono" fontSize="9" fill={accent} textAnchor="end">+38%</text>
</svg>,
// 3 — agent
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g stroke={ink} strokeOpacity="0.35" fill="none">
<line x1="80" y1="150" x2="180" y2="80"/>
<line x1="80" y1="150" x2="180" y2="150"/>
<line x1="80" y1="150" x2="180" y2="220"/>
<line x1="180" y1="80" x2="300" y2="120"/>
<line x1="180" y1="220" x2="300" y2="180"/>
</g>
<g>
<circle className="ag-ping" cx="80" cy="150" r="14" fill="none" stroke={accent}/>
<circle className="ag-center" cx="80" cy="150" r="14" fill={accent}/>
<circle cx="180" cy="80" r="10" fill="var(--bg)" stroke={ink}/>
<circle cx="180" cy="150" r="10" fill="var(--bg)" stroke={ink}/>
<circle cx="180" cy="220" r="10" fill="var(--bg)" stroke={ink}/>
<circle cx="300" cy="120" r="14" fill={terra}/>
<circle cx="300" cy="180" r="14" fill="var(--bg)" stroke={ink}/>
</g>
</svg>,
// 4 — pipeline
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g fontFamily="JetBrains Mono" fontSize="9" fill={mute}>
{['kafka', 'transform', 'features', 'serve'].map((s, i) => (
<g key={i}>
<rect x={22 + i*92} y={120} width="80" height="60" fill="var(--bg)" stroke={ink} strokeOpacity="0.5"/>
<text x={62 + i*92} y={155} textAnchor="middle" fill={ink}>{s}</text>
{i < 3 && <path d={`M ${102 + i*92} 150 L ${114 + i*92} 150`} stroke={accent} strokeWidth="1.5"/>}
</g>
))}
</g>
<text x="22" y="44" fontFamily="JetBrains Mono" fontSize="9" fill={mute} letterSpacing="1.2">PIPELINE</text>
</svg>,
// 5 — notes
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g stroke={ink} strokeOpacity="0.3">
{Array.from({length:14}).map((_, i) => <line key={i} x1="40" y1={40+i*18} x2={i % 3 === 2 ? 200 : 340} y2={40+i*18}/>)}
</g>
<rect x="40" y="76" width="160" height="2" fill={accent}/>
</svg>,
// 6 — editorial
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<text x="40" y="120" fontFamily="Instrument Serif" fontStyle="italic" fontSize="72" fill={ink}>Tek</text>
<text x="40" y="180" fontFamily="Geist" fontWeight="300" fontSize="50" fill={accent}>nav.</text>
<line className="ed-line" x1="40" y1="220" x2="360" y2="220" stroke={ink} strokeOpacity="0.4"/>
<text x="40" y="245" fontFamily="JetBrains Mono" fontSize="9" fill={mute}>ISSUE — 06 · ed. A.S.</text>
</svg>,
// 7 — studio
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g>
<rect x="40" y="40" width="160" height="220" fill={terra} fillOpacity="0.85"/>
<rect x="210" y="40" width="150" height="100" fill="var(--bg)" stroke={ink}/>
<rect x="210" y="150" width="150" height="110" fill={ink}/>
</g>
<text x="48" y="252" fontFamily="Instrument Serif" fontStyle="italic" fontSize="22" fill="var(--bg)">studio</text>
</svg>,
// 8 — health
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<path d="M 0 200 Q 100 140 200 200 T 400 180" stroke={accent} strokeWidth="2" fill="none"/>
<path d="M 0 220 Q 100 160 200 220 T 400 200" stroke={accent} strokeOpacity="0.4" strokeWidth="1" fill="none"/>
<circle cx="200" cy="200" r="36" fill="var(--bg)" stroke={ink}/>
<path d="M 190 200 L 198 208 L 215 188" stroke={accent} strokeWidth="2" fill="none"/>
</svg>,
// 9 — lab
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<text x="200" y="180" textAnchor="middle" fontFamily="Instrument Serif" fontStyle="italic" fontSize="120" fill={ink}>q</text>
<circle cx="245" cy="125" r="10" fill={accent}/>
<text x="200" y="232" textAnchor="middle" fontFamily="JetBrains Mono" fontSize="10" fill={mute} letterSpacing="2">QUANTA — LABS</text>
</svg>,
// 10 — marketplace
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g>
{Array.from({length: 12}).map((_, i) => (
<rect key={i} x={30 + (i%4)*92} y={40 + Math.floor(i/4)*78} width="76" height="62" fill="var(--bg)" stroke={ink} strokeOpacity="0.3"/>
))}
<rect x="30" y="40" width="76" height="62" fill={accent} fillOpacity="0.18" stroke={accent}/>
</g>
</svg>,
// 11 — self
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" className="proj-thumb-art">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<text x="200" y="170" textAnchor="middle" fontFamily="Instrument Serif" fontStyle="italic" fontSize="64" fill={ink}>A &amp; S</text>
<line x1="120" y1="190" x2="280" y2="190" stroke={accent}/>
<text x="200" y="216" textAnchor="middle" fontFamily="JetBrains Mono" fontSize="9" fill={mute} letterSpacing="2">v6 — 2026</text>
</svg>,
];
const offset = kind === 'ai' ? 0 : 6;
return arts[(offset + index) % arts.length];
}
// --- Writing
function Writing({ t, lang }) {
const featured = ARTICLES.find((a) => a.featured);
const rest = ARTICLES.filter((a) => !a.featured);
const tt = (a, k) => (lang === 'fa' && a[k + 'Fa']) ? a[k + 'Fa'] : a[k];
const fmt = (n) => n.toLocaleString(lang === 'fa' ? 'fa-IR' : 'en-US');
return (
<section className="section" id="writing">
<div className="wrap">
<SectionHead num={t.writing.num} title={t.writing.title} em={t.writing.titleEm}/>
<p className="writing-lede" data-reveal>{t.writing.lede}</p>
<a href="https://teknav.ir/profile/@arsam-sabbagh" target="_blank" rel="noreferrer" className="writing-feature" data-reveal>
<div className="wf-thumb">
<svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
<rect width="400" height="300" fill="var(--bg-elev)"/>
<g stroke="var(--ink)" strokeOpacity="0.12" fill="none">
{Array.from({length: 18}).map((_, i) => <line key={i} x1="0" y1={i*18} x2="400" y2={i*18}/>)}
</g>
<text x="40" y="170" fontFamily="Instrument Serif" fontStyle="italic" fontSize="68" fill="var(--ink)">Frontier</text>
<text x="40" y="220" fontFamily="Geist" fontWeight="300" fontSize="38" fill="var(--accent)">Eval.</text>
</svg>
</div>
<div className="wf-body">
<div className="mono accent">★ {t.writing.featured}</div>
<h3 className="wf-title">{tt(featured, 'title')}</h3>
{featured.excerpt && <p className="wf-excerpt serif">{lang === 'fa' ? featured.excerptFa : featured.excerpt}</p>}
<div className="wf-meta">
<span>{tt(featured, 'date')}</span>
<span className="dotsep">·</span>
<span>{fmt(featured.views)} {lang === 'fa' ? 'بازدید' : 'views'}</span>
<span className="dotsep">·</span>
<span>★ {fmt(featured.reactions)}</span>
</div>
<span className="wf-cta mono accent">{t.writing.readMore} <Icon name="arrow-up-right" size={12}/></span>
</div>
</a>
<div className="writing-list" data-reveal>
<div className="wl-head mono mute">
<span>{t.writing.cols.title}</span>
<span>{t.writing.cols.date}</span>
<span style={{textAlign:'end'}}>{t.writing.cols.views}</span>
<span style={{textAlign:'end'}}>{t.writing.cols.reactions}</span>
</div>
{rest.map((a, i) => (
<a key={i} href="https://teknav.ir/profile/@arsam-sabbagh" target="_blank" rel="noreferrer" className="wl-row">
<span className="wl-title">{tt(a, 'title')}<Icon name="arrow-up-right" size={12}/></span>
<span className="wl-date mono">{tt(a, 'date')}</span>
<span className="wl-views mono">{fmt(a.views)}</span>
<span className="wl-react mono">{fmt(a.reactions)}</span>
</a>
))}
</div>
<a href="https://teknav.ir/profile/@arsam-sabbagh" target="_blank" rel="noreferrer" className="writing-all" data-reveal>
<span>{t.writing.viewAll}</span><Icon name="arrow-up-right" size={14}/>
</a>
</div>
<style>{`
.writing-lede { margin: 0 0 40px; max-width: 60ch; font-size: 17px; line-height: 1.6; color: var(--ink-soft); }
.writing-feature { display: grid; grid-template-columns: 1.1fr 1fr; gap: 0; border: 1px solid var(--rule); margin-bottom: 56px; overflow: hidden; transition: border-color 0.25s; }
.writing-feature:hover { border-color: var(--ink); }
@media (max-width: 820px) { .writing-feature { grid-template-columns: 1fr; } }
.wf-thumb { aspect-ratio: 4/3; overflow: hidden; }
.wf-thumb svg { width: 100%; height: 100%; display: block; }
.wf-body { padding: clamp(24px, 3vw, 40px); display: flex; flex-direction: column; gap: 14px; justify-content: center; }
.wf-title { margin: 0; font-size: clamp(22px, 2.4vw, 32px); font-weight: 400; letter-spacing: -0.01em; line-height: 1.2; }
.wf-excerpt { margin: 0; font-size: clamp(16px, 1.3vw, 19px); color: var(--ink-soft); line-height: 1.5; }
.wf-meta { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; font-size: 13px; color: var(--ink-mute); font-family: var(--mono); }
.wf-meta .dotsep { opacity: 0.5; }
.wf-cta { display: inline-flex; align-items: center; gap: 8px; margin-top: 4px; }
.writing-list { border-top: 1px solid var(--rule); }
.wl-head, .wl-row { display: grid; grid-template-columns: 1fr 130px 110px 80px; gap: 18px; align-items: center; padding: 16px 0; border-bottom: 1px solid var(--rule); font-size: 14px; }
.wl-head { padding: 12px 0; }
.wl-head span:nth-child(n+3) { text-align: end; }
.wl-row { color: var(--ink); transition: padding 0.25s cubic-bezier(0.2,0.7,0.2,1), background 0.25s; border-radius: 4px; }
.wl-row:hover { padding-left: 12px; background: var(--bg-elev); }
html[lang="fa"] .wl-row:hover { padding-left: 0; padding-right: 12px; }
.wl-row:hover .wl-title { color: var(--accent); }
.wl-title { display: inline-flex; align-items: center; gap: 10px; font-size: 16px; }
.wl-date, .wl-views, .wl-react { color: var(--ink-soft); text-align: end; }
@media (max-width: 720px) {
.wl-head, .wl-row { grid-template-columns: 1fr 90px; }
.wl-head span:nth-child(3), .wl-head span:nth-child(4), .wl-row .wl-views, .wl-row .wl-react { display: none; }
}
.writing-all { display: inline-flex; align-items: center; gap: 10px; margin-top: 32px; padding: 14px 22px; border: 1px solid var(--rule); border-radius: 999px; font-size: 14px; transition: border-color 0.25s, color 0.25s, transform 0.12s; }
.writing-all:active { transform: scale(0.97); }
.writing-all:hover { background: var(--ink); color: var(--bg); border-color: var(--ink); }
`}</style>
</section>
);
}
// --- Stats bar
function StatsBar({ t, lang }) {
const ref = useRef(null);
const [run, setRun] = useState(false);
useEffect(() => {
if (!ref.current) return;
const obs = new IntersectionObserver(([e]) => {
if (e.isIntersecting) { setRun(true); obs.disconnect(); }
}, { threshold: 0.4 });
obs.observe(ref.current);
return () => obs.disconnect();
}, []);
const fmt = (n) => n.toLocaleString(lang === 'fa' ? 'fa-IR' : 'en-US');
return (
<section className="stats-bar" ref={ref}>
<div className="wrap stats-wrap">
<div className="mono stats-kicker">{t.stats.kicker}</div>
<div className="stats-row">
{t.stats.items.map((it, i) => (
<Stat key={i} target={it.n} label={it.label} run={run} fmt={fmt} suffix={it.suffix}/>
))}
</div>
</div>
<style>{`
.stats-bar { background: var(--bg-elev); color: var(--ink); padding: clamp(50px, 7vw, 90px) 0; position: relative; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.stats-wrap { display: flex; flex-direction: column; gap: 36px; }
.stats-kicker { color: var(--ink-mute); letter-spacing: 0.16em; }
.stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
@media (max-width: 720px) { .stats-row { grid-template-columns: 1fr 1fr; gap: 28px; } }
@media (max-width: 460px) { .stats-row { grid-template-columns: 1fr; gap: 28px; } }
.stat { display: flex; flex-direction: column; gap: 14px; border-top: 1px solid var(--rule); padding-top: 20px; }
.stat-num { font-family: var(--sans); font-weight: 300; font-size: clamp(48px, 7vw, 96px); letter-spacing: -0.04em; line-height: 1; color: var(--ink); }
.stat-num .unit { color: var(--accent); font-family: var(--serif); font-style: italic; font-weight: 400; }
.stat-label { font-size: 14px; color: var(--ink-soft); }
`}</style>
</section>
);
}
function Stat({ target, label, run, fmt, suffix }) {
const v = useCountUp(target, run);
return (
<div className="stat">
<div className="stat-num">{fmt(v)}{suffix && <span className="unit">{suffix}</span>}</div>
<div className="stat-label">{label}</div>
</div>
);
}
// --- Contact
function Contact({ t }) {
return (
<section className="section" id="contact" style={{paddingBottom: 'clamp(80px, 12vw, 160px)'}}>
<div className="wrap">
<SectionHead num={t.contact.num} title={t.contact.title} em={t.contact.titleEm}/>
<p className="contact-lede" data-reveal>{t.contact.lede}</p>
<div className="contact-grid" data-reveal>
{t.contact.socials.map((s, i) => (
<a key={i} href={s.href} target="_blank" rel="noreferrer" className="contact-row">
<span className="cr-icon"><Icon name={s.icon} size={18}/></span>
<span className="cr-label mono">{s.label}</span>
<span className="cr-handle">{s.handle}</span>
<span className="cr-arrow"><Icon name="arrow-up-right" size={16}/></span>
</a>
))}
</div>
</div>
<style>{`
.contact-lede { margin: 0 0 40px; max-width: 56ch; font-size: clamp(20px, 2.2vw, 30px); line-height: 1.4; color: var(--ink); font-family: var(--serif); font-style: italic; }
html[lang="fa"] .contact-lede { font-family: var(--farsi); font-style: normal; font-weight: 500; }
.contact-grid { border-top: 1px solid var(--rule); }
.contact-row { display: grid; grid-template-columns: 32px 100px 1fr 32px; gap: 20px; align-items: center; padding: 22px 0; border-bottom: 1px solid var(--rule); font-size: 18px; transition: padding 0.25s cubic-bezier(0.2,0.7,0.2,1), background 0.25s; border-radius: 4px; }
.contact-row:hover { padding-left: 12px; background: var(--bg-elev); }
html[lang="fa"] .contact-row:hover { padding-left: 0; padding-right: 12px; }
.contact-row:hover .cr-handle { color: var(--accent); }
.contact-row:hover .cr-arrow { color: var(--accent); transform: translate(2px, -2px); }
html[lang="fa"] .contact-row:hover .cr-arrow { transform: translate(-2px, -2px); }
.cr-icon { color: var(--ink-soft); }
.cr-label { color: var(--ink-mute); }
.cr-handle { color: var(--ink); transition: color 0.25s; }
.cr-arrow { color: var(--ink-mute); justify-self: end; transition: transform 0.25s, color 0.25s; }
@media (max-width: 640px) { .contact-row { grid-template-columns: 28px 1fr 24px; } .cr-label { display: none; } }
`}</style>
</section>
);
}
// --- Footer
function Footer({ t, lang, onNav }) {
const colA = lang === 'fa' ? 'پیمایش' : 'Navigate';
const colB = lang === 'fa' ? 'ارتباط' : 'Elsewhere';
const colC = lang === 'fa' ? 'این مدرک' : 'This document';
const top = lang === 'fa' ? 'بازگشت به بالا ↑' : 'Back to top ↑';
const elsewhere = [
{ label: lang === 'fa' ? 'گیت‌هاب' : 'GitHub', handle: 'Coaxys-A', href: 'https://github.com/Coaxys-A' },
{ label: lang === 'fa' ? 'تلگرام' : 'Telegram', handle: 't.me/Coaxys', href: 'https://t.me/Coaxys' },
{ label: lang === 'fa' ? 'اینستاگرام' : 'Instagram', handle: '@_coaxys', href: 'https://instagram.com/_coaxys' },
{ label: 'Teknav', handle: '@arsam-sabbagh', href: 'https://teknav.ir/profile/@arsam-sabbagh' },
{ label: lang === 'fa' ? 'دامنه' : 'Domain', handle: 'arsamsabbagh.ir', href: 'https://arsamsabbagh.ir' },
];
const meta = [
{ k: lang === 'fa' ? 'ست‌شده با' : 'Set in', v: lang === 'fa' ? 'Geist، Markazi، لاله‌زار، وزیرمتن' : 'Geist · Instrument Serif · Markazi Text' },
{ k: lang === 'fa' ? 'ساخته‌شده با' : 'Built with', v: 'React · CSS Grid · pure SVG' },
{ k: lang === 'fa' ? 'میزبانی' : 'Hosted on', v: 'www.arsamsabbagh.ir' },
{ k: lang === 'fa' ? 'آخرین ویرایش' : 'Last revised', v: lang === 'fa' ? 'اردیبهشت ۱۴۰۵' : 'May 2026' },
];
return (
<footer className="ft">
<div className="wrap">
{/* MIX: giant contact CTA */}
<div className="mix-cta-wrap" data-reveal>
<p className="mix-cta-kicker mono">{lang === 'fa' ? '( ایده‌ای داری که ارزش وسواس دارد؟ )' : "( Got an idea worth obsessing over? )"}</p>
<a className="mix-cta" href="mailto:arsam12sb@gmail.com">
<span className="mix-cta-line">{lang === 'fa' ? 'بیا' : "LET’S"}</span>
<span className="mix-cta-line mix-cta-line--stroke">{lang === 'fa' ? 'حرف بزنیم' : 'TALK'}</span>
</a>
</div>
<div className="ft-top">
<div className="ft-col ft-sig">
<div className="ft-brand">
<span className="brand-mark"/>
<div className="ft-brand-text">
<div className="ft-brand-a serif">{lang === 'fa' ? 'آرسام صباغ' : 'Arsam Sabbagh'}</div>
<div className="ft-brand-b mono mute">{lang === 'fa' ? 'با نام کواکسیس' : 'working as Coaxys'}</div>
</div>
</div>
<p className="ft-quote serif">
{lang === 'fa'
? '«نوشتن، اندیشیدن است — به همان اندازه که ساختن، اندیشیدن است.»'
: '"Writing is thinking — as much as building is thinking."'}
</p>
<div className="ft-cta">
<a href="mailto:arsam12sb@gmail.com" className="ft-mail">
<span className="serif">arsam12sb@gmail.com</span>
<Icon name="arrow-up-right" size={14}/>
</a>
</div>
</div>
<div className="ft-col">
<div className="mono ft-h">{colA}</div>
<ul className="ft-list">
{t.nav.links.map((l) => (
<li key={l.id}>
<a href={'#' + l.id} onClick={(e) => { e.preventDefault(); onNav(l.id); }}>
<span className="mono ft-num">{(t.nav.links.indexOf(l) + 1).toString().padStart(2, '0')}</span>
<span>{l.label}</span>
</a>
</li>
))}
</ul>
</div>
<div className="ft-col">
<div className="mono ft-h">{colB}</div>
<ul className="ft-list">
{elsewhere.map((l, i) => (
<li key={i}>
<a href={l.href} target="_blank" rel="noreferrer">
<span>{l.label}</span>
<span className="ft-handle mono mute">{l.handle}</span>
</a>
</li>
))}
</ul>
</div>
<div className="ft-col">
<div className="mono ft-h">{colC}</div>
<dl className="ft-meta">
{meta.map((m, i) => (
<div key={i} className="ft-meta-row">
<dt className="mono mute">{m.k}</dt>
<dd className="mono">{m.v}</dd>
</div>
))}
</dl>
</div>
</div>
<div className="ft-bottom">
<div className="ft-bottom-l">
<span className="mono mute">{t.footer.year}</span>
<span className="mono mute ft-dot">·</span>
<span className="mono mute">{lang === 'fa' ? 'تمام حقوق محفوظ است' : 'All rights reserved'}</span>
</div>
<div className="ft-bottom-c">
<span className="mono mute">{t.footer.sig}</span>
</div>
<button className="ft-top-btn mono" onClick={() => { if (window.__lenis) { window.__lenis.scrollTo(0); } else { window.scrollTo({ top: 0, behavior: 'smooth' }); } }}>
{top}
</button>
</div>
</div>
<style>{`
.ft { padding: clamp(40px, 6vw, 80px) 0 32px; border-top: 1px solid var(--rule); background: var(--bg); position: relative; }
.ft-top { display: grid; grid-template-columns: 1.6fr 0.8fr 1fr 1.2fr; gap: clamp(28px, 4vw, 56px); padding-bottom: clamp(40px, 5vw, 64px); }
@media (max-width: 980px) { .ft-top { grid-template-columns: 1fr 1fr; } .ft-sig { grid-column: 1 / -1; } }
@media (max-width: 560px) { .ft-top { grid-template-columns: 1fr; } }
.ft-col { display: flex; flex-direction: column; gap: 18px; }
.ft-h { color: var(--accent); letter-spacing: 0.16em; }
.ft-brand { display: flex; align-items: center; gap: 12px; }
.ft-brand .brand-mark { width: 26px; height: 26px; }
.ft-brand-text { display: flex; flex-direction: column; gap: 2px; }
.ft-brand-a { font-family: var(--serif); font-style: italic; font-size: 22px; line-height: 1; color: var(--ink); }
html[lang="fa"] .ft-brand-a { font-family: var(--farsi-display); font-style: normal; font-size: 26px; }
.ft-brand-b { font-size: 10px; }
.ft-quote { margin: 0; font-size: clamp(18px, 1.8vw, 22px); line-height: 1.5; color: var(--ink); max-width: 36ch; }
html[lang="fa"] .ft-quote { font-family: var(--farsi-serif); font-weight: 500; font-style: normal; }
.ft-mail { display: inline-flex; align-items: center; gap: 10px; padding: 12px 18px; border: 1px solid var(--ink); border-radius: 999px; color: var(--ink); font-size: 16px; transition: background 0.25s, border-color 0.25s, color 0.25s, transform 0.12s; align-self: flex-start; }
.ft-mail:active { transform: scale(0.96); }
.ft-mail:hover { background: var(--ink); color: var(--bg); }
.ft-mail .serif { font-family: var(--serif); font-style: italic; }
html[lang="fa"] .ft-mail .serif { font-family: var(--farsi-serif); font-style: normal; font-weight: 500; }
.ft-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ft-list li a { display: flex; align-items: baseline; gap: 12px; padding: 6px 0; font-size: 15px; color: var(--ink); transition: color 0.2s, border-color 0.2s; border-bottom: 1px solid transparent; }
.ft-list li a:hover { color: var(--accent); padding-left: 4px; border-bottom-color: var(--rule); }
html[lang="fa"] .ft-list li a:hover { padding-left: 0; padding-right: 4px; }
.ft-num { color: var(--ink-mute); font-size: 10px; min-width: 22px; }
.ft-handle { font-size: 11px; margin-left: auto; }
html[lang="fa"] .ft-handle { margin-left: 0; margin-right: auto; }
.ft-meta { margin: 0; display: flex; flex-direction: column; gap: 10px; }
.ft-meta-row { display: flex; flex-direction: column; gap: 2px; }
.ft-meta-row dt { font-size: 10px; letter-spacing: 0.12em; }
.ft-meta-row dd { margin: 0; font-size: 12px; color: var(--ink); letter-spacing: 0.02em; }
.ft-bottom { display: grid; grid-template-columns: 1fr auto auto; gap: 18px; align-items: center; padding-top: 22px; border-top: 1px solid var(--rule); font-size: 11px; }
@media (max-width: 720px) { .ft-bottom { grid-template-columns: 1fr; gap: 10px; text-align: start; } .ft-top-btn { justify-self: flex-start; } }
.ft-bottom-l { display: flex; align-items: center; gap: 10px; }
.ft-bottom-c { color: var(--ink-mute); justify-self: center; text-align: center; }
@media (max-width: 720px) { .ft-bottom-c { justify-self: flex-start; text-align: start; } }
.ft-dot { opacity: 0.5; }
.ft-top-btn { padding: 8px 14px; border: 1px solid var(--rule); border-radius: 999px; background: transparent; color: var(--ink-soft); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; transition: border-color 0.2s, color 0.2s, transform 0.12s; }
.ft-top-btn:active { transform: scale(0.93); }
html[lang="fa"] .ft-top-btn { text-transform: none; }
.ft-top-btn:hover { color: var(--ink); border-color: var(--ink); background: var(--bg-elev); }
`}</style>
</footer>
);
}
Object.assign(window, {
Icon, useReveal, useCountUp, Hero, About, Expertise, Projects, Writing, StatsBar, Contact, Footer,
});
// Extras: polish + new sections
const { useState: _useState, useEffect: _useEffect, useRef: _useRef } = React;
// ─── Reading progress bar (top of page) ──────────────────────────────────────
function ReadingProgress() {
const [p, setP] = _useState(0);
_useEffect(() => {
const onScroll = () => {
const h = document.documentElement;
const max = h.scrollHeight - h.clientHeight;
setP(max > 0 ? Math.min(1, h.scrollTop / max) : 0);
};
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
<div className="rp" aria-hidden="true">
<div className="rp-fill" style={{ transform: `scaleX(${p})` }} />
<style>{`
.rp { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 100; background: transparent; pointer-events: none; }
.rp-fill { height: 100%; background: var(--accent); transform-origin: left center; transition: transform 0.08s linear; }
html[lang="fa"] .rp-fill { transform-origin: right center; }
`}</style>
</div>
);
}
// ─── Live Tehran time (HH:MM:SS) ──────────────────────────────────────────────
function LiveTime({ lang }) {
const [now, setNow] = _useState(() => new Date());
_useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
// Format in Tehran (UTC+3:30)
const fmt = new Intl.DateTimeFormat(lang === 'fa' ? 'fa-IR' : 'en-GB', {
timeZone: 'Asia/Tehran',
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false,
});
return <span className="mono live-time">{fmt.format(now)}</span>;
}
// ─── Availability dot ────────────────────────────────────────────────────────
function AvailabilityPill({ t }) {
return (
<span className="avail">
<span className="avail-dot" aria-hidden="true">
<span className="avail-pulse" />
</span>
<span className="mono avail-label">{t.nav_status.available}</span>
<style>{`
.avail { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px 6px 8px; border: 1px solid var(--rule); border-radius: 999px; }
.avail-dot { position: relative; width: 8px; height: 8px; border-radius: 50%; background: #2ea862; }
.avail-pulse { position: absolute; inset: -4px; border-radius: 50%; background: #2ea86266; animation: availPulse 2.2s ease-out infinite; }
@keyframes availPulse { 0% { transform: scale(0.6); opacity: 0.9; } 100% { transform: scale(1.8); opacity: 0; } }
.avail-label { color: var(--ink-soft); }
`}</style>
</span>
);
}
// ─── Keyword marquee (between hero and about) ────────────────────────────────
function Marquee({ items, accent, dir }) {
// Duplicate items so the strip is seamless
const row = items.concat(items);
return (
<div className="mq" aria-hidden="true">
<div className={"mq-track" + (dir === 'rtl' ? ' rtl' : '')}>
{row.map((it, i) => (
<span className="mq-item" key={i}>
<span className="mq-text">{it}</span>
<span className="mq-glyph" style={{ color: accent }}>✦</span>
</span>
))}
</div>
<style>{`
.mq { position: relative; overflow: hidden; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); padding: 22px 0; background: var(--bg); -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 7%, black 93%, transparent 100%); mask-image: linear-gradient(90deg, transparent 0%, black 7%, black 93%, transparent 100%); }
.mq-track { display: flex; gap: 36px; width: max-content; animation: mqScroll 60s linear infinite; }
.mq-track.rtl { animation-direction: reverse; }
@keyframes mqScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.mq-item { display: inline-flex; align-items: center; gap: 36px; font-family: var(--serif); font-style: italic; font-size: clamp(28px, 4vw, 48px); letter-spacing: -0.01em; color: var(--ink); white-space: nowrap; }
html[lang="fa"] .mq-item { font-family: var(--farsi); font-style: normal; font-weight: 400; font-size: clamp(22px, 3.2vw, 38px); }
.mq-glyph { font-family: var(--sans); font-style: normal; font-size: 0.55em; transform: translateY(-0.1em); }
`}</style>
</div>
);
}
// ─── Testimonials ──────────────────────────────────────────────────────────────
function Testimonials({ t }) {
return (
<section className="section" id="testimonials">
<div className="wrap">
<SectionHead num={t.testimonials.num} title={t.testimonials.title} em={t.testimonials.titleEm} />
<div className="tm-grid">
{t.testimonials.items.map((item, i) => (
<div key={i} className="tm-card" data-reveal style={{ transitionDelay: (i * 80) + 'ms' }}>
<blockquote className="tm-quote serif">"{item.quote}"</blockquote>
<div className="tm-author">
<div className="tm-avatar mono">{item.name.split(' ').map(w => w[0]).join('')}</div>
<div>
<div className="tm-name">{item.name}</div>
<div className="tm-role mono mute">{item.role}</div>
</div>
</div>
</div>
))}
</div>
</div>
<style>{`
.tm-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 28px; }
@media (max-width: 820px) { .tm-grid { grid-template-columns: 1fr; } }
.tm-card { border: 1px solid var(--rule); padding: 28px 24px; display: flex; flex-direction: column; gap: 20px; background: var(--bg); transition: transform 0.3s cubic-bezier(0.2,0.7,0.2,1), border-color 0.3s; }
.tm-card:hover { transform: translateY(-3px); border-color: var(--ink-mute); }
.tm-quote { margin: 0; font-size: clamp(16px, 1.3vw, 19px); line-height: 1.55; color: var(--ink); font-style: italic; }
html[lang="fa"] .tm-quote { font-family: var(--farsi-serif); font-style: normal; font-weight: 500; }
.tm-author { display: flex; align-items: center; gap: 14px; padding-top: 16px; border-top: 1px solid var(--rule); }
.tm-avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--bg-elev); border: 1px solid var(--rule); flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 13px; color: var(--ink-soft); letter-spacing: 0.02em; }
.tm-name { font-size: 15px; font-weight: 500; color: var(--ink); }
.tm-role { font-size: 11px; letter-spacing: 0.06em; }
html[lang="fa"] .tm-role { letter-spacing: 0; }
`}</style>
</section>
);
}
// ─── Principles ──────────────────────────────────────────────────────────────
function Principles({ t }) {
return (
<section className="section" id="principles">
<div className="wrap">
<SectionHead num={t.principles.num} title={t.principles.title} em={t.principles.titleEm} />
<div className="pr-grid">
{t.principles.items.map((p, i) => (
<div key={i} className="pr-item" data-reveal style={{ transitionDelay: (i * 80) + 'ms' }}>
<div className="pr-n serif">{p.n}.</div>
<h3 className="pr-title">{p.title}</h3>
<p className="pr-body">{p.body}</p>
<div className="pr-rule" />
</div>
))}
</div>
</div>
<style>{`
.pr-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; border-top: 1px solid var(--rule); }
@media (max-width: 980px) { .pr-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .pr-grid { grid-template-columns: 1fr; } }
.pr-item { padding: 36px 28px 36px 24px; position: relative; border-right: 1px solid var(--rule); }
.pr-item:first-child { padding-left: 0; }
.pr-item:last-child { border-right: none; }
html[lang="fa"] .pr-item { border-right: none; border-left: 1px solid var(--rule); padding: 36px 24px 36px 28px; }
html[lang="fa"] .pr-item:first-child { padding-right: 0; }
html[lang="fa"] .pr-item:last-child { border-left: none; }
@media (max-width: 980px) {
.pr-item:nth-child(2n) { border-right: none; }
html[lang="fa"] .pr-item:nth-child(2n) { border-left: none; }
}
@media (max-width: 560px) {
.pr-item { border-right: none !important; border-left: none !important; border-bottom: 1px solid var(--rule); padding: 28px 0; }
}
.pr-n { color: var(--accent); font-size: 36px; line-height: 1; margin-bottom: 18px; letter-spacing: -0.02em; }
.pr-title { margin: 0 0 12px; font-size: 18px; font-weight: 500; letter-spacing: -0.01em; line-height: 1.25; color: var(--ink); }
.pr-body { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink-soft); }
`}</style>
</section>
);
}
// ─── Toolkit specimen strip ──────────────────────────────────────────────────
function Toolkit({ t }) {
return (
<section className="section tk-section" id="toolkit" style={{ paddingTop: 'clamp(40px,5vw,72px)', paddingBottom: 'clamp(40px,5vw,72px)' }}>
<div className="wrap">
<div className="tk-head" data-reveal>
<span className="mono mute">{t.toolkit.kicker}</span>
<h3 className="tk-title">{t.toolkit.title}</h3>
</div>
<div className="tk-grid" data-reveal>
{t.toolkit.groups.map((g, i) => (
<div key={i} className="tk-group">
<div className="mono tk-label">{g.label}</div>
<div className="tk-chips">
{g.items.map((it, j) => (
<span key={j} className="tk-chip">{it}</span>
))}
</div>
</div>
))}
</div>
<div className="tk-note serif" data-reveal>{t.toolkit.note}</div>
</div>
<style>{`
.tk-section { background: var(--bg-elev); border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.tk-head { display: flex; align-items: baseline; gap: 18px; margin-bottom: 24px; }
.tk-title { margin: 0; font-size: clamp(20px, 2.2vw, 26px); font-weight: 400; letter-spacing: -0.01em; }
.tk-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 28px 32px; }
@media (max-width: 980px) { .tk-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .tk-grid { grid-template-columns: 1fr; } }
.tk-group { display: flex; flex-direction: column; gap: 12px; }
.tk-label { color: var(--accent); }
.tk-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.tk-chip { padding: 5px 10px; border: 1px solid var(--rule); border-radius: 4px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em; color: var(--ink); background: var(--bg); transition: transform 0.2s, border-color 0.2s, color 0.2s; }
.tk-chip:hover { transform: translateY(-1px); border-color: var(--accent); color: var(--accent); }
html[lang="fa"] .tk-chip { font-family: var(--mono); }
.tk-note { margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--rule); font-size: clamp(14px, 1.1vw, 16px); color: var(--ink-soft); font-style: italic; }
html[lang="fa"] .tk-note { font-family: var(--farsi); font-style: normal; }
`}</style>
</section>
);
}
// ─── Now widget ──────────────────────────────────────────────────────────────
function Now({ t }) {
return (
<section className="section now-section" id="now" style={{ paddingTop: 'clamp(40px,5vw,80px)', paddingBottom: 'clamp(40px,5vw,80px)' }}>
<div className="wrap">
<div className="now-card" data-reveal>
<div className="now-head">
<div>
<span className="mono accent">{t.now.kicker}</span>
<h3 className="now-title">{t.now.title} <span className="serif">{t.now.titleEm}</span></h3>
</div>
<div className="mono mute">{t.now.asof}</div>
</div>
<dl className="now-list">
{t.now.items.map((it, i) => (
<div key={i} className="now-row">
<dt className="mono">{it.k}</dt>
<dd>{it.v}</dd>
</div>
))}
</dl>
</div>
</div>
<style>{`
.now-card { border: 1px solid var(--rule); padding: clamp(28px, 4vw, 48px); background: var(--bg); position: relative; }
.now-card::before { content: ''; position: absolute; left: 0; right: 0; top: -1px; height: 2px; background: linear-gradient(90deg, var(--accent) 0%, var(--accent) 30%, transparent 30%); }
.now-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; margin-bottom: 28px; flex-wrap: wrap; }
.now-title { margin: 8px 0 0; font-size: clamp(28px, 4vw, 44px); font-weight: 300; letter-spacing: -0.02em; line-height: 1; }
.now-list { margin: 0; padding: 0; display: flex; flex-direction: column; }
.now-row { display: grid; grid-template-columns: 160px 1fr; gap: 24px; align-items: baseline; padding: 16px 0; border-top: 1px solid var(--rule); }
.now-row:last-child { border-bottom: 1px solid var(--rule); }
.now-row dt { color: var(--ink-mute); }
.now-row dd { margin: 0; font-size: clamp(16px, 1.3vw, 19px); color: var(--ink); line-height: 1.5; }
@media (max-width: 640px) { .now-row { grid-template-columns: 1fr; gap: 6px; } }
`}</style>
</section>
);
}
// ─── Big footer wordmark ─────────────────────────────────────────────────────
function BigWordmark({ lang }) {
const text = lang === 'fa' ? 'آرسام صباغ' : 'ARSAM SABBAGH';
return (
<div className="bw" aria-hidden="true">
<div className="wrap">
<div className="bw-text">{text}</div>
<div className="bw-foot">
<span className="mono mute">{lang === 'fa' ? 'پایانِ مدرک' : 'End of document'}</span>
<span className="mono mute">{lang === 'fa' ? '— ۲۰۲۶ —' : '— 2026 —'}</span>
</div>
</div>
<style>{`
.bw { padding: clamp(40px, 8vw, 100px) 0 0; border-top: 1px solid var(--rule); overflow: hidden; }
.bw-text { font-family: var(--sans); font-weight: 300; font-size: clamp(40px, 10vw, 164px); letter-spacing: -0.05em; line-height: 0.85; color: var(--ink); white-space: nowrap; cursor: default; transition: letter-spacing 0.8s cubic-bezier(0.2,0.7,0.2,1); }
.bw-text:hover { letter-spacing: 0.12em; }
html[lang="fa"] .bw-text { font-family: var(--farsi); font-weight: 600; letter-spacing: -0.01em; }
html[lang="fa"] .bw-text:hover { letter-spacing: -0.01em; }
.bw-foot { display: flex; justify-content: space-between; padding: 24px 0; border-top: 1px solid var(--rule); margin-top: clamp(20px, 4vw, 48px); }
`}</style>
</div>
);
}
// ─── Page-number gutter (magazine style) ─────────────────────────────────────
function PageNumber({ n, total = '06' }) {
return (
<div className="pgn mono" aria-hidden="true">
<span>{n}</span>
<span className="mute">/</span>
<span className="mute">{total}</span>
<style>{`
.pgn { position: absolute; top: 32px; right: 32px; display: flex; gap: 4px; color: var(--ink); font-size: 11px; letter-spacing: 0.16em; z-index: 2; }
html[lang="fa"] .pgn { right: auto; left: 32px; }
`}</style>
</div>
);
}
// ─── Command palette (⌘K) ────────────────────────────────────────────────────
function CommandPalette({ items, onClose, onLang, onMode, lang }) {
const [q, setQ] = _useState('');
const [idx, setIdx] = _useState(0);
const inputRef = _useRef(null);
_useEffect(() => { inputRef.current?.focus(); }, []);
const filtered = items.filter((it) => it.label.toLowerCase().includes(q.toLowerCase()) || (it.sub || '').toLowerCase().includes(q.toLowerCase()));
_useEffect(() => { setIdx(0); }, [q]);
_useEffect(() => {
const onKey = (e) => {
if (e.key === 'Escape') { onClose(); }
else if (e.key === 'ArrowDown') { e.preventDefault(); setIdx((i) => Math.min(filtered.length - 1, i + 1)); }
else if (e.key === 'ArrowUp') { e.preventDefault(); setIdx((i) => Math.max(0, i - 1)); }
else if (e.key === 'Enter') { e.preventDefault(); filtered[idx]?.run(); onClose(); }
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [filtered, idx, onClose]);
return (
<div className="cmdk" onClick={onClose}>
<div className="cmdk-inner" onClick={(e) => e.stopPropagation()}>
<input ref={inputRef} value={q} onChange={(e) => setQ(e.target.value)} placeholder={lang === 'fa' ? 'یک بخش، تم یا زبان تایپ کنید…' : 'Type a section, theme, or language…'} />
<div className="cmdk-list">
{filtered.length === 0 && <div className="cmdk-empty mono mute">{lang === 'fa' ? 'نتیجه‌ای نیست' : 'No results'}</div>}
{filtered.map((it, i) => (
<button
key={it.id}
className={"cmdk-row " + (i === idx ? 'on' : '')}
onMouseEnter={() => setIdx(i)}
onClick={() => { it.run(); onClose(); }}
>
<span className="cmdk-glyph">{it.glyph}</span>
<span className="cmdk-label">{it.label}</span>
{it.sub && <span className="cmdk-sub mono">{it.sub}</span>}
<span className="cmdk-arrow"><Icon name="arrow" size={14} /></span>
</button>
))}
</div>
<div className="cmdk-foot mono mute">
<span>↑↓ {lang === 'fa' ? 'حرکت' : 'navigate'}</span>
<span>↵ {lang === 'fa' ? 'انتخاب' : 'select'}</span>
<span>esc {lang === 'fa' ? 'بستن' : 'close'}</span>
</div>
</div>
<style>{`
.cmdk { position: fixed; inset: 0; z-index: 200; background: color-mix(in oklab, var(--bg) 60%, transparent); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); display: flex; align-items: flex-start; justify-content: center; padding-top: 14vh; animation: cmdkFade 0.18s ease both; }
@keyframes cmdkFade { from { opacity: 0; } to { opacity: 1; } }
.cmdk-inner { width: min(540px, calc(100% - 32px)); background: var(--bg); border: 1px solid var(--rule); box-shadow: 0 28px 64px -16px color-mix(in oklab, var(--ink) 18%, transparent); animation: cmdkPop 0.22s cubic-bezier(0.2,0.7,0.2,1) both; }
@keyframes cmdkPop { from { transform: translateY(-8px); opacity: 0; } to { transform: none; opacity: 1; } }
.cmdk-inner input { width: 100%; padding: 18px 22px; background: transparent; border: none; outline: none; border-bottom: 1px solid var(--rule); font: inherit; font-size: 17px; color: var(--ink); font-family: var(--sans); }
html[lang="fa"] .cmdk-inner input { font-family: var(--farsi); }
.cmdk-inner input::placeholder { color: var(--ink-mute); }
.cmdk-list { max-height: 50vh; overflow-y: auto; padding: 6px; }
.cmdk-row { display: grid; grid-template-columns: 28px 1fr auto auto; gap: 12px; align-items: center; width: 100%; padding: 10px 14px; border: none; background: transparent; color: var(--ink); text-align: start; border-radius: 0; transition: background 0.12s; cursor: pointer; }
.cmdk-row.on { background: var(--bg-elev); }
.cmdk-row.on .cmdk-arrow { color: var(--accent); transform: translateX(2px); }
html[lang="fa"] .cmdk-row.on .cmdk-arrow { transform: translateX(-2px); }
.cmdk-glyph { color: var(--accent); font-family: var(--mono); font-size: 12px; text-align: center; }
.cmdk-label { font-size: 15px; }
.cmdk-sub { color: var(--ink-mute); }
.cmdk-arrow { color: var(--ink-mute); transition: transform 0.2s, color 0.2s; }
html[lang="fa"] .cmdk-arrow svg { transform: scaleX(-1); }
.cmdk-empty { padding: 22px; text-align: center; }
.cmdk-foot { display: flex; gap: 18px; padding: 12px 18px; border-top: 1px solid var(--rule); font-size: 10px; }
`}</style>
</div>
);
}
function CmdKHint({ onOpen, lang, t }) {
return (
<button className="cmdk-hint" onClick={onOpen} aria-label="Open command palette">
<span className="mono">{t.cmdk.hint}</span>
<kbd className="mono">{t.cmdk.key}</kbd>
<span className="mono mute">{t.cmdk.label}</span>
<style>{`
.cmdk-hint { position: fixed; bottom: 24px; left: 24px; display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px; border: 1px solid var(--rule); border-radius: 999px; background: color-mix(in oklab, var(--bg) 80%, transparent); backdrop-filter: blur(8px); color: var(--ink-soft); font-size: 11px; z-index: 40; transition: all 0.2s; }
.cmdk-hint:hover { color: var(--ink); border-color: var(--ink-mute); }
html[lang="fa"] .cmdk-hint { left: auto; right: 24px; }
.cmdk-hint kbd { padding: 2px 6px; border: 1px solid var(--rule); border-radius: 4px; background: var(--bg-elev); color: var(--ink); font-size: 10px; }
@media (max-width: 720px) { .cmdk-hint { display: none; } }
`}</style>
</button>
);
}
// ─── Vertical side rail text (for hero edge) ─────────────────────────────────
function SideRail({ text }) {
return (
<div className="side-rail mono mute" aria-hidden="true">
<span>{text}</span>
<style>{`
.side-rail { position: absolute; left: 18px; top: 50%; transform: rotate(-90deg) translateX(50%); transform-origin: left center; font-size: 10px; letter-spacing: 0.4em; white-space: nowrap; z-index: 2; }
html[lang="fa"] .side-rail { left: auto; right: 18px; transform: rotate(90deg) translateX(-50%); transform-origin: right center; }
@media (max-width: 980px) { .side-rail { display: none; } }
`}</style>
</div>
);
}
// ─── Custom Typographic Cursor ───────────────────────────────────────────────
function CustomCursor({ lang }) {
const containerRef = React.useRef(null);
const [hoverType, setHoverType] = React.useState('');
const [hoverText, setHoverText] = React.useState('');
const isVisible = React.useRef(false);
React.useEffect(() => {
const isFinePointer = window.matchMedia('(pointer: fine)').matches;
if (!isFinePointer) return;
const onPointerMove = (e) => {
const x = e.clientX;
const y = e.clientY;
if (!isVisible.current) {
isVisible.current = true;
if (containerRef.current) {
containerRef.current.style.opacity = '1';
}
}
if (containerRef.current) {
containerRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`;
}
};
const onMouseLeave = () => {
isVisible.current = false;
if (containerRef.current) {
containerRef.current.style.opacity = '0';
}
};
window.addEventListener('pointermove', onPointerMove, { passive: true });
document.addEventListener('mouseleave', onMouseLeave);
const handleMouseOver = (e) => {
const targetEl = e.target;
if (!targetEl) return;
const isProject = targetEl.closest('.proj-card, .proj-thumb');
if (isProject) {
setHoverType('project');
setHoverText(lang === 'fa' ? 'نمایش' : 'View');
return;
}
const isToggle = targetEl.closest('.mode-toggle');
if (isToggle) {
setHoverType('toggle');
setHoverText(lang === 'fa' ? 'تم' : 'Theme');
return;
}
const isCursorToggle = targetEl.closest('.cursor-toggle');
if (isCursorToggle) {
setHoverType('toggle');
setHoverText(lang === 'fa' ? 'نشانه گر' : 'Cursor');
return;
}
const isClickable = targetEl.closest('a, button, select, input, [role="button"], [role="radio"]');
if (isClickable) {
setHoverType('normal');
setHoverText('');
return;
}
setHoverType('');
setHoverText('');
};
document.addEventListener('mouseover', handleMouseOver);
return () => {
window.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('mouseleave', onMouseLeave);
document.removeEventListener('mouseover', handleMouseOver);
};
}, [lang]);
return (
<div
ref={containerRef}
className="custom-cursor-container"
style={{ opacity: 0 }}
>
<div
className={`custom-cursor ${hoverType ? (hoverType === 'normal' ? 'hovering' : `hover-${hoverType}`) : ''}`}
>
<span className="custom-cursor-text">{hoverText}</span>
</div>
<div
className="custom-cursor-dot"
/>
</div>
);
}
Object.assign(window, {
ReadingProgress, LiveTime, AvailabilityPill, Marquee, Principles, Toolkit, Now, BigWordmark, PageNumber, CommandPalette, CmdKHint, SideRail, CustomCursor,
});
// Root app
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"accent": "#0F6B73",
"headingStyle": "mixed",
"density": "spacious",
"mode": "light"
}/*EDITMODE-END*/;
function Nav({ t, lang, onLang, mode, onMode, cursorEnabled, onToggleCursor, onNav, activeSection }) {
const [menuOpen, setMenuOpen] = React.useState(false);
const close = () => setMenuOpen(false);
const handleNav = (id) => { close(); onNav(id); };
return (
<nav className="nav">
<div className="nav-inner">
<a href="#top" className="brand" onClick={(e) => { e.preventDefault(); close(); if (window.__lenis) { window.__lenis.scrollTo(0); } else { window.scrollTo({ top: 0, behavior: 'smooth' }); } }}>
<span className="brand-mark"/>
<span>{lang === 'fa' ? 'آرسام صباغ' : 'Arsam Sabbagh'}</span>
</a>
<div className="nav-links">
{t.nav.links.map((l) => (
<a key={l.id} href={'#' + l.id} className={activeSection === l.id ? 'active' : ''} onClick={(e) => { e.preventDefault(); onNav(l.id); }}>{l.label}</a>
))}
</div>
<div className="nav-actions">
<button className="mode-toggle" onClick={onMode} title="Toggle dark mode" aria-label="Toggle theme">
<Icon name={mode === 'dark' ? 'sun' : 'moon'} size={13}/>
</button>
<button className="cursor-toggle" onClick={onToggleCursor} title={cursorEnabled ? (lang === 'fa' ? 'غیرفعال کردن نشانه گر' : 'Disable custom cursor') : (lang === 'fa' ? 'فعال کردن نشانه گر' : 'Enable custom cursor')} aria-label="Toggle custom cursor">
<Icon name={cursorEnabled ? 'cursor' : 'cursor-off'} size={13}/>
</button>
<button className={`nav-hamburger${menuOpen ? ' open' : ''}`} onClick={() => setMenuOpen(o => !o)} aria-label="Menu" aria-expanded={menuOpen}>
<span/><span/><span/>
</button>
</div>
</div>
<div className={`nav-mobile${menuOpen ? ' open' : ''}`}>
{t.nav.links.map((l) => (
<a key={l.id} href={'#' + l.id} onClick={(e) => { e.preventDefault(); handleNav(l.id); }}>{l.label}</a>
))}
</div>
</nav>
);
}
function App() {
const [tw, setTweak] = useTweaks(TWEAK_DEFAULTS);
const [lang, setLang] = React.useState('en');
const [mode, setMode] = React.useState(tw.mode || 'light');
const [cmdOpen, setCmdOpen] = React.useState(false);
const [activeSection, setActiveSection] = React.useState('');
const [cursorEnabled, setCursorEnabled] = React.useState(() => {
const saved = localStorage.getItem('customCursorEnabled');
return saved !== 'false';
});
const t = CONTENT[lang];
// Apply custom cursor toggle
React.useEffect(() => {
if (cursorEnabled) {
document.documentElement.classList.remove('custom-cursor-disabled');
localStorage.setItem('customCursorEnabled', 'true');
} else {
document.documentElement.classList.add('custom-cursor-disabled');
localStorage.setItem('customCursorEnabled', 'false');
}
}, [cursorEnabled]);
const toggleCursor = () => setCursorEnabled(e => !e);
// Language transition states
const [isFlipping, setIsFlipping] = React.useState(false);
const [flipDir, setFlipDir] = React.useState('');
const handleLangChange = (targetLang) => {
if (targetLang === lang || isFlipping) return;
const dir = targetLang === 'fa' ? 'to-rtl' : 'to-ltr';
setFlipDir(dir);
setIsFlipping(true);
setTimeout(() => {
setLang(targetLang);
}, 400);
setTimeout(() => {
setIsFlipping(false);
setFlipDir('');
}, 800);
};
// Apply lang + dir
React.useEffect(() => {
document.documentElement.lang = lang;
document.documentElement.dir = CONTENT[lang].dir;
}, [lang]);
// Apply dark mode
React.useEffect(() => {
document.documentElement.classList.toggle('dark', mode === 'dark');
}, [mode]);
// Apply accent
React.useEffect(() => {
document.documentElement.style.setProperty('--accent', tw.accent);
}, [tw.accent]);
// Apply density
React.useEffect(() => {
const root = document.documentElement;
if (tw.density === 'tight') root.style.setProperty('--max', '1080px');
else if (tw.density === 'wide') root.style.setProperty('--max', '1320px');
else root.style.setProperty('--max', '1200px');
}, [tw.density]);
// Heading style toggle
React.useEffect(() => {
const id = 'heading-style-override';
let el = document.getElementById(id);
if (!el) { el = document.createElement('style'); el.id = id; document.head.appendChild(el); }
if (tw.headingStyle === 'serif') el.textContent = `.section-head h2, .hero-name { font-family: var(--serif) !important; font-style: italic !important; font-weight: 400 !important; }`;
else if (tw.headingStyle === 'sans') el.textContent = `.section-head h2 em, .hero-name .hero-amp { font-family: var(--sans) !important; font-style: normal !important; }`;
else el.textContent = '';
}, [tw.headingStyle]);
useReveal();
// Lenis smooth scroll
const lenisRef = React.useRef(null);
React.useEffect(() => {
if (typeof Lenis === 'undefined') return;
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smoothWheel: true,
wheelMultiplier: 1,
touchMultiplier: 2,
});
lenisRef.current = lenis;
window.__lenis = lenis;
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
return () => {
lenis.destroy();
delete window.__lenis;
};
}, []);
// Track active section for nav highlighting
React.useEffect(() => {
const ids = ['about', 'testimonials', 'principles', 'expertise', 'projects', 'toolkit', 'writing', 'now', 'contact'];
const obs = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => { if (entry.isIntersecting) setActiveSection(entry.target.id); });
},
{ rootMargin: '-30% 0px -65% 0px' }
);
ids.forEach((id) => { const el = document.getElementById(id); if (el) obs.observe(el); });
return () => obs.disconnect();
}, []);
const handleNav = (id) => {
const el = document.getElementById(id);
if (el) {
const y = el.getBoundingClientRect().top + window.scrollY - 90;
if (lenisRef.current) {
lenisRef.current.scrollTo(y, { duration: 1.4, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)) });
} else {
window.scrollTo({ top: y, behavior: 'smooth' });
}
}
};
const toggleLang = () => handleLangChange(lang === 'en' ? 'fa' : 'en');
const toggleMode = () => {
const next = mode === 'dark' ? 'light' : 'dark';
setMode(next);
setTweak('mode', next);
};
// Cmd+K listener
React.useEffect(() => {
const onKey = (e) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
e.preventDefault();
setCmdOpen((o) => !o);
}
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, []);
const cmdItems = [
{ id: 'about', glyph: '§', label: lang === 'fa' ? 'بخش: درباره' : 'Go to · About', sub: '01', run: () => handleNav('about') },
{ id: 'testimonials', glyph: '§', label: lang === 'fa' ? 'بخش: توصیه‌نامه‌ها' : 'Go to · Testimonials', sub: '02', run: () => handleNav('testimonials') },
{ id: 'principles', glyph: '§', label: lang === 'fa' ? 'بخش: اصول' : 'Go to · Principles', sub: '—', run: () => handleNav('principles') },
{ id: 'expertise', glyph: '§', label: lang === 'fa' ? 'بخش: تخصص' : 'Go to · Expertise', sub: '03', run: () => handleNav('expertise') },
{ id: 'projects', glyph: '§', label: lang === 'fa' ? 'بخش: پروژه‌ها' : 'Go to · Projects', sub: '04', run: () => handleNav('projects') },
{ id: 'toolkit', glyph: '§', label: lang === 'fa' ? 'بخش: جعبه‌ابزار' : 'Go to · Toolkit', sub: '—', run: () => handleNav('toolkit') },
{ id: 'writing', glyph: '§', label: lang === 'fa' ? 'بخش: نوشته‌ها' : 'Go to · Writing', sub: '05', run: () => handleNav('writing') },
{ id: 'now', glyph: '§', label: lang === 'fa' ? 'بخش: اکنون' : 'Go to · Now', sub: '/now', run: () => handleNav('now') },
{ id: 'contact', glyph: '§', label: lang === 'fa' ? 'بخش: تماس' : 'Go to · Contact', sub: '06', run: () => handleNav('contact') },
{ id: 'theme', glyph: '◐', label: lang === 'fa' ? 'تغییر تم (روشن/تاریک)' : 'Toggle theme · Light/Dark', sub: mode, run: toggleMode },
{ id: 'cursor', glyph: '⬈', label: lang === 'fa' ? 'تغییر وضعیت نشانه‌گر اختصاصی' : 'Toggle custom cursor · On/Off', sub: cursorEnabled ? 'ON' : 'OFF', run: toggleCursor },
{ id: 'github', glyph: '↗', label: 'GitHub · Coaxys-A', sub: 'open', run: () => window.open('https://github.com/Coaxys-A', '_blank') },
{ id: 'telegram', glyph: '↗', label: 'Telegram · t.me/Coaxys', sub: 'open', run: () => window.open('https://t.me/Coaxys', '_blank') },
{ id: 'teknav', glyph: '↗', label: 'Teknav · @arsam-sabbagh', sub: 'open', run: () => window.open('https://teknav.ir/profile/@arsam-sabbagh', '_blank') },
];
return (
<>
{cursorEnabled && <CustomCursor lang={lang} />}
<div className={`lang-flip-overlay${flipDir ? ' active-' + flipDir : ''}`} />
<ReadingProgress/>
<Nav t={t} lang={lang} onLang={toggleLang} mode={mode} onMode={toggleMode} cursorEnabled={cursorEnabled} onToggleCursor={toggleCursor} onNav={handleNav} activeSection={activeSection}/>
<main className="fadeIn" key={lang}>
<Hero t={t} accent={tw.accent} terra={mode === 'dark' ? '#D67B5C' : '#B7593C'} onNav={handleNav}/>
<Marquee items={t.marquee} accent={tw.accent} dir={CONTENT[lang].dir}/>
<About t={t}/>
<Testimonials t={t}/>
<Principles t={t}/>
<Expertise t={t}/>
<Projects t={t} lang={lang}/>
<Toolkit t={t}/>
<StatsBar t={t} lang={lang}/>
<Writing t={t} lang={lang}/>
<Now t={t}/>
<Contact t={t}/>
<BigWordmark lang={lang}/>
<Footer t={t} lang={lang} onNav={handleNav}/>
</main>
<CmdKHint onOpen={() => setCmdOpen(true)} lang={lang} t={t}/>
{cmdOpen && <CommandPalette items={cmdItems} onClose={() => setCmdOpen(false)} lang={lang} onLang={toggleLang} onMode={toggleMode}/>}
<TweaksPanel title="Tweaks">
<TweakSection label="Theme">
<TweakColor label="Accent" value={tw.accent} onChange={(v) => setTweak('accent', v)} options={['#0F6B73', '#1F5C8B', '#7A1F2B', '#B7593C', '#3A6B40']}/>
<TweakRadio label="Mode" value={mode} onChange={(v) => { setMode(v); setTweak('mode', v); }} options={[{ label: 'Light', value: 'light' }, { label: 'Dark', value: 'dark' }]}/>
</TweakSection>
<TweakSection label="Type">
<TweakRadio label="Headings" value={tw.headingStyle} onChange={(v) => setTweak('headingStyle', v)} options={[{ label: 'Mixed', value: 'mixed' }, { label: 'Sans', value: 'sans' }, { label: 'Serif', value: 'serif' }]}/>
</TweakSection>
<TweakSection label="Layout">
<TweakRadio label="Density" value={tw.density} onChange={(v) => setTweak('density', v)} options={[{ label: 'Tight', value: 'tight' }, { label: 'Spacious', value: 'spacious' }, { label: 'Wide', value: 'wide' }]}/>
</TweakSection>
<TweakSection label="Language">
<TweakRadio label="Locale" value={lang} onChange={(v) => handleLangChange(v)} options={[{ label: 'English', value: 'en' }, { label: 'فارسی', value: 'fa' }]}/>
</TweakSection>
</TweaksPanel>
</>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
</body>
</html>