Rewrite: Next.js → ASP.NET Core 10 Razor Pages
deploy / deploy (push) Failing after 1m21s

Full rewrite of the portfolio site from Next.js 14 to .NET 10:

- ASP.NET Core 10 Razor Pages, no Node.js dependency
- EF Core 10 + SQLite (same schema as before — data survives upgrade)
- Cookie authentication (same single-password model)
- Resend contact form via HttpClient
- Bilingual FA/EN via locale cookie + BasePageModel
- All UI ported to Razor Pages with Tailwind CDN + custom CSS
- Vanilla JS: particles, typewriter, cursor, animations, portfolio modal
- Dockerfile: SDK 10.0-alpine → aspnet 10.0-alpine (no npm/Node needed)
- CI/CD: dropped NPM_TOKEN, ADMIN_SESSION_SECRET — pure dotnet publish

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-01 07:46:56 +03:30
parent bcea9dc2f6
commit 1b3a8b493e
111 changed files with 2409 additions and 14062 deletions
+550
View File
@@ -0,0 +1,550 @@
@page
@model SoroushAsadi.Pages.IndexModel
@{
var fa = Model.IsFa;
var locale = Model.Locale;
}
<!-- ─── HERO ─────────────────────────────────────────────────────────── -->
<section id="top" class="relative isolate overflow-hidden min-h-[100svh] pt-28 pb-20 sm:pt-32">
<!-- Particle canvas -->
<div class="pointer-events-none absolute inset-0 -z-10">
<canvas id="particle-canvas" class="h-full w-full opacity-60"></canvas>
<div aria-hidden class="absolute inset-x-0 bottom-0 h-40 bg-gradient-to-b from-transparent to-base"></div>
</div>
<!-- Aurora background -->
<div aria-hidden class="pointer-events-none absolute inset-0 -z-20"
style="background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(56,189,248,.18),transparent 60%),
radial-gradient(ellipse 60% 40% at 80% 10%,rgba(232,121,249,.10),transparent 60%),
radial-gradient(ellipse 60% 40% at 10% 30%,rgba(129,140,248,.10),transparent 60%)"></div>
<div class="mx-auto flex max-w-7xl flex-col items-center px-5 text-center sm:px-8">
<!-- Availability chip -->
<div class="mb-7 reveal">
<span class="chip">
<span class="relative inline-flex h-2 w-2">
<span class="absolute inset-0 animate-pulse-dot rounded-full bg-emerald"></span>
<span class="relative inline-block h-2 w-2 rounded-full bg-emerald"></span>
</span>
@(fa ? "پذیرش پروژه‌های منتخب فصل سوم ۲۰۲۶" : "Available for select Q3 2026 engagements")
</span>
</div>
<!-- Eyebrow -->
<p class="label-mono mb-6 inline-flex items-center gap-3 reveal" style="transition-delay:.08s">
<span class="h-px w-10 bg-electric/60" aria-hidden></span>
@(fa ? "مهندس هوش مصنوعی · مشاور · معمار راهکار" : "AI Engineer · Consultant · Solution Architect")
<span class="h-px w-10 bg-electric/60" aria-hidden></span>
</p>
<!-- Name -->
<h1 class="font-display text-balance font-extrabold leading-[1.02] tracking-tight text-white reveal @(fa ? "font-fa" : "")"
style="font-size:clamp(2.4rem,7vw,5.4rem);transition-delay:.15s">
@(fa ? "سروش اسعدی" : "Soroush Asadi")
</h1>
<!-- Headline -->
<p class="mt-5 max-w-4xl text-balance font-medium leading-[1.25] text-slate-200 reveal"
style="font-size:clamp(1.15rem,2.2vw,1.75rem);transition-delay:.25s">
@(fa ? "طراحی سامانه‌های" : "Architecting")
<span class="gradient-text font-semibold">@(fa ? "هوش مصنوعی" : "production-grade AI")</span>
@(fa ? "در مقیاس سازمانی." : "for the enterprise.")
</p>
<!-- Typewriter -->
<div class="mt-5 flex items-center gap-3 font-mono uppercase tracking-[.15em] text-slate-400 reveal"
style="font-size:clamp(.9rem,1.4vw,1.05rem);transition-delay:.35s">
<span class="h-px w-6 bg-slate-700" aria-hidden></span>
<span id="typewriter"
data-words='@(fa
? "[\"راهبرد هوش مصنوعی\",\"مهندسی LLM و RAG\",\"معماری راهکار\",\"اتوماسیون عامل‌محور\",\"استک گوگل کلود\"]"
: "[\"AI Strategy\",\"LLM & RAG Engineering\",\"Solution Architecture\",\"Agentic Automation\",\"Google Cloud Stack\"]")'></span>
<span class="inline-block w-px h-[1em] bg-electric animate-caret-blink" aria-hidden></span>
<span class="h-px w-6 bg-slate-700" aria-hidden></span>
</div>
<!-- Sub -->
<p class="mt-7 max-w-2xl text-balance leading-relaxed text-slate-400 reveal"
style="font-size:clamp(.95rem,1.4vw,1.08rem);transition-delay:.42s">
@(fa
? "از راهبرد تا تولید — ساخت پایپ‌لاین‌های LLM، عامل‌های خودکار، و معماری‌های ابری که در میلیون‌ها رویداد در روز پایدار می‌مانند."
: "From strategy to deployment — building LLM pipelines, autonomous agents, and cloud architectures that hold up at millions of events per day.")
</p>
<!-- CTAs -->
<div class="mt-9 flex flex-wrap items-center justify-center gap-3 reveal" style="transition-delay:.5s">
<a href="#contact" class="btn-primary">
@(fa ? "رزرو جلسه مشاوره" : "Book a consultation")
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" class="@(fa ? "rotate-180" : "")" aria-hidden="true"><path d="M5 12H19"/><path d="M13 6L19 12L13 18"/></svg>
</a>
<a href="#services" class="btn-ghost">@(fa ? "مشاهده خدمات" : "View services")</a>
</div>
<!-- Metrics -->
<div class="mt-16 grid w-full max-w-4xl grid-cols-2 gap-4 sm:grid-cols-4 reveal" style="transition-delay:.6s">
@{
var metrics = fa
? new[]{ ("۱۸+","مدل هوش مصنوعی مستقر","text-electric"), ("۴۰+","میکروسرویس تولید","text-violet"), ("۱۲ms","تأخیر استنتاج","text-magenta"), ("۹۹٪","پایداری SLA","text-emerald") }
: new[]{ ("18+","AI models in production","text-electric"), ("40+","microservices shipped","text-violet"), ("12ms","inference latency","text-magenta"), ("99%","SLA uptime","text-emerald") };
}
@foreach (var (val, label, color) in metrics)
{
<div class="glass relative overflow-hidden px-5 py-5 text-start">
<span aria-hidden class="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-electric/50 to-transparent"></span>
<div class="font-display font-bold leading-none @color" style="font-size:clamp(1.6rem,3vw,2.25rem)">@val</div>
<div class="mt-2 text-[.78rem] leading-snug text-slate-400">@label</div>
</div>
}
</div>
<!-- Scroll cue -->
<a href="#services" class="mt-14 inline-flex flex-col items-center gap-2 text-slate-500 transition-colors hover:text-slate-200 reveal" style="transition-delay:.75s" aria-label="@(fa ? "اسکرول" : "Scroll")">
<span class="label-mono">@(fa ? "اسکرول" : "Scroll")</span>
<span class="relative block h-9 w-5 rounded-full border border-slate-700">
<span class="absolute left-1/2 top-1.5 inline-block h-1.5 w-0.5 -translate-x-1/2 animate-float-y rounded-full bg-electric"></span>
</span>
</a>
</div>
</section>
<!-- ─── SERVICES ─────────────────────────────────────────────────────── -->
<section id="services" class="relative px-5 py-28 sm:px-8">
<div class="mx-auto max-w-7xl">
<div class="section-header">
<div class="eyebrow"><span class="chip">@(fa ? "خدمات" : "Services")</span></div>
<h2>@(fa ? "شش حوزه تخصصی" : "Six areas of practice")</h2>
<p>@(fa ? "از اولین جلسه‌ی راهبرد تا استقرار تولید — یک شریک مهندسی برای کل چرخه‌ی عمر هوش مصنوعی شما." : "From the first strategy session to production rollout — one engineering partner for the full AI lifecycle.")</p>
</div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
@{
var services = fa ? new[]{
("strategy","راهبرد و نقشه راه هوش مصنوعی","ارزیابی بلوغ سازمانی، شناسایی موارد کاربری با بیشترین بازده، و طراحی نقشه راه ۱۲–۱۸ ماهه با KPIهای روشن.","electric",new[]{"Discovery","ROI Mapping","Roadmap"}),
("automation","اتوماسیون هوش مصنوعی","ساخت عامل‌های خودکار و گردش‌کارهای n8n که فرایندهای دستی را به سامانه‌های قابل ممیزی تبدیل می‌کنند.","violet",new[]{"n8n","Agents","Workflows"}),
("llm-rag","مهندسی LLM و RAG","طراحی pipeline‌های RAG با پایگاه‌های برداری، evaluation framework، و سرویس‌دهی با تأخیر زیر ۵۰ میلی‌ثانیه.","magenta",new[]{"RAG","Vector DB","Eval"}),
("architecture","معماری راهکار","طراحی سامانه‌های توزیع‌شده روی Kubernetes با میکروسرویس‌ها، event streaming، و الگوهای پایداری در مقیاس بالا.","emerald",new[]{"K8s","Microservices","Event-Driven"}),
("mobile","اپلیکیشن‌های موبایل هوش مصنوعی","برنامه‌های Flutter، Swift و Kotlin با on-device inference، استریم LLM و تجربه‌ی کاربری بومی.","electric",new[]{"Flutter","Swift","Kotlin"}),
("google-stack","تخصص استک گوگل","استقرار روی Vertex AI، GKE و Gemini با بهینه‌سازی هزینه و الگوهای امنیتی سطح enterprise.","cyan",new[]{"Vertex AI","GKE","Gemini"}),
} : new[]{
("strategy","AI Strategy & Roadmap","Maturity assessment, highest-ROI use-case discovery, and a 1218 month roadmap with measurable KPIs.","electric",new[]{"Discovery","ROI Mapping","Roadmap"}),
("automation","AI Automation","Autonomous agents and n8n workflows that turn manual processes into auditable, observable systems.","violet",new[]{"n8n","Agents","Workflows"}),
("llm-rag","LLM & RAG Engineering","Production RAG pipelines with vector stores, evaluation frameworks, and sub-50ms serving.","magenta",new[]{"RAG","Vector DB","Eval"}),
("architecture","Solution Architecture","Distributed systems on Kubernetes — microservices, event streaming, and resilience patterns at scale.","emerald",new[]{"K8s","Microservices","Event-Driven"}),
("mobile","Mobile AI Apps","Flutter, Swift, and Kotlin apps with on-device inference, streaming LLM UX, and native polish.","electric",new[]{"Flutter","Swift","Kotlin"}),
("google-stack","Google Stack Specialist","Vertex AI, GKE, and Gemini deployments with cost optimization and enterprise security patterns.","cyan",new[]{"Vertex AI","GKE","Gemini"}),
};
int si = 0;
}
@foreach (var (id, title, desc, color, tags) in services)
{
var (ringCls, glowCls, textCls, chipCls) = color switch {
"violet" => ("group-hover:border-violet/50", "group-hover:shadow-glow-violet", "text-violet", "border-violet/30 bg-violet/5 text-violet/90"),
"magenta" => ("group-hover:border-magenta/50", "group-hover:shadow-glow-magenta", "text-magenta", "border-magenta/30 bg-magenta/5 text-magenta/90"),
"emerald" => ("group-hover:border-emerald/50", "group-hover:shadow-glow-emerald", "text-emerald", "border-emerald/30 bg-emerald/5 text-emerald/90"),
"cyan" => ("group-hover:border-cyan/50", "group-hover:shadow-glow-electric","text-cyan", "border-cyan/30 bg-cyan/5 text-cyan/90"),
_ => ("group-hover:border-electric/50","group-hover:shadow-glow-electric","text-electric","border-electric/30 bg-electric/5 text-electric/90"),
};
<article class="group relative isolate overflow-hidden p-6 sm:p-7 glass service-card reveal @ringCls @glowCls" style="transition-delay:@(si * 50)ms">
<div class="flex items-start justify-between">
<span class="label-mono">@((si + 1).ToString("D2"))</span>
<span class="@textCls">
@Html.Raw(ServiceIcon(id))
</span>
</div>
<h3 class="mt-6 font-display font-semibold leading-snug text-white @(fa ? "font-fa" : "")" style="font-size:clamp(1.15rem,1.8vw,1.4rem)">@title</h3>
<p class="mt-3 text-[.94rem] leading-relaxed text-slate-400">@desc</p>
<div class="mt-5 flex flex-wrap gap-1.5">
@foreach (var tag in tags)
{
<span class="rounded-full border px-2.5 py-0.5 font-mono text-[.65rem] uppercase tracking-wider @chipCls">@tag</span>
}
</div>
<span aria-hidden class="absolute inset-x-6 bottom-0 h-px bg-gradient-to-r from-transparent via-white/10 to-transparent"></span>
</article>
si++;
}
</div>
</div>
</section>
<!-- ─── DATA FLOW ────────────────────────────────────────────────────── -->
<section id="dataflow" class="relative overflow-hidden px-5 py-28 sm:px-8">
<div class="mx-auto max-w-5xl">
<div class="section-header">
<div class="eyebrow"><span class="chip">@(fa ? "پایپ‌لاین" : "Pipeline")</span></div>
<h2>@(fa ? "از سند خام تا پاسخ قابل اتکا" : "From raw document to trustworthy answer")</h2>
<p>@(fa ? "مسیری که هر پرسش در یک سامانه‌ی RAG تولیدی طی می‌کند — هر مرحله قابل اندازه‌گیری، قابل ممیزی و بهینه‌شده برای تأخیر." : "The path every query takes through a production RAG system — each stage measurable, auditable, and tuned for latency.")</p>
</div>
<!-- Flow diagram -->
<div class="reveal mt-10 overflow-x-auto">
<div class="flex min-w-max items-center gap-0 mx-auto w-fit">
@{
var nodes = fa ? new[]{
("ingest","دریافت","نرمال‌سازی، قطعه‌بندی و پاک‌سازی اسناد منبع","electric"),
("embed","برداری‌سازی","تولید embedding و نمایه‌سازی در پایگاه برداری","violet"),
("retrieve","بازیابی","جستجوی ترکیبی معنایی و کلیدواژه‌ای","cyan"),
("rerank","بازرتبه‌بندی","مرتب‌سازی مجدد نامزدها با cross-encoder","magenta"),
("generate","تولید","پاسخ مستند با ارجاع به منبع","emerald"),
} : new[]{
("ingest","Ingest","Normalize, chunk, and clean source documents","electric"),
("embed","Embed","Generate embeddings and index in vector store","violet"),
("retrieve","Retrieve","Hybrid semantic + keyword search","cyan"),
("rerank","Rerank","Re-order candidates with a cross-encoder","magenta"),
("generate","Generate","Grounded answer with source citations","emerald"),
};
var colorMap2 = new Dictionary<string,(string border, string text, string bg)>{
["electric"] = ("border-electric/40","text-electric","bg-electric/10"),
["violet"] = ("border-violet/40", "text-violet", "bg-violet/10"),
["cyan"] = ("border-cyan/40", "text-cyan", "bg-cyan/10"),
["magenta"] = ("border-magenta/40", "text-magenta", "bg-magenta/10"),
["emerald"] = ("border-emerald/40", "text-emerald", "bg-emerald/10"),
};
}
@for (int ni = 0; ni < nodes.Length; ni++)
{
var (nid, nlabel, ndesc, naccent) = nodes[ni];
var (nborder, ntext, nbg) = colorMap2[naccent];
<div class="flex flex-col items-center">
<div class="glass @nborder @nbg flex flex-col items-center gap-2 rounded-2xl border p-5 text-center w-40">
<span class="label-mono @ntext">@nlabel</span>
<p class="text-[.72rem] leading-snug text-slate-400">@ndesc</p>
</div>
</div>
if (ni < nodes.Length - 1)
{
<div class="flex items-center px-2">
<svg width="40" height="16" viewBox="0 0 40 16" fill="none" aria-hidden="true">
<line x1="0" y1="8" x2="32" y2="8" stroke="rgba(56,189,248,0.4)" stroke-width="1.5" stroke-dasharray="4 3">
<animate attributeName="stroke-dashoffset" values="0;-14" dur="1.1s" repeatCount="indefinite"/>
</line>
<polygon points="32,4 40,8 32,12" fill="rgba(56,189,248,0.6)"/>
</svg>
</div>
}
}
</div>
</div>
<p class="mt-8 text-center label-mono text-slate-500 reveal">@(fa ? "تأخیر سرتاسری زیر ۵۰ میلی‌ثانیه · هر مرحله مشاهده‌پذیر" : "Sub-50ms end-to-end · every stage observable")</p>
</div>
</section>
<!-- ─── STACK ─────────────────────────────────────────────────────────── -->
<section id="stack" class="relative px-5 py-28 sm:px-8">
<div class="mx-auto max-w-7xl">
<div class="section-header">
<div class="eyebrow"><span class="chip">@(fa ? "استک" : "Stack")</span></div>
<h2>@(fa ? "ابزارهای روزانه" : "Daily tooling")</h2>
<p>@(fa ? "هر چه ساخته می‌شود از این پایه‌ها بیرون می‌آید — انتخاب‌شده برای عمر طولانی، نه ترند روز." : "Everything I ship sits on this foundation — chosen for longevity, not hype cycles.")</p>
</div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 reveal">
@{
var cats = fa ? new[]{
("زبان‌ها", new[]{"Python","TypeScript","Go","Rust","SQL"}),
("موبایل", new[]{"Flutter","Swift / SwiftUI","Kotlin","React Native"}),
("زیرساخت", new[]{"Kubernetes","Terraform","Postgres","Redis","Kafka","NATS"}),
("هوش مصنوعی", new[]{"Vertex AI","Gemini","OpenAI","Anthropic","LangGraph","Pinecone","pgvector"}),
} : new[]{
("Languages", new[]{"Python","TypeScript","Go","Rust","SQL"}),
("Mobile", new[]{"Flutter","Swift / SwiftUI","Kotlin","React Native"}),
("Infrastructure",new[]{"Kubernetes","Terraform","Postgres","Redis","Kafka","NATS"}),
("AI / ML", new[]{"Vertex AI","Gemini","OpenAI","Anthropic","LangGraph","Pinecone","pgvector"}),
};
string[] catColors = ["text-electric","text-violet","text-emerald","text-magenta"];
int ci2 = 0;
}
@foreach (var (catLabel, items) in cats)
{
<div class="glass p-6">
<h3 class="font-display font-semibold text-white mb-4 @catColors[ci2]">@catLabel</h3>
<ul class="space-y-2">
@foreach (var item in items)
{
<li class="flex items-center gap-2 text-[.9rem] text-slate-300">
<span class="h-1 w-1 rounded-full @catColors[ci2].Replace("text-","bg-")" aria-hidden></span>
@item
</li>
}
</ul>
</div>
ci2++;
}
</div>
</div>
</section>
<!-- ─── EXPERTISE ────────────────────────────────────────────────────── -->
<section id="expertise" class="relative px-5 py-28 sm:px-8">
<div class="mx-auto max-w-3xl">
<div class="section-header">
<div class="eyebrow"><span class="chip">@(fa ? "تخصص" : "Expertise")</span></div>
<h2>@(fa ? "اعدادی که اهمیت دارند" : "The numbers that matter")</h2>
<p>@(fa ? "سامانه‌هایی که در میلیون‌ها رویداد در روز پایدار می‌مانند — این‌ها معیارهایی هستند که اندازه می‌گیریم." : "Systems that survive millions of events per day — these are the metrics I optimize for.")</p>
</div>
<div class="space-y-6 reveal">
@{
var bars = fa ? new[]{
("مهندسی LLM و RAG", 95),
("معماری ابری و Kubernetes", 92),
("سیستم‌های عامل‌محور و اتوماسیون", 90),
("استک گوگل کلود (Vertex / GKE)", 88),
("موبایل بومی و cross-platform", 82),
} : new[]{
("LLM & RAG engineering", 95),
("Cloud architecture & Kubernetes", 92),
("Agentic systems & automation", 90),
("Google Cloud stack (Vertex / GKE)", 88),
("Native + cross-platform mobile", 82),
};
string[] barColors = ["bg-electric","bg-violet","bg-cyan","bg-magenta","bg-emerald"];
int bi = 0;
}
@foreach (var (blabel, bval) in bars)
{
<div>
<div class="mb-2 flex items-center justify-between">
<span class="text-[.9rem] text-slate-300">@blabel</span>
<span class="label-mono text-slate-400">@bval%</span>
</div>
<div class="bar-track">
<div class="bar-fill @barColors[bi]" data-w="@bval%" style="width:0%"></div>
</div>
</div>
bi++;
}
</div>
</div>
</section>
<!-- ─── PORTFOLIO ────────────────────────────────────────────────────── -->
<section id="portfolio" class="relative px-5 py-28 sm:px-8">
<div class="mx-auto max-w-7xl">
<div class="section-header">
<div class="eyebrow"><span class="chip">@(fa ? "نمونه‌کارها" : "Selected work")</span></div>
<h2>@(fa ? "سامانه‌هایی که در تولید کار می‌کنند" : "Systems that run in production")</h2>
<p>@(fa ? "گزیده‌ای از پروژه‌های واقعی. روی هر کارت بزنید تا جزئیات معماری را ببینید." : "A selection of real engagements. Tap any card for the gallery and architecture details.")</p>
</div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
@{
var projects = fa ? new[]{
("atlas-rag","اطلس — پلتفرم RAG سازمانی","بانک ردیف‌اول","مهندس ارشد هوش مصنوعی","۲۰۲۵","دستیار دانش روی بیش از ۴ میلیون سند داخلی؛ بازیابی ترکیبی با pgvector و reranker.","electric",new[]{"RAG","pgvector","Vertex AI","Eval"},new[]{("۴M+","سند نمایه‌شده"),("۳۸ms","تأخیر p95"),("۹۲٪","دقت پاسخ")},"/portfolio/atlas-rag/cover.svg",new[]{"/portfolio/atlas-rag/01.svg","/portfolio/atlas-rag/02.svg","/portfolio/atlas-rag/03.svg"}),
("sentinel-agents","Sentinel — اتوماسیون Ops عامل‌محور","SaaS scale-up","معمار راهکار","۲۰۲۵","پاسخ خودکار به حوادث با ترکیب n8n و LangGraph — عامل‌های قابل ممیزی که alert تریاژ می‌کنند.","violet",new[]{"n8n","LangGraph","Agents"},new[]{("۷۰٪","کاهش MTTR"),("۲۴/۷","پوشش on-call"),("۱۵۰+","جریان خودکار")},"/portfolio/sentinel-agents/cover.svg",new[]{"/portfolio/sentinel-agents/01.svg","/portfolio/sentinel-agents/02.svg","/portfolio/sentinel-agents/03.svg"}),
("vertex-vision","Vertex Vision — استنتاج بینایی بلادرنگ","زنجیره خرده‌فروشی","مهندس هوش مصنوعی","۲۰۲۴","استنتاج بینایی بلادرنگ روی GKE با Triton و Vertex AI برای تحلیل قفسه و جریان مشتری.","cyan",new[]{"Vertex AI","GKE","Triton"},new[]{("۱.۲B","استنتاج ماهانه"),("۳۰۰+","فروشگاه"),("۶۰٪","کاهش هزینه GPU")},"/portfolio/vertex-vision/cover.svg",new[]{"/portfolio/vertex-vision/01.svg","/portfolio/vertex-vision/02.svg","/portfolio/vertex-vision/03.svg"}),
("mirage-mobile","Mirage — مجموعه هوش مصنوعی on-device","محصول مصرفی","رهبر موبایل + هوش مصنوعی","۲۰۲۴","اپلیکیشن Flutter با استنتاج کاملاً آفلاین با Gemini Nano و LiteRT.","magenta",new[]{"Flutter","Gemini Nano","LiteRT"},new[]{("۰","وابستگی شبکه"),("<80ms","پاسخ"),("۴.۸★","امتیاز کاربران")},"/portfolio/mirage-mobile/cover.svg",new[]{"/portfolio/mirage-mobile/01.svg","/portfolio/mirage-mobile/02.svg","/portfolio/mirage-mobile/03.svg"}),
("flux-stream","Flux — مش داده رویدادمحور","پلتفرم لجستیک","معمار پلتفرم","۲۰۲۳","ستون استریمینگ روی Kafka و NATS روی Kubernetes — ۴۰+ میکروسرویس با الگوهای پایداری.","emerald",new[]{"Kafka","NATS","Kubernetes","Go"},new[]{("۴۰+","میکروسرویس"),("۲M/s","رویداد در ثانیه"),("۹۹.۹٪","uptime")},"/portfolio/flux-stream/cover.svg",new[]{"/portfolio/flux-stream/01.svg","/portfolio/flux-stream/02.svg","/portfolio/flux-stream/03.svg"}),
("oracle-forecast","Oracle — موتور پیش‌بینی تقاضا","زنجیره تامین","مهندس ML","۲۰۲۳","پایپ‌لاین پیش‌بینی سری زمانی روی BigQuery و dbt با بازآموزی خودکار.","electric",new[]{"Forecasting","BigQuery","dbt","MLOps"},new[]{("۲۳٪","کاهش ضایعات"),("۸۹٪","دقت پیش‌بینی"),("روزانه","بازآموزی")},"/portfolio/oracle-forecast/cover.svg",new[]{"/portfolio/oracle-forecast/01.svg","/portfolio/oracle-forecast/02.svg","/portfolio/oracle-forecast/03.svg"}),
} : new[]{
("atlas-rag","Atlas — Enterprise RAG Platform","Tier-1 bank","Lead AI Engineer","2025","A knowledge assistant over 4M+ internal documents — hybrid retrieval with pgvector and a reranker, sub-40ms serving on Vertex AI.","electric",new[]{"RAG","pgvector","Vertex AI","Eval"},new[]{("4M+","docs indexed"),("38ms","p95 latency"),("92%","answer accuracy")},"/portfolio/atlas-rag/cover.svg",new[]{"/portfolio/atlas-rag/01.svg","/portfolio/atlas-rag/02.svg","/portfolio/atlas-rag/03.svg"}),
("sentinel-agents","Sentinel — Agentic Ops Automation","SaaS scale-up","Solution Architect","2025","Autonomous incident response combining n8n and LangGraph — auditable agents that triage alerts and self-heal.","violet",new[]{"n8n","LangGraph","Agents"},new[]{("70%","MTTR reduction"),("24/7","on-call coverage"),("150+","automated flows")},"/portfolio/sentinel-agents/cover.svg",new[]{"/portfolio/sentinel-agents/01.svg","/portfolio/sentinel-agents/02.svg","/portfolio/sentinel-agents/03.svg"}),
("vertex-vision","Vertex Vision — Realtime Vision Inference","Retail chain","AI Engineer","2024","Real-time vision inference on GKE with Triton and Vertex AI for shelf analytics and customer flow across 300+ stores.","cyan",new[]{"Vertex AI","GKE","Triton"},new[]{("1.2B","inferences / mo"),("300+","stores"),("60%","GPU cost cut")},"/portfolio/vertex-vision/cover.svg",new[]{"/portfolio/vertex-vision/01.svg","/portfolio/vertex-vision/02.svg","/portfolio/vertex-vision/03.svg"}),
("mirage-mobile","Mirage — On-device AI Suite","Consumer product","Mobile + AI Lead","2024","A Flutter app with fully offline inference via Gemini Nano and LiteRT — streaming response UX with zero network dependency.","magenta",new[]{"Flutter","Gemini Nano","LiteRT"},new[]{("0","network deps"),("<80ms","response"),("4.8★","user rating")},"/portfolio/mirage-mobile/cover.svg",new[]{"/portfolio/mirage-mobile/01.svg","/portfolio/mirage-mobile/02.svg","/portfolio/mirage-mobile/03.svg"}),
("flux-stream","Flux — Event-Driven Data Mesh","Logistics platform","Platform Architect","2023","Streaming backbone on Kafka and NATS over Kubernetes — 40+ microservices with resilience patterns and exactly-once delivery.","emerald",new[]{"Kafka","NATS","Kubernetes","Go"},new[]{("40+","microservices"),("2M/s","events / sec"),("99.9%","uptime")},"/portfolio/flux-stream/cover.svg",new[]{"/portfolio/flux-stream/01.svg","/portfolio/flux-stream/02.svg","/portfolio/flux-stream/03.svg"}),
("oracle-forecast","Oracle — Demand Forecasting Engine","Supply chain","ML Engineer","2023","Time-series forecasting pipeline on BigQuery and dbt with automated retraining — reduced inventory waste significantly.","electric",new[]{"Forecasting","BigQuery","dbt","MLOps"},new[]{("23%","waste reduction"),("89%","forecast accuracy"),("daily","retraining")},"/portfolio/oracle-forecast/cover.svg",new[]{"/portfolio/oracle-forecast/01.svg","/portfolio/oracle-forecast/02.svg","/portfolio/oracle-forecast/03.svg"}),
};
}
@foreach (var (pid, ptitle, pclient, prole, pyear, psummary, paccent, ptags, pmetrics, pcover, pgallery) in projects)
{
var (pborder, ptext) = paccent switch {
"violet" => ("border-violet/30", "text-violet"),
"cyan" => ("border-cyan/30", "text-cyan"),
"magenta" => ("border-magenta/30", "text-magenta"),
"emerald" => ("border-emerald/30", "text-emerald"),
_ => ("border-electric/30", "text-electric"),
};
var galleryJson = System.Text.Json.JsonSerializer.Serialize(pgallery);
<div class="glass cursor-pointer select-none p-6 transition-all duration-300 hover:-translate-y-1 reveal @pborder"
data-portfolio-card
tabindex="0"
data-title="@ptitle"
data-summary="@psummary"
data-gallery="@galleryJson"
role="button"
aria-label="@ptitle">
<div class="mb-4 aspect-video w-full overflow-hidden rounded-xl bg-base-700">
<img src="@pcover" alt="@ptitle" class="h-full w-full object-cover" loading="lazy" />
</div>
<div class="mb-3 flex flex-wrap gap-1.5">
@foreach (var tag in ptags)
{
<span class="rounded-full border @pborder px-2 py-0.5 font-mono text-[.62rem] uppercase tracking-wider @ptext/80">@tag</span>
}
</div>
<h3 class="font-display font-semibold text-white @(fa ? "font-fa" : "")" style="font-size:clamp(1rem,1.5vw,1.2rem)">@ptitle</h3>
<p class="mt-1 text-[.82rem] text-slate-400">@pclient · @pyear</p>
<div class="mt-4 grid grid-cols-3 gap-3 border-t border-white/5 pt-4">
@foreach (var (mv, ml) in pmetrics)
{
<div class="text-center">
<div class="font-display font-bold @ptext text-lg">@mv</div>
<div class="text-[.68rem] text-slate-500">@ml</div>
</div>
}
</div>
</div>
}
</div>
</div>
</section>
<!-- Portfolio modal -->
<div id="portfolio-modal" class="fixed inset-0 z-[100] hidden" role="dialog" aria-modal="true">
<div id="modal-overlay" class="absolute inset-0 bg-black/80 backdrop-blur-sm"></div>
<div class="relative z-10 flex h-full items-center justify-center p-4">
<div class="glass w-full max-w-3xl max-h-[90vh] overflow-auto rounded-2xl p-6">
<div class="mb-4 flex items-start justify-between gap-4">
<h2 id="modal-title" class="font-display text-xl font-bold text-white"></h2>
<button id="modal-close" class="text-slate-400 hover:text-white transition-colors p-1" aria-label="Close">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
</button>
</div>
<div class="mb-4 aspect-video overflow-hidden rounded-xl bg-base-700">
<img id="modal-img" src="" alt="" class="h-full w-full object-contain" />
</div>
<div class="mb-4 flex justify-between gap-2">
<button id="modal-prev" class="btn-ghost text-sm py-2 px-4 disabled:opacity-30">@(fa ? "قبلی" : "Previous")</button>
<button id="modal-next" class="btn-ghost text-sm py-2 px-4 disabled:opacity-30">@(fa ? "بعدی" : "Next")</button>
</div>
<p id="modal-body" class="text-sm leading-relaxed text-slate-400"></p>
</div>
</div>
</div>
<!-- ─── BLOG ──────────────────────────────────────────────────────────── -->
<section id="blog" class="relative px-5 py-28 sm:px-8">
<div class="mx-auto max-w-7xl">
<div class="section-header">
<div class="eyebrow"><span class="chip">@(fa ? "بلاگ" : "Journal")</span></div>
<h2>@(fa ? "یادداشت‌های مهندسی" : "Engineering notes")</h2>
<p>@(fa ? "یافته‌ها از پروژه‌های واقعی — نه ترجمه‌ی مقاله، نه فهرست hype." : "Findings from real engagements — not translated articles, not hype lists.")</p>
</div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
@{
var posts = fa ? new[]{
("rag-eval-framework","LLM","چارچوب ارزیابی RAG که در تولید کار می‌کند","چرا BLEU و ROUGE برای RAG ناکافی‌اند، و معیارهایی که در پروژه‌های واقعی تصمیم می‌سازند.",8),
("agentic-n8n-patterns","Automation","الگوهای عامل‌محور با n8n برای سازمان","چگونه n8n را با LangGraph ترکیب کنیم تا گردش‌کارهای قابل ممیزی بسازیم.",11),
("vertex-cost-control","Google Stack","کنترل هزینه روی Vertex AI در مقیاس بالا","سه ضدالگو که در ۸۰٪ پروژه‌های Vertex می‌بینم، و چگونه ۶۰٪ هزینه را کاهش دادیم.",6),
("k8s-llm-inference","Infra","استنتاج LLM روی Kubernetes با تأخیر زیر ۵۰ میلی‌ثانیه","الگوی استقرار با KEDA، GPU sharing، و request hedging برای سرویس‌دهی پایدار.",14),
("flutter-on-device-ai","Mobile","هوش مصنوعی on-device در Flutter","استفاده از Gemini Nano و LiteRT برای استنتاج آفلاین در اپلیکیشن‌های موبایل.",9),
("enterprise-ai-roadmap","Strategy","نقشه راه هوش مصنوعی سازمانی در ۹۰ روز","چارچوبی که برای CTOها می‌سازم — از کشف موارد کاربری تا اولین استقرار تولید.",7),
} : new[]{
("rag-eval-framework","LLM","A RAG evaluation framework that holds up in production","Why BLEU and ROUGE fall short for RAG, and the metrics that actually drive decisions in real projects.",8),
("agentic-n8n-patterns","Automation","Agentic patterns with n8n for the enterprise","How to combine n8n with LangGraph to build auditable, debuggable autonomous workflows.",11),
("vertex-cost-control","Google Stack","Vertex AI cost control at scale","Three anti-patterns I see in 80% of Vertex projects — and how we cut 60% of monthly spend.",6),
("k8s-llm-inference","Infra","Sub-50ms LLM inference on Kubernetes","Deployment pattern with KEDA, GPU sharing, and request hedging for stable serving.",14),
("flutter-on-device-ai","Mobile","On-device AI in Flutter","Using Gemini Nano and LiteRT for offline inference inside mobile apps.",9),
("enterprise-ai-roadmap","Strategy","A 90-day enterprise AI roadmap","The framework I build for CTOs — from use-case discovery to first production deployment.",7),
};
}
@foreach (var (slug, cat, btitle, excerpt, readTime) in posts)
{
<a href="/blog/@slug" class="group glass block p-6 transition-all duration-300 hover:-translate-y-1 hover:border-electric/40 reveal">
<span class="label-mono text-electric mb-3 block">@cat</span>
<h3 class="font-display font-semibold leading-snug text-white group-hover:text-electric transition-colors @(fa ? "font-fa" : "")" style="font-size:clamp(1rem,1.4vw,1.15rem)">@btitle</h3>
<p class="mt-3 text-[.88rem] leading-relaxed text-slate-400 line-clamp-3">@excerpt</p>
<div class="mt-4 flex items-center justify-between">
<span class="label-mono">@readTime @(fa ? "دقیقه" : "min") @(fa ? "ادامه" : "read")</span>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="text-electric @(fa ? "rotate-180" : "")"><path d="M5 12H19"/><path d="M13 6L19 12L13 18"/></svg>
</div>
</a>
}
</div>
</div>
</section>
<!-- ─── CONTACT ───────────────────────────────────────────────────────── -->
<section id="contact" class="relative px-5 py-28 sm:px-8">
<div class="mx-auto max-w-2xl">
<div class="section-header">
<div class="eyebrow"><span class="chip">@(fa ? "تماس" : "Contact")</span></div>
<h2>@(fa ? "رزرو یک جلسه ۳۰ دقیقه‌ای" : "Book a 30-minute call")</h2>
<p>@(fa ? "بدون هزینه، بدون تعهد. موارد کاربردی، محدودیت‌ها و گام بعدی را با هم بررسی می‌کنیم." : "No cost, no commitment. We map the use case, the constraints, and the next step together.")</p>
</div>
<form id="contact-form"
class="glass p-8 space-y-5"
data-success-msg="@(fa ? "پیام ارسال شد. معمولاً ظرف ۲۴ ساعت کاری پاسخ می‌دهم." : "Sent! Typical reply within 24 working hours.")"
data-error-msg="@(fa ? "خطایی رخ داد. لطفاً دوباره امتحان کنید." : "Something went wrong. Please try again.")">
<input type="hidden" name="locale" value="@locale" />
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class="label-mono mb-2 block" for="name">@(fa ? "نام" : "Name")</label>
<input id="name" name="name" type="text" required placeholder="@(fa ? "نام و نام خانوادگی" : "Full name")" class="w-full rounded-xl border border-white/10 bg-white/[.03] px-4 py-3 text-sm text-white placeholder-slate-500 outline-none focus:border-electric/60 transition-colors" />
</div>
<div>
<label class="label-mono mb-2 block" for="company">@(fa ? "سازمان" : "Company")</label>
<input id="company" name="company" type="text" placeholder="@(fa ? "نام سازمان" : "Organization")" class="w-full rounded-xl border border-white/10 bg-white/[.03] px-4 py-3 text-sm text-white placeholder-slate-500 outline-none focus:border-electric/60 transition-colors" />
</div>
</div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class="label-mono mb-2 block" for="service">@(fa ? "خدمت" : "Service")</label>
<select id="service" name="service" required class="w-full rounded-xl border border-white/10 bg-base-800 px-4 py-3 text-sm text-white outline-none focus:border-electric/60 transition-colors">
<option value="" disabled selected>—</option>
@if (fa)
{
<option value="strategy">راهبرد و نقشه راه</option>
<option value="automation">اتوماسیون هوش مصنوعی</option>
<option value="llm-rag">مهندسی LLM و RAG</option>
<option value="architecture">معماری راهکار</option>
<option value="mobile">موبایل</option>
<option value="google-stack">استک گوگل</option>
}
else
{
<option value="strategy">AI Strategy & Roadmap</option>
<option value="automation">AI Automation</option>
<option value="llm-rag">LLM & RAG Engineering</option>
<option value="architecture">Solution Architecture</option>
<option value="mobile">Mobile AI Apps</option>
<option value="google-stack">Google Stack</option>
}
</select>
</div>
<div>
<label class="label-mono mb-2 block" for="budget">@(fa ? "بودجه (تقریبی)" : "Budget (rough)")</label>
<select id="budget" name="budget" required class="w-full rounded-xl border border-white/10 bg-base-800 px-4 py-3 text-sm text-white outline-none focus:border-electric/60 transition-colors">
<option value="" disabled selected>—</option>
<option>Under $10k</option>
<option>$10k$50k</option>
<option>$50k$200k</option>
<option>$200k+</option>
</select>
</div>
</div>
<div>
<label class="label-mono mb-2 block" for="message">@(fa ? "پیام" : "Message")</label>
<textarea id="message" name="message" required rows="4" placeholder="@(fa ? "هدف، بازه زمانی، موانع فعلی…" : "Goal, timeline, current blockers…")" class="w-full rounded-xl border border-white/10 bg-white/[.03] px-4 py-3 text-sm text-white placeholder-slate-500 outline-none focus:border-electric/60 transition-colors resize-none"></textarea>
</div>
<button type="submit" class="btn-primary w-full justify-center">
@(fa ? "ارسال درخواست" : "Send request")
</button>
<p id="contact-status" class="text-center text-sm text-slate-500">@(fa ? "معمولاً ظرف ۲۴ ساعت کاری پاسخ می‌دهم." : "Typical reply within 24 working hours.")</p>
</form>
</div>
</section>
<!-- ─── FOOTER ────────────────────────────────────────────────────────── -->
<footer class="border-t border-white/5 px-5 py-10 sm:px-8">
<div class="mx-auto flex max-w-7xl flex-col items-center gap-3 text-center">
<img src="/logo-mark.svg" alt="" width="24" height="24" />
<p class="label-mono">@(fa ? "طراحی‌شده در تهران · ساخته‌شده برای سازمان‌ها" : "Designed in Tehran · Built for the enterprise")</p>
<p class="text-[.78rem] text-slate-600">© 2026 Soroush Asadi. @(fa ? "تمام حقوق محفوظ است." : "All rights reserved.")</p>
</div>
</footer>
@functions {
static string ServiceIcon(string id) => id switch {
"strategy" => """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M9 20H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h4"/><polyline points="9,9 4,9"/><polyline points="9,12 4,12"/><polyline points="9,15 4,15"/><rect x="9" y="2" width="6" height="6"/><path d="M15 8h4a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-4"/></svg>""",
"automation" => """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M4.22 4.22l2.12 2.12M17.66 17.66l2.12 2.12M2 12h3M19 12h3M4.22 19.78l2.12-2.12M17.66 6.34l2.12-2.12"/></svg>""",
"llm-rag" => """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>""",
"architecture" => """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="2" y="3" width="6" height="6"/><rect x="16" y="3" width="6" height="6"/><rect x="9" y="15" width="6" height="6"/><path d="M5 9v3h14V9M12 12v3"/></svg>""",
"mobile" => """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="5" y="2" width="14" height="20" rx="2"/><path d="M12 18h.01"/></svg>""",
"google-stack" => """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>""",
_ => """<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="10"/></svg>""",
};
}