first commit
ci / build (push) Failing after 23s
deploy / deploy (push) Failing after 10m12s

This commit is contained in:
soroush.asadi
2026-05-31 12:47:02 +03:30
commit add78d8460
100 changed files with 15221 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import { notFound } from 'next/navigation';
import { loadContent } from '@/lib/content/load';
import { loadPost } from '@/lib/content/posts-store';
import { BlogArticle } from '@/components/blog/BlogArticle';
// Live content: bodies and card meta both come from the CMS-merged tree, so
// admin edits show immediately. (No generateStaticParams — render on demand.)
export const dynamic = 'force-dynamic';
type Params = { slug: string };
export function generateMetadata({ params }: { params: Params }) {
const { en } = loadContent();
const post = en.blog.items.find((p) => p.slug === params.slug);
if (!post) return {};
return {
title: post.title,
description: post.excerpt,
openGraph: { title: post.title, description: post.excerpt, type: 'article' },
alternates: {
canonical: `/blog/${post.slug}`,
languages: {
'fa-IR': `/blog/${post.slug}`,
'en-US': `/blog/${post.slug}`,
},
},
};
}
export default function BlogPostPage({ params }: { params: Params }) {
const content = loadPost(params.slug);
const { en: enContent, fa: faContent } = loadContent();
const en = enContent.blog.items.find((p) => p.slug === params.slug);
const fa = faContent.blog.items.find((p) => p.slug === params.slug);
if (!content || !en || !fa) notFound();
return (
<BlogArticle
content={content}
meta={{
en: { title: en.title, category: en.category, readTime: en.readTime },
fa: { title: fa.title, category: fa.category, readTime: fa.readTime },
}}
/>
);
}
+38
View File
@@ -0,0 +1,38 @@
import { LocaleProvider } from '@/lib/i18n/locale-context';
import { loadContent } from '@/lib/content/load';
import { Navbar } from '@/components/nav/Navbar';
import { CustomCursor } from '@/components/ui/CustomCursor';
/**
* Public site shell. Reads the live content tree (dict defaults merged with
* any admin overrides) on every request so edits made in the panel appear
* immediately, then feeds it to the client-side LocaleProvider.
*/
export const dynamic = 'force-dynamic';
export default function SiteLayout({ children }: { children: React.ReactNode }) {
const content = loadContent();
return (
<LocaleProvider content={content}>
{/* Ambient backdrop */}
<div
aria-hidden
className="pointer-events-none fixed inset-0 -z-10 bg-radial-aurora"
/>
<div
aria-hidden
className="pointer-events-none fixed inset-0 -z-10 bg-grid-faint bg-grid opacity-40"
style={{
maskImage:
'radial-gradient(ellipse at center, black 30%, transparent 75%)',
WebkitMaskImage:
'radial-gradient(ellipse at center, black 30%, transparent 75%)',
}}
/>
<CustomCursor />
<Navbar />
<main>{children}</main>
</LocaleProvider>
);
}
+25
View File
@@ -0,0 +1,25 @@
import { Hero } from '@/components/hero/Hero';
import { Services } from '@/components/sections/Services';
import { DataFlow } from '@/components/sections/DataFlow';
import { Stack } from '@/components/sections/Stack';
import { Expertise } from '@/components/sections/Expertise';
import { Portfolio } from '@/components/sections/Portfolio';
import { Blog } from '@/components/sections/Blog';
import { Contact } from '@/components/sections/Contact';
import { Footer } from '@/components/sections/Footer';
export default function HomePage() {
return (
<>
<Hero />
<Services />
<DataFlow />
<Stack />
<Expertise />
<Portfolio />
<Blog />
<Contact />
<Footer />
</>
);
}
+84
View File
@@ -0,0 +1,84 @@
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { dict, SERVICE_IDS, type ServiceId } from '@/lib/i18n/dictionaries';
type Params = { slug: string };
export function generateStaticParams() {
return SERVICE_IDS.map((slug) => ({ slug }));
}
export function generateMetadata({ params }: { params: Params }) {
const id = params.slug as ServiceId;
const fa = dict.fa.services.items.find((s) => s.id === id);
const en = dict.en.services.items.find((s) => s.id === id);
if (!en) return {};
return {
title: en.title,
description: en.description,
openGraph: { title: en.title, description: en.description },
alternates: { canonical: `/services/${id}`, languages: { 'fa-IR': `/services/${id}`, 'en-US': `/services/${id}` } },
other: { 'fa-title': fa?.title ?? '' },
};
}
export default function ServiceDetailPage({ params }: { params: Params }) {
const id = params.slug as ServiceId;
if (!SERVICE_IDS.includes(id)) notFound();
const en = dict.en.services.items.find((s) => s.id === id)!;
const fa = dict.fa.services.items.find((s) => s.id === id)!;
return (
<article className="relative px-5 py-32 sm:px-8">
<div className="mx-auto max-w-3xl">
<Link
href="/#services"
className="label-mono inline-flex items-center gap-2 text-slate-400 hover:text-electric"
>
{dict.en.nav.services}
</Link>
<h1 className="mt-6 font-display text-[clamp(2rem,4.5vw,3.4rem)] font-extrabold leading-tight text-white">
{en.title}
</h1>
<p
dir="rtl"
className="mt-2 font-fa text-[clamp(1.1rem,2vw,1.5rem)] text-slate-400"
>
{fa.title}
</p>
<div className="mt-8 flex flex-wrap gap-2">
{en.tags.map((t) => (
<span
key={t}
className="rounded-full border border-electric/30 bg-electric/5 px-3 py-1 font-mono text-xs text-electric"
>
{t}
</span>
))}
</div>
<p className="mt-10 text-[1.05rem] leading-relaxed text-slate-300">
{en.description}
</p>
<p
dir="rtl"
className="mt-6 font-fa text-[1rem] leading-loose text-slate-400"
>
{fa.description}
</p>
<div className="mt-12 flex flex-wrap gap-3">
<Link href="/#contact" className="btn-primary">
Book a consultation
</Link>
<Link href="/#services" className="btn-ghost">
All services
</Link>
</div>
</div>
</article>
);
}