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
+179
View File
@@ -0,0 +1,179 @@
@{
var locale = (string)(ViewData["Locale"] ?? "fa");
var isRtl = locale == "fa";
var dir = isRtl ? "rtl" : "ltr";
var lang = locale == "fa" ? "fa" : "en";
var title = (string?)ViewData["Title"] ?? (locale == "fa"
? "سروش اسعدی — مهندس هوش مصنوعی، مشاور، معمار راهکار"
: "Soroush Asadi — AI Engineer, Consultant, Solution Architect");
}
<!doctype html>
<html lang="@lang" dir="@dir">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>@title</title>
<meta name="description" content="@(locale == "fa"
? "طراحی و پیاده‌سازی سامانه‌های هوش مصنوعی در مقیاس سازمانی — راهبرد، LLM و RAG، اتوماسیون عامل‌محور، زیرساخت ابری و استک گوگل."
: "Designing and deploying enterprise-grade AI systems — strategy, LLM & RAG, agentic automation, cloud infrastructure, and Google Stack.")" />
<!-- Fonts -->
<style>
@@font-face { font-family:'Syne'; src:url('/fonts/Syne-Variable.woff2') format('woff2'); font-weight:100 900; font-display:swap; }
@@font-face { font-family:'Vazirmatn'; src:url('/fonts/Vazirmatn-Arabic.woff2') format('woff2'); font-display:swap; }
@@font-face { font-family:'VazirmatnLat'; src:url('/fonts/Vazirmatn-Latin.woff2') format('woff2'); font-display:swap; }
@@font-face { font-family:'SpaceMono'; src:url('/fonts/SpaceMono-Regular.woff2') format('woff2'); font-weight:400; font-display:swap; }
@@font-face { font-family:'SpaceMono'; src:url('/fonts/SpaceMono-Bold.woff2') format('woff2'); font-weight:700; font-display:swap; }
</style>
<!-- Tailwind CDN (play) + custom config -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
base: { DEFAULT:'#020510', 800:'#050a1a', 700:'#0a1224', 600:'#0f1b33' },
electric:'#38bdf8',
violet: '#818cf8',
magenta: '#e879f9',
emerald: '#34d399',
cyan: '#22d3ee',
},
fontFamily: {
sans: ['Syne','Vazirmatn','VazirmatnLat','system-ui','sans-serif'],
display: ['Syne','Vazirmatn','sans-serif'],
fa: ['Vazirmatn','VazirmatnLat','sans-serif'],
mono: ['SpaceMono','ui-monospace','monospace'],
},
keyframes: {
'pulse-dot': {'0%,100%':{opacity:'1',transform:'scale(1)'},'50%':{opacity:'.6',transform:'scale(1.4)'}},
'gradient-pan': {'0%,100%':{backgroundPosition:'0% 50%'},'50%':{backgroundPosition:'100% 50%'}},
'caret-blink': {'0%,49%':{opacity:'1'},'50%,100%':{opacity:'0'}},
'float-y': {'0%,100%':{transform:'translateY(0)'},'50%':{transform:'translateY(-6px)'}},
'flow-dash': {'0%':{strokeDashoffset:'0'},'100%':{strokeDashoffset:'-66'}},
'fade-up': {'0%':{opacity:'0',transform:'translateY(24px)'},'100%':{opacity:'1',transform:'translateY(0)'}},
'bar-grow': {'0%':{width:'0%'},'100%':{width:'var(--bar-w)'}},
},
animation: {
'pulse-dot': 'pulse-dot 1.8s ease-in-out infinite',
'caret-blink':'caret-blink 1s steps(2) infinite',
'float-y': 'float-y 4s ease-in-out infinite',
'flow-dash': 'flow-dash 1.1s linear infinite',
'fade-up': 'fade-up .7s cubic-bezier(.22,1,.36,1) forwards',
'bar-grow': 'bar-grow 1.2s cubic-bezier(.22,1,.36,1) forwards',
},
boxShadow: {
'glow-electric':'0 0 40px -8px rgba(56,189,248,.55)',
'glow-magenta': '0 0 40px -8px rgba(232,121,249,.55)',
'glow-violet': '0 0 40px -8px rgba(129,140,248,.55)',
'glow-emerald': '0 0 40px -8px rgba(52,211,153,.55)',
},
}
}
}
</script>
<!-- Site CSS -->
<link rel="stylesheet" href="/css/site.css" />
<link rel="icon" href="/logo-mark.svg" type="image/svg+xml" />
</head>
<body class="bg-base text-slate-200 antialiased">
<!-- Custom cursor (desktop only) -->
<div id="cursor" class="pointer-events-none fixed z-[9999] hidden h-5 w-5 -translate-x-1/2 -translate-y-1/2 rounded-full border border-electric/70 transition-transform duration-100 lg:block" aria-hidden="true"></div>
<div id="cursor-dot" class="pointer-events-none fixed z-[9999] hidden h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-electric lg:block" aria-hidden="true"></div>
<!-- Navbar -->
<header id="navbar" class="fixed inset-x-0 top-0 z-50 transition-all duration-300">
<div class="mx-auto flex max-w-7xl items-center justify-between px-5 py-4 sm:px-8">
<!-- Logo -->
<a href="/#top" class="flex items-center gap-2.5" aria-label="Home">
<img src="/logo-mark.svg" alt="" width="28" height="28" class="h-7 w-7" />
<span class="font-display font-bold text-white @(isRtl ? "font-fa" : "")">
@(locale == "fa" ? "سروش اسعدی" : "Soroush Asadi")
</span>
</a>
<!-- Desktop nav -->
<nav class="hidden items-center gap-6 md:flex" aria-label="Main">
@if (locale == "fa")
{
<a href="/#services" class="nav-link">خدمات</a>
<a href="/#stack" class="nav-link">استک</a>
<a href="/#expertise" class="nav-link">تخصص</a>
<a href="/#portfolio" class="nav-link">نمونه‌کارها</a>
<a href="/#blog" class="nav-link">بلاگ</a>
<a href="/#contact" class="nav-link">تماس</a>
}
else
{
<a href="/#services" class="nav-link">Services</a>
<a href="/#stack" class="nav-link">Stack</a>
<a href="/#expertise" class="nav-link">Expertise</a>
<a href="/#portfolio" class="nav-link">Portfolio</a>
<a href="/#blog" class="nav-link">Blog</a>
<a href="/#contact" class="nav-link">Contact</a>
}
<a href="/#contact" class="btn-primary text-sm">
@(locale == "fa" ? "رزرو جلسه" : "Book a call")
</a>
<!-- Locale toggle -->
<form method="post" action="/locale">
<input type="hidden" name="locale" value="@(locale == "fa" ? "en" : "fa")" />
<input type="hidden" name="returnUrl" value="@Context.Request.Path@Context.Request.QueryString" />
<button type="submit" class="label-mono text-slate-400 hover:text-white transition-colors">
@(locale == "fa" ? "EN" : "FA")
</button>
</form>
</nav>
<!-- Mobile menu button -->
<button id="menu-btn" class="flex flex-col gap-1.5 p-2 md:hidden" aria-label="Menu">
<span class="block h-0.5 w-5 bg-slate-300 transition-all"></span>
<span class="block h-0.5 w-5 bg-slate-300 transition-all"></span>
<span class="block h-0.5 w-5 bg-slate-300 transition-all"></span>
</button>
</div>
<!-- Mobile drawer -->
<div id="mobile-menu" class="hidden border-t border-white/5 bg-base-800/95 backdrop-blur-xl md:hidden">
<nav class="flex flex-col gap-1 px-5 py-4">
@if (locale == "fa")
{
<a href="/#services" class="nav-link py-2">خدمات</a>
<a href="/#stack" class="nav-link py-2">استک</a>
<a href="/#expertise" class="nav-link py-2">تخصص</a>
<a href="/#portfolio" class="nav-link py-2">نمونه‌کارها</a>
<a href="/#blog" class="nav-link py-2">بلاگ</a>
<a href="/#contact" class="nav-link py-2">تماس</a>
}
else
{
<a href="/#services" class="nav-link py-2">Services</a>
<a href="/#stack" class="nav-link py-2">Stack</a>
<a href="/#expertise" class="nav-link py-2">Expertise</a>
<a href="/#portfolio" class="nav-link py-2">Portfolio</a>
<a href="/#blog" class="nav-link py-2">Blog</a>
<a href="/#contact" class="nav-link py-2">Contact</a>
}
<form method="post" action="/locale" class="mt-2">
<input type="hidden" name="locale" value="@(locale == "fa" ? "en" : "fa")" />
<input type="hidden" name="returnUrl" value="@Context.Request.Path@Context.Request.QueryString" />
<button type="submit" class="label-mono text-slate-400">
@(locale == "fa" ? "Switch to English" : "تغییر به فارسی")
</button>
</form>
</nav>
</div>
</header>
<main>
@RenderBody()
</main>
<!-- Scripts -->
<script src="/js/app.js" defer></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>