Refine homepage and restore hero
This commit is contained in:
+189
-103
@@ -1,27 +1,36 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import {
|
|
||||||
ArrowRight,
|
|
||||||
BadgeCheck,
|
|
||||||
Blocks,
|
|
||||||
BriefcaseBusiness,
|
|
||||||
ChartNoAxesCombined,
|
|
||||||
Globe,
|
|
||||||
Package2,
|
|
||||||
Search,
|
|
||||||
} from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
import { Container } from "@/components/layout/container";
|
import { Container } from "@/components/layout/container";
|
||||||
import { HomeHero } from "@/components/layout/home-hero";
|
import { HomeHero } from "@/components/layout/home-hero";
|
||||||
import { StackedMarqueeSection, type MarqueeRow } from "@/components/layout/stacked-marquee-section";
|
|
||||||
import { MotionFade } from "@/components/motion-fade";
|
import { MotionFade } from "@/components/motion-fade";
|
||||||
import { getMarqueeSettings, getSiteSettings, splitMarqueeRowItems } from "@/lib/app-config";
|
import { BentoGridSection } from "@/components/home/bento-grid-section";
|
||||||
|
import { CapabilitiesSection } from "@/components/home/capabilities-section";
|
||||||
|
import { ContactCtaSection } from "@/components/home/contact-cta-section";
|
||||||
|
import { MarqueeSection } from "@/components/home/marquee-section";
|
||||||
|
import { ProcessSection } from "@/components/home/process-section";
|
||||||
|
import { ProjectsSection } from "@/components/home/projects-section";
|
||||||
|
import type {
|
||||||
|
BentoSectionCopy,
|
||||||
|
CapabilitiesSectionCopy,
|
||||||
|
ContactCtaSectionCopy,
|
||||||
|
MarqueeSectionCopy,
|
||||||
|
ProcessSectionCopy,
|
||||||
|
ProjectCardCopy,
|
||||||
|
ProjectsSectionCopy,
|
||||||
|
} from "@/components/home/types";
|
||||||
import { buildLocalizedMetadata } from "@/lib/metadata";
|
import { buildLocalizedMetadata } from "@/lib/metadata";
|
||||||
import { AppCard } from "@/components/ui/app-card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { CardContent } from "@/components/ui/card";
|
|
||||||
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
||||||
|
import {
|
||||||
|
getMarqueeSettings,
|
||||||
|
getSiteSettings,
|
||||||
|
splitMarqueeRowItems,
|
||||||
|
} from "@/lib/app-config";
|
||||||
|
import {
|
||||||
|
getLocalizedValue,
|
||||||
|
getPublishedPortfolioProjects,
|
||||||
|
} from "@/lib/portfolio";
|
||||||
|
import { type MarqueeRow } from "@/components/layout/stacked-marquee-section";
|
||||||
|
|
||||||
type HomePageProps = {
|
type HomePageProps = {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
@@ -29,29 +38,22 @@ type HomePageProps = {
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const serviceIcons = [
|
|
||||||
BriefcaseBusiness,
|
|
||||||
Globe,
|
|
||||||
Package2,
|
|
||||||
Blocks,
|
|
||||||
Search,
|
|
||||||
ChartNoAxesCombined,
|
|
||||||
BadgeCheck,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export async function generateMetadata({ params }: HomePageProps): Promise<Metadata> {
|
export async function generateMetadata({ params }: HomePageProps): Promise<Metadata> {
|
||||||
await params;
|
await params;
|
||||||
const siteSettings = await getSiteSettings();
|
const siteSettings = await getSiteSettings();
|
||||||
const localeKey = resolveLocale(await getLocale().catch(() => siteSettings.defaultLocale), siteSettings.defaultLocale);
|
const localeKey = resolveLocale(
|
||||||
|
await getLocale().catch(() => siteSettings.defaultLocale),
|
||||||
|
siteSettings.defaultLocale,
|
||||||
|
);
|
||||||
const t = await getTranslations({ locale: localeKey, namespace: "homepage" });
|
const t = await getTranslations({ locale: localeKey, namespace: "homepage" });
|
||||||
|
|
||||||
return await buildLocalizedMetadata({
|
return await buildLocalizedMetadata({
|
||||||
locale: localeKey,
|
locale: localeKey,
|
||||||
pathname: "/",
|
pathname: "/",
|
||||||
title: siteSettings.locales[localeKey].siteName,
|
title: t("meta.title"),
|
||||||
description: t("heroText"),
|
description: t("meta.description"),
|
||||||
applyTitleTemplate: false,
|
applyTitleTemplate: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -59,100 +61,184 @@ export async function generateMetadata({ params }: HomePageProps): Promise<Metad
|
|||||||
export default async function HomePage({ params }: HomePageProps) {
|
export default async function HomePage({ params }: HomePageProps) {
|
||||||
await params;
|
await params;
|
||||||
const siteSettings = await getSiteSettings();
|
const siteSettings = await getSiteSettings();
|
||||||
const localeKey = resolveLocale(await getLocale().catch(() => siteSettings.defaultLocale), siteSettings.defaultLocale);
|
const localeKey = resolveLocale(
|
||||||
const [t, marqueeSettings] = await Promise.all([
|
await getLocale().catch(() => siteSettings.defaultLocale),
|
||||||
|
siteSettings.defaultLocale,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [t, marqueeSettings, publishedProjects] = await Promise.all([
|
||||||
getTranslations({ locale: localeKey, namespace: "homepage" }),
|
getTranslations({ locale: localeKey, namespace: "homepage" }),
|
||||||
getMarqueeSettings(),
|
getMarqueeSettings(),
|
||||||
|
getPublishedPortfolioProjects(),
|
||||||
]);
|
]);
|
||||||
const serviceItems = [
|
|
||||||
t("serviceBrand"),
|
const bentoCopy: BentoSectionCopy = {
|
||||||
t("serviceWebsite"),
|
eyebrow: t("bento.eyebrow"),
|
||||||
t("serviceStore"),
|
title: t("bento.title"),
|
||||||
t("serviceWordPress"),
|
description: t("bento.description"),
|
||||||
t("serviceSeo"),
|
about: {
|
||||||
t("serviceTracking"),
|
eyebrow: t("bento.about.eyebrow"),
|
||||||
t("serviceSystem"),
|
title: t("bento.about.title"),
|
||||||
];
|
description: t("bento.about.description"),
|
||||||
|
points: t.raw("bento.about.points") as string[],
|
||||||
|
},
|
||||||
|
coreStack: {
|
||||||
|
eyebrow: t("bento.coreStack.eyebrow"),
|
||||||
|
title: t("bento.coreStack.title"),
|
||||||
|
items: t.raw("bento.coreStack.items") as string[],
|
||||||
|
},
|
||||||
|
availability: {
|
||||||
|
eyebrow: t("bento.availability.eyebrow"),
|
||||||
|
title: t("bento.availability.title"),
|
||||||
|
description: t("bento.availability.description"),
|
||||||
|
status: t("bento.availability.status"),
|
||||||
|
},
|
||||||
|
currentFocus: {
|
||||||
|
eyebrow: t("bento.currentFocus.eyebrow"),
|
||||||
|
title: t("bento.currentFocus.title"),
|
||||||
|
items: t.raw("bento.currentFocus.items") as string[],
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
eyebrow: t("bento.contact.eyebrow"),
|
||||||
|
title: t("bento.contact.title"),
|
||||||
|
description: t("bento.contact.description"),
|
||||||
|
emailLabel: t("bento.contact.emailLabel"),
|
||||||
|
emailValue: t("bento.contact.emailValue"),
|
||||||
|
},
|
||||||
|
highlights: {
|
||||||
|
eyebrow: t("bento.highlights.eyebrow"),
|
||||||
|
title: t("bento.highlights.title"),
|
||||||
|
items: t.raw("bento.highlights.items") as Array<{ value: string; label: string }>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const marqueeCopy: MarqueeSectionCopy = {
|
||||||
|
eyebrow: t("marquee.eyebrow"),
|
||||||
|
title: t("marquee.title"),
|
||||||
|
description: t("marquee.description"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectsCopy: ProjectsSectionCopy = {
|
||||||
|
eyebrow: t("projects.eyebrow"),
|
||||||
|
title: t("projects.title"),
|
||||||
|
description: t("projects.description"),
|
||||||
|
stackLabel: t("projects.stackLabel"),
|
||||||
|
fallbackItems: t.raw("projects.fallbackItems") as ProjectsSectionCopy["fallbackItems"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const capabilitiesCopy: CapabilitiesSectionCopy = {
|
||||||
|
eyebrow: t("capabilities.eyebrow"),
|
||||||
|
title: t("capabilities.title"),
|
||||||
|
description: t("capabilities.description"),
|
||||||
|
items: t.raw("capabilities.items") as CapabilitiesSectionCopy["items"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const processCopy: ProcessSectionCopy = {
|
||||||
|
eyebrow: t("process.eyebrow"),
|
||||||
|
title: t("process.title"),
|
||||||
|
description: t("process.description"),
|
||||||
|
steps: t.raw("process.steps") as ProcessSectionCopy["steps"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const contactCtaCopy: ContactCtaSectionCopy = {
|
||||||
|
eyebrow: t("contactCta.eyebrow"),
|
||||||
|
title: t("contactCta.title"),
|
||||||
|
description: t("contactCta.description"),
|
||||||
|
contactCta: t("contactCta.contactCta"),
|
||||||
|
githubCta: t("contactCta.githubCta"),
|
||||||
|
emailLabel: t("contactCta.emailLabel"),
|
||||||
|
emailValue: t("contactCta.emailValue"),
|
||||||
|
availabilityLabel: t("contactCta.availabilityLabel"),
|
||||||
|
availabilityValue: t("contactCta.availabilityValue"),
|
||||||
|
githubHref: t("contactCta.githubHref"),
|
||||||
|
};
|
||||||
|
|
||||||
const marqueeLocale = marqueeSettings.locales[localeKey];
|
const marqueeLocale = marqueeSettings.locales[localeKey];
|
||||||
const marqueeRows: MarqueeRow[] = [
|
const marqueeRows = [
|
||||||
{ direction: "left", duration: 126, items: splitMarqueeRowItems(marqueeLocale.row1) },
|
{ direction: "left", duration: 126, items: splitMarqueeRowItems(marqueeLocale.row1) },
|
||||||
{ direction: "right", duration: 220, items: splitMarqueeRowItems(marqueeLocale.row2) },
|
{ direction: "right", duration: 220, items: splitMarqueeRowItems(marqueeLocale.row2) },
|
||||||
{ direction: "left", duration: 168, items: splitMarqueeRowItems(marqueeLocale.row3) },
|
{ direction: "left", duration: 168, items: splitMarqueeRowItems(marqueeLocale.row3) },
|
||||||
{ direction: "right", duration: 144, items: splitMarqueeRowItems(marqueeLocale.row4) },
|
{ direction: "right", duration: 144, items: splitMarqueeRowItems(marqueeLocale.row4) },
|
||||||
];
|
] satisfies MarqueeRow[];
|
||||||
|
|
||||||
|
const portfolioHref = getLocalizedPath(localeKey, "/portfolio", siteSettings.defaultLocale);
|
||||||
|
const contactHref = getLocalizedPath(localeKey, "/contact", siteSettings.defaultLocale);
|
||||||
|
const stackSets = t.raw("projects.stackSets") as string[][];
|
||||||
|
|
||||||
|
const selectedProjects = [
|
||||||
|
...publishedProjects.filter((project) => project.isFeatured),
|
||||||
|
...publishedProjects.filter((project) => !project.isFeatured),
|
||||||
|
].slice(0, 3);
|
||||||
|
|
||||||
|
const projectCards: ProjectCardCopy[] = selectedProjects.map((project, index) => ({
|
||||||
|
title: getLocalizedValue(project.title, localeKey),
|
||||||
|
summary: getLocalizedValue(project.summary, localeKey),
|
||||||
|
stack: stackSets[index] ?? stackSets[stackSets.length - 1] ?? [],
|
||||||
|
href: getLocalizedPath(
|
||||||
|
localeKey,
|
||||||
|
`/portfolio/${project.slug}`,
|
||||||
|
siteSettings.defaultLocale,
|
||||||
|
),
|
||||||
|
cta: t("projects.cta"),
|
||||||
|
meta: [
|
||||||
|
getLocalizedValue(project.category.name, localeKey),
|
||||||
|
getLocalizedValue(project.serviceLabel, localeKey),
|
||||||
|
String(project.projectYear),
|
||||||
|
],
|
||||||
|
featured: index === 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const fallbackProjectCards: ProjectCardCopy[] = projectsCopy.fallbackItems.map((item) => ({
|
||||||
|
title: item.title,
|
||||||
|
summary: item.summary,
|
||||||
|
stack: item.stack,
|
||||||
|
href: item.href === "/portfolio" ? portfolioHref : item.href === "/contact" ? contactHref : item.href,
|
||||||
|
cta: t("projects.cta"),
|
||||||
|
meta: [t("projects.fallbackMeta")],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mergedProjectCards = [
|
||||||
|
...projectCards,
|
||||||
|
...fallbackProjectCards.slice(0, Math.max(0, 3 - projectCards.length)),
|
||||||
|
].slice(0, 3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HomeHero
|
<HomeHero
|
||||||
locale={localeKey}
|
locale={localeKey}
|
||||||
kicker={t("heroKicker")}
|
kicker={t("heroVisual.kicker")}
|
||||||
title={t("heroTitle")}
|
title={t("heroVisual.title")}
|
||||||
description={t("heroText")}
|
description={t("heroVisual.description")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Container id="home-content" className="relative z-10 -mt-6 flex scroll-mt-28 flex-col gap-section pb-12 lg:-mt-8 lg:pb-16">
|
<Container
|
||||||
<MotionFade delay={0.05}>
|
id="home-content"
|
||||||
<AppCard className="overflow-hidden">
|
className="relative z-10 flex max-w-7xl flex-col gap-16 pb-14 pt-6 sm:gap-20 sm:pb-16 sm:pt-8 lg:gap-24 lg:pb-20 lg:pt-10"
|
||||||
<CardContent className="p-6 lg:p-10">
|
|
||||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,1.05fr)_minmax(0,1fr)] lg:items-start">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium tracking-[0.08em] text-foreground/56">
|
|
||||||
{t("servicesEyebrow")}
|
|
||||||
</p>
|
|
||||||
<h2 className="mt-4 max-w-[12ch] text-balance text-4xl font-semibold leading-[0.92] tracking-[-0.06em] text-foreground sm:text-5xl lg:text-6xl">
|
|
||||||
{t("servicesTitle")}
|
|
||||||
</h2>
|
|
||||||
<p className="mt-5 max-w-[34rem] text-sm leading-7 text-muted-foreground sm:text-base">
|
|
||||||
{t("servicesText")}
|
|
||||||
</p>
|
|
||||||
<div className="mt-8 flex flex-wrap gap-3">
|
|
||||||
<Button asChild size="lg" className="min-w-[11rem]">
|
|
||||||
<Link href={getLocalizedPath(localeKey, "/contact", siteSettings.defaultLocale)}>
|
|
||||||
{t("servicesPrimaryCta")}
|
|
||||||
<ArrowRight className="h-4 w-4" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
asChild
|
|
||||||
size="lg"
|
|
||||||
variant="outline"
|
|
||||||
className="min-w-[11rem] border-border/80 bg-background/70 hover:bg-background"
|
|
||||||
>
|
>
|
||||||
<Link href={getLocalizedPath(localeKey, "/portfolio", siteSettings.defaultLocale)}>
|
<MotionFade>
|
||||||
{t("servicesSecondaryCta")}
|
<BentoGridSection copy={bentoCopy} />
|
||||||
</Link>
|
</MotionFade>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<MotionFade delay={0.06}>
|
||||||
{serviceItems.map((item, index) => {
|
<MarqueeSection copy={marqueeCopy} rows={marqueeRows} />
|
||||||
const Icon = serviceIcons[index];
|
</MotionFade>
|
||||||
|
|
||||||
return (
|
<MotionFade delay={0.08}>
|
||||||
<AppCard
|
<ProjectsSection copy={projectsCopy} items={mergedProjectCards} />
|
||||||
key={item}
|
</MotionFade>
|
||||||
layer="single"
|
|
||||||
interactive
|
<MotionFade delay={0.1}>
|
||||||
className="h-full"
|
<CapabilitiesSection copy={capabilitiesCopy} />
|
||||||
>
|
</MotionFade>
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-[linear-gradient(135deg,hsl(var(--hero-left)/0.42),hsl(var(--hero-right)/0.26))]">
|
|
||||||
<Icon className="h-5 w-5 text-foreground/78" />
|
<MotionFade delay={0.12}>
|
||||||
</div>
|
<ProcessSection copy={processCopy} />
|
||||||
<p className="mt-4 text-sm font-medium leading-6 text-foreground/88 sm:text-base">
|
</MotionFade>
|
||||||
{item}
|
|
||||||
</p>
|
<MotionFade delay={0.14}>
|
||||||
</AppCard>
|
<ContactCtaSection copy={contactCtaCopy} contactHref={contactHref} />
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</AppCard>
|
|
||||||
</MotionFade>
|
</MotionFade>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<StackedMarqueeSection rows={marqueeRows} className="relative z-10 mt-2 pb-10 sm:pb-12 lg:pb-14" />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
import { AppCard } from "@/components/ui/app-card";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type BentoCardProps = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
icon?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BentoCard({
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
}: BentoCardProps) {
|
||||||
|
return (
|
||||||
|
<AppCard
|
||||||
|
layer="single"
|
||||||
|
interactive
|
||||||
|
className={cn(
|
||||||
|
"group h-full border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:bg-background/80 hover:shadow-lg",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex h-full flex-col gap-6 p-6">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||||
|
{eyebrow}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-2xl font-semibold tracking-[-0.05em] text-foreground transition-colors duration-300 group-hover:text-foreground/95">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
{description ? (
|
||||||
|
<p className="text-sm leading-7 text-muted-foreground">{description}</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{icon ? (
|
||||||
|
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-nested border border-border/70 bg-background text-foreground transition-all duration-300 group-hover:-translate-y-0.5 group-hover:border-border-strong group-hover:shadow-sm">
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</AppCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
BriefcaseBusiness,
|
||||||
|
Layers3,
|
||||||
|
Mail,
|
||||||
|
Sparkles,
|
||||||
|
TimerReset,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { SectionHeading } from "./section-heading";
|
||||||
|
import { BentoCard } from "./bento-card";
|
||||||
|
import type { BentoSectionCopy } from "./types";
|
||||||
|
|
||||||
|
type BentoGridSectionProps = {
|
||||||
|
copy: BentoSectionCopy;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BentoGridSection({ copy }: BentoGridSectionProps) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-8">
|
||||||
|
<SectionHeading
|
||||||
|
eyebrow={copy.eyebrow}
|
||||||
|
title={copy.title}
|
||||||
|
description={copy.description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4 xl:gap-6">
|
||||||
|
<BentoCard
|
||||||
|
eyebrow={copy.about.eyebrow}
|
||||||
|
title={copy.about.title}
|
||||||
|
description={copy.about.description}
|
||||||
|
icon={<BriefcaseBusiness className="h-5 w-5" />}
|
||||||
|
className="xl:col-span-2 xl:row-span-2"
|
||||||
|
>
|
||||||
|
<ul className="grid gap-3 sm:grid-cols-2">
|
||||||
|
{copy.about.points.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="rounded-nested border border-border/70 bg-background px-4 py-3 text-sm text-foreground/88"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</BentoCard>
|
||||||
|
|
||||||
|
<BentoCard
|
||||||
|
eyebrow={copy.coreStack.eyebrow}
|
||||||
|
title={copy.coreStack.title}
|
||||||
|
icon={<Layers3 className="h-5 w-5" />}
|
||||||
|
>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{copy.coreStack.items.map((item) => (
|
||||||
|
<Badge key={item} variant="outline" className="border-border/80 bg-background">
|
||||||
|
{item}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</BentoCard>
|
||||||
|
|
||||||
|
<BentoCard
|
||||||
|
eyebrow={copy.availability.eyebrow}
|
||||||
|
title={copy.availability.title}
|
||||||
|
description={copy.availability.description}
|
||||||
|
icon={<Activity className="h-5 w-5" />}
|
||||||
|
>
|
||||||
|
<Badge variant="success" className="w-fit">
|
||||||
|
{copy.availability.status}
|
||||||
|
</Badge>
|
||||||
|
</BentoCard>
|
||||||
|
|
||||||
|
<BentoCard
|
||||||
|
eyebrow={copy.currentFocus.eyebrow}
|
||||||
|
title={copy.currentFocus.title}
|
||||||
|
icon={<Sparkles className="h-5 w-5" />}
|
||||||
|
>
|
||||||
|
<ul className="space-y-3 text-sm text-foreground/88">
|
||||||
|
{copy.currentFocus.items.map((item) => (
|
||||||
|
<li key={item} className="rounded-nested border border-border/70 bg-background px-4 py-3">
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</BentoCard>
|
||||||
|
|
||||||
|
<BentoCard
|
||||||
|
eyebrow={copy.contact.eyebrow}
|
||||||
|
title={copy.contact.title}
|
||||||
|
description={copy.contact.description}
|
||||||
|
icon={<Mail className="h-5 w-5" />}
|
||||||
|
>
|
||||||
|
<div className="rounded-nested border border-border/70 bg-background px-4 py-4">
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||||
|
{copy.contact.emailLabel}
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-base font-medium text-foreground">
|
||||||
|
{copy.contact.emailValue}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</BentoCard>
|
||||||
|
|
||||||
|
<BentoCard
|
||||||
|
eyebrow={copy.highlights.eyebrow}
|
||||||
|
title={copy.highlights.title}
|
||||||
|
icon={<TimerReset className="h-5 w-5" />}
|
||||||
|
>
|
||||||
|
<div className="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
|
||||||
|
{copy.highlights.items.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.label}
|
||||||
|
className="rounded-nested border border-border/70 bg-background px-4 py-4"
|
||||||
|
>
|
||||||
|
<p className="text-2xl font-semibold tracking-[-0.05em] text-foreground">
|
||||||
|
{item.value}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">{item.label}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</BentoCard>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Bot, Database, Globe, ServerCog } from "lucide-react";
|
||||||
|
|
||||||
|
import { AppCard } from "@/components/ui/app-card";
|
||||||
|
import { SectionHeading } from "./section-heading";
|
||||||
|
import type { CapabilitiesSectionCopy } from "./types";
|
||||||
|
|
||||||
|
type CapabilitiesSectionProps = {
|
||||||
|
copy: CapabilitiesSectionCopy;
|
||||||
|
};
|
||||||
|
|
||||||
|
const capabilityIcons = [Globe, Database, ServerCog, Bot];
|
||||||
|
|
||||||
|
export function CapabilitiesSection({ copy }: CapabilitiesSectionProps) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-8">
|
||||||
|
<SectionHeading
|
||||||
|
eyebrow={copy.eyebrow}
|
||||||
|
title={copy.title}
|
||||||
|
description={copy.description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4 xl:gap-6">
|
||||||
|
{copy.items.map((item, index) => {
|
||||||
|
const Icon = capabilityIcons[index] ?? Globe;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppCard
|
||||||
|
key={item.title}
|
||||||
|
layer="single"
|
||||||
|
interactive
|
||||||
|
className="group border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:shadow-panel"
|
||||||
|
>
|
||||||
|
<div className="flex h-full flex-col gap-5 p-6">
|
||||||
|
<div className="flex h-11 w-11 items-center justify-center rounded-nested border border-border/70 bg-background text-foreground transition-transform duration-300 group-hover:-translate-y-0.5">
|
||||||
|
<Icon className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h3 className="text-xl font-semibold tracking-[-0.04em] text-foreground">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm leading-7 text-muted-foreground">{item.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppCard>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { ArrowUpRight, Mail } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { AppCard } from "@/components/ui/app-card";
|
||||||
|
import type { ContactCtaSectionCopy } from "./types";
|
||||||
|
|
||||||
|
type ContactCtaSectionProps = {
|
||||||
|
copy: ContactCtaSectionCopy;
|
||||||
|
contactHref: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ContactCtaSection({
|
||||||
|
copy,
|
||||||
|
contactHref,
|
||||||
|
}: ContactCtaSectionProps) {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<AppCard className="overflow-hidden">
|
||||||
|
<div className="grid gap-6 rounded-nested border border-border/70 bg-background p-6 sm:p-8 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end lg:p-10">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
||||||
|
{copy.eyebrow}
|
||||||
|
</p>
|
||||||
|
<h2 className="max-w-3xl text-balance text-3xl font-semibold tracking-[-0.05em] text-foreground sm:text-4xl lg:text-5xl">
|
||||||
|
{copy.title}
|
||||||
|
</h2>
|
||||||
|
<p className="max-w-2xl text-sm leading-7 text-muted-foreground sm:text-base">
|
||||||
|
{copy.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-6 text-sm">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-semibold text-foreground">{copy.emailLabel}</p>
|
||||||
|
<a href={`mailto:${copy.emailValue}`} className="text-muted-foreground hover:text-foreground">
|
||||||
|
{copy.emailValue}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-semibold text-foreground">{copy.availabilityLabel}</p>
|
||||||
|
<p className="text-muted-foreground">{copy.availabilityValue}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-3 lg:justify-end">
|
||||||
|
<Button asChild size="lg">
|
||||||
|
<Link href={contactHref}>
|
||||||
|
{copy.contactCta}
|
||||||
|
<Mail className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild size="lg" variant="outline">
|
||||||
|
<a href={copy.githubHref} target="_blank" rel="noreferrer">
|
||||||
|
{copy.githubCta}
|
||||||
|
<ArrowUpRight className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppCard>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { ArrowRight, MapPin, Sparkles } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { AppCard } from "@/components/ui/app-card";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import type { HeroCopy } from "./types";
|
||||||
|
|
||||||
|
type HeroSectionProps = {
|
||||||
|
copy: HeroCopy;
|
||||||
|
contactHref: string;
|
||||||
|
portfolioHref: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function HeroSection({
|
||||||
|
copy,
|
||||||
|
contactHref,
|
||||||
|
portfolioHref,
|
||||||
|
}: HeroSectionProps) {
|
||||||
|
return (
|
||||||
|
<section className="relative pt-8 sm:pt-12 lg:pt-16">
|
||||||
|
<div className="grid gap-6 lg:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)] lg:items-end">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="w-fit border-border/80 bg-background/80 text-foreground shadow-xs"
|
||||||
|
>
|
||||||
|
<span className="mr-2 h-2 w-2 rounded-full bg-status-success" aria-hidden="true" />
|
||||||
|
{copy.availability}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
||||||
|
{copy.name}
|
||||||
|
</p>
|
||||||
|
<h1 className="max-w-4xl text-balance text-5xl font-semibold tracking-[-0.07em] text-foreground sm:text-6xl lg:text-7xl">
|
||||||
|
{copy.headline}
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-2xl text-base leading-8 text-muted-foreground sm:text-lg">
|
||||||
|
{copy.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<Button asChild size="lg" className="shadow-sm">
|
||||||
|
<Link href={contactHref}>
|
||||||
|
{copy.primaryCta}
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
className="border-border/80 bg-background/70 hover:border-border-strong hover:bg-background"
|
||||||
|
>
|
||||||
|
<Link href={portfolioHref}>{copy.secondaryCta}</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{copy.highlights.map((item) => (
|
||||||
|
<Badge
|
||||||
|
key={item}
|
||||||
|
variant="secondary"
|
||||||
|
className="bg-secondary/70 text-foreground shadow-none"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AppCard className="overflow-hidden">
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
|
<MetricCard label={copy.roleLabel} value={copy.roleValue} />
|
||||||
|
<MetricCard label={copy.locationLabel} value={copy.locationValue} icon={MapPin} />
|
||||||
|
</div>
|
||||||
|
<MetricCard label={copy.focusLabel} value={copy.focusValue} icon={Sparkles} strong />
|
||||||
|
</div>
|
||||||
|
</AppCard>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetricCardProps = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
strong?: boolean;
|
||||||
|
icon?: typeof MapPin;
|
||||||
|
};
|
||||||
|
|
||||||
|
function MetricCard({ label, value, strong = false, icon: Icon }: MetricCardProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"rounded-nested border border-border/70 bg-background p-5 transition-all duration-300",
|
||||||
|
strong && "bg-surface-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||||
|
{Icon ? <Icon className="h-3.5 w-3.5" /> : null}
|
||||||
|
<span>{label}</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 text-xl font-semibold tracking-[-0.04em] text-foreground">
|
||||||
|
{value}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { StackedMarqueeSection, type MarqueeRow } from "@/components/layout/stacked-marquee-section";
|
||||||
|
import { AppCard } from "@/components/ui/app-card";
|
||||||
|
import { SectionHeading } from "./section-heading";
|
||||||
|
import type { MarqueeSectionCopy } from "./types";
|
||||||
|
|
||||||
|
type MarqueeSectionProps = {
|
||||||
|
copy: MarqueeSectionCopy;
|
||||||
|
rows: MarqueeRow[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MarqueeSection({ copy, rows }: MarqueeSectionProps) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-8 overflow-hidden">
|
||||||
|
<SectionHeading
|
||||||
|
eyebrow={copy.eyebrow}
|
||||||
|
title={copy.title}
|
||||||
|
description={copy.description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AppCard className="overflow-hidden">
|
||||||
|
<div className="rounded-nested border border-border/70 bg-background py-6">
|
||||||
|
<StackedMarqueeSection rows={rows} className="py-0" />
|
||||||
|
</div>
|
||||||
|
</AppCard>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { AppCard } from "@/components/ui/app-card";
|
||||||
|
import { SectionHeading } from "./section-heading";
|
||||||
|
import type { ProcessSectionCopy } from "./types";
|
||||||
|
|
||||||
|
type ProcessSectionProps = {
|
||||||
|
copy: ProcessSectionCopy;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ProcessSection({ copy }: ProcessSectionProps) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-8">
|
||||||
|
<SectionHeading
|
||||||
|
eyebrow={copy.eyebrow}
|
||||||
|
title={copy.title}
|
||||||
|
description={copy.description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4 xl:gap-6">
|
||||||
|
{copy.steps.map((step, index) => (
|
||||||
|
<AppCard
|
||||||
|
key={step.title}
|
||||||
|
layer="single"
|
||||||
|
interactive
|
||||||
|
className="group border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:shadow-panel"
|
||||||
|
>
|
||||||
|
<div className="flex h-full flex-col gap-5 p-6">
|
||||||
|
<p className="text-sm font-semibold tracking-[0.18em] text-muted-foreground">
|
||||||
|
{String(index + 1).padStart(2, "0")}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h3 className="text-2xl font-semibold tracking-[-0.05em] text-foreground">
|
||||||
|
{step.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm leading-7 text-muted-foreground">{step.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppCard>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { ArrowUpRight } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { AppCard } from "@/components/ui/app-card";
|
||||||
|
import { SectionHeading } from "./section-heading";
|
||||||
|
import type { ProjectCardCopy, ProjectsSectionCopy } from "./types";
|
||||||
|
|
||||||
|
type ProjectsSectionProps = {
|
||||||
|
copy: ProjectsSectionCopy;
|
||||||
|
items: ProjectCardCopy[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ProjectsSection({ copy, items }: ProjectsSectionProps) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-10">
|
||||||
|
<SectionHeading
|
||||||
|
eyebrow={copy.eyebrow}
|
||||||
|
title={copy.title}
|
||||||
|
description={copy.description}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3 xl:gap-6">
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<AppCard
|
||||||
|
key={`${item.title}-${index}`}
|
||||||
|
layer="single"
|
||||||
|
interactive
|
||||||
|
className={[
|
||||||
|
"group h-full border-border/80 bg-surface-2 transition-all duration-300 hover:-translate-y-1 hover:border-border-strong hover:bg-background/80 hover:shadow-lg",
|
||||||
|
index === 0 ? "lg:col-span-2" : "",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
<div className="flex h-full flex-col gap-6 p-6">
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{item.meta.map((metaItem) => (
|
||||||
|
<Badge
|
||||||
|
key={metaItem}
|
||||||
|
variant="outline"
|
||||||
|
className="border-border/80 bg-background text-muted-foreground transition-colors duration-300 group-hover:border-border-strong group-hover:text-foreground/80"
|
||||||
|
>
|
||||||
|
{metaItem}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h3 className="text-2xl font-semibold tracking-[-0.05em] text-foreground transition-colors duration-300 group-hover:text-foreground/95">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm leading-7 text-muted-foreground">{item.summary}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||||
|
{copy.stackLabel}
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{item.stack.map((stackItem) => (
|
||||||
|
<Badge
|
||||||
|
key={stackItem}
|
||||||
|
variant="secondary"
|
||||||
|
className="bg-secondary/70 text-foreground transition-colors duration-300 group-hover:bg-secondary"
|
||||||
|
>
|
||||||
|
{stackItem}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-auto">
|
||||||
|
<Link
|
||||||
|
href={item.href}
|
||||||
|
className="inline-flex items-center gap-2 text-sm font-medium text-foreground transition-all duration-200 group-hover:translate-x-0.5"
|
||||||
|
>
|
||||||
|
{item.cta}
|
||||||
|
<ArrowUpRight className="h-4 w-4 transition-transform duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppCard>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type SectionHeadingProps = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
align?: "left" | "center";
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SectionHeading({
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
align = "left",
|
||||||
|
className,
|
||||||
|
}: SectionHeadingProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"space-y-4",
|
||||||
|
align === "center" && "mx-auto max-w-3xl text-center",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
||||||
|
{eyebrow}
|
||||||
|
</p>
|
||||||
|
<h2 className="max-w-4xl text-balance text-3xl font-semibold tracking-[-0.05em] text-foreground sm:text-4xl lg:text-5xl">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<p className="max-w-3xl text-sm leading-7 text-muted-foreground sm:text-[1.02rem]">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
export type HeroCopy = {
|
||||||
|
availability: string;
|
||||||
|
name: string;
|
||||||
|
headline: string;
|
||||||
|
description: string;
|
||||||
|
primaryCta: string;
|
||||||
|
secondaryCta: string;
|
||||||
|
locationLabel: string;
|
||||||
|
locationValue: string;
|
||||||
|
roleLabel: string;
|
||||||
|
roleValue: string;
|
||||||
|
focusLabel: string;
|
||||||
|
focusValue: string;
|
||||||
|
highlights: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BentoSectionCopy = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
about: {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
points: string[];
|
||||||
|
};
|
||||||
|
coreStack: {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
items: string[];
|
||||||
|
};
|
||||||
|
availability: {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
currentFocus: {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
items: string[];
|
||||||
|
};
|
||||||
|
contact: {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
emailLabel: string;
|
||||||
|
emailValue: string;
|
||||||
|
};
|
||||||
|
highlights: {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
items: Array<{
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MarqueeSectionCopy = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProjectCardCopy = {
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
stack: string[];
|
||||||
|
href: string;
|
||||||
|
cta: string;
|
||||||
|
meta: string[];
|
||||||
|
featured?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProjectsSectionCopy = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
stackLabel: string;
|
||||||
|
fallbackItems: Array<{
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
stack: string[];
|
||||||
|
href: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CapabilitiesSectionCopy = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
items: Array<{
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProcessSectionCopy = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
steps: Array<{
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ContactCtaSectionCopy = {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
contactCta: string;
|
||||||
|
githubCta: string;
|
||||||
|
emailLabel: string;
|
||||||
|
emailValue: string;
|
||||||
|
availabilityLabel: string;
|
||||||
|
availabilityValue: string;
|
||||||
|
githubHref: string;
|
||||||
|
};
|
||||||
+218
-17
@@ -36,23 +36,224 @@
|
|||||||
"secondaryCta": "العودة للرئيسية"
|
"secondaryCta": "العودة للرئيسية"
|
||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
"heroKicker": "مرحبااا، أنا محمد",
|
"meta": {
|
||||||
"heroTitle": "مطوّر واجهات ويب\nومصمّم جرافيك",
|
"title": "معرض أعمال مطور Full-stack",
|
||||||
"heroText": "بشتغل على حلول رقمية لمشاكل حقيقية\nبتطلع ممتازة وبتشتغل بكفاءة.",
|
"description": "منتجات رقمية حديثة وأنظمة قابلة للتوسع وتجارب متعددة اللغات مبنية بعقلية منتج واضحة."
|
||||||
"servicesEyebrow": "شو فيني قدّم",
|
},
|
||||||
"servicesTitle": "حلول رقمية مصممة حسب شغلك، مو قوالب جاهزة.",
|
"heroVisual": {
|
||||||
"servicesText": "من الهوية والموقع، للمتجر والسيو والتراكنغ، لحد الأنظمة الخاصة الداخلية. الفكرة إنو كل شيء يطلع مرتب، مفهوم، وقابل للنمو.",
|
"kicker": "مرحبا، أنا محمد",
|
||||||
"servicesPrimaryCta": "احكيلي عن مشروعك",
|
"title": "مطوّر واجهات ويب\nومصمّم جرافيك",
|
||||||
"servicesSecondaryCta": "شوف شغلي",
|
"description": "أبني حلول رقمية لمشاكل حقيقية\nتبدو قوية وتعمل بكفاءة."
|
||||||
"serviceBrand": "بناء براند واضح ومرتب.",
|
},
|
||||||
"serviceWebsite": "موقع إلكتروني يليق بشغلك.",
|
"hero": {
|
||||||
"serviceStore": "متجر أسهل بالعرض والبيع.",
|
"availability": "متاح لمشاريع مختارة",
|
||||||
"serviceWordPress": "إضافة WordPress خاصة فيك.",
|
"name": "محمد فراواتي",
|
||||||
"serviceSeo": "تحسين الظهور بمحركات البحث.",
|
"headline": "منتجات Full-stack تجمع بين تنفيذ Frontend قوي وأنظمة Backend موثوقة.",
|
||||||
"serviceTracking": "تراكنغ واضح وفهم لحركة الزوار.",
|
"description": "أصمم وأبني تجارب ويب بمستوى عالي لفرق تحتاج وضوح وسرعة وتنفيذ جاهز للإنتاج من أول الفكرة حتى الإطلاق.",
|
||||||
"serviceSystem": "سستم خاص لشركتك أو لفريقك.",
|
"primaryCta": "ابدأ مشروعك",
|
||||||
"toPortfolio": "عرض الأعمال",
|
"secondaryCta": "شوف الأعمال المختارة",
|
||||||
"toContact": "تواصل معي"
|
"locationLabel": "الموقع",
|
||||||
|
"locationValue": "برلين، ألمانيا",
|
||||||
|
"roleLabel": "الدور",
|
||||||
|
"roleValue": "مطور Full-stack",
|
||||||
|
"focusLabel": "التركيز الحالي",
|
||||||
|
"focusValue": "منتجات عالية الأداء وأتمتة وعمل منصات قابلة للصيانة.",
|
||||||
|
"highlights": [
|
||||||
|
"تسليم بثلاث لغات",
|
||||||
|
"تنفيذ بعقلية منتج",
|
||||||
|
"شريك تقني لفرق جاهزة للإطلاق"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bento": {
|
||||||
|
"eyebrow": "لمحات",
|
||||||
|
"title": "مبني لفرق تهتم بالجودة والسرعة والقرارات التقنية النظيفة.",
|
||||||
|
"description": "تنظيم الصفحة يعكس أسلوب شغلي: وضوح أولاً، تنفيذ قوي ثانياً، ومن دون تعقيد زائد بالنص.",
|
||||||
|
"about": {
|
||||||
|
"eyebrow": "عني",
|
||||||
|
"title": "أشتغل بين المنتج والهندسة والتنفيذ.",
|
||||||
|
"description": "شغلي يجمع بين التفكير المنتج والتنفيذ العملي، حتى يحصل المؤسسون والوكالات والفرق التقنية على مطور قادر ينقل المشروع من المعمارية للتفاصيل النهائية بدون ما يضيع المستوى.",
|
||||||
|
"points": [
|
||||||
|
"أنظمة Frontend مع انضباط UX واضح",
|
||||||
|
"خدمات Backend وربط أنظمة لسير عمل حقيقي",
|
||||||
|
"تواصل واضح مع أصحاب القرار التقني وغير التقني",
|
||||||
|
"تسليم مع قابلية صيانة محسوبة من أول يوم"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coreStack": {
|
||||||
|
"eyebrow": "Core stack",
|
||||||
|
"title": "أدواتي اليومية",
|
||||||
|
"items": [
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"React",
|
||||||
|
"Node.js",
|
||||||
|
"PostgreSQL",
|
||||||
|
"Tailwind CSS",
|
||||||
|
"Prisma",
|
||||||
|
"Docker"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"availability": {
|
||||||
|
"eyebrow": "التوفر",
|
||||||
|
"title": "جاهز لتعاونات مركزة",
|
||||||
|
"description": "أفضل مناسبة لستارت أب ووكالات وفرق تريد شخصاً تقنياً واحداً يدير التنفيذ من البداية للنهاية.",
|
||||||
|
"status": "فرص جديدة متاحة"
|
||||||
|
},
|
||||||
|
"currentFocus": {
|
||||||
|
"eyebrow": "التركيز الحالي",
|
||||||
|
"title": "أكثر شيء أعمل عليه حالياً",
|
||||||
|
"items": [
|
||||||
|
"مواقع تسويقية بمستوى UX قريب من المنتجات",
|
||||||
|
"لوحات تحكم وأدوات داخلية للإدارة",
|
||||||
|
"مسارات أتمتة للمحتوى والعمليات"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"eyebrow": "تواصل سريع",
|
||||||
|
"title": "أقصر طريق للوصول إلي",
|
||||||
|
"description": "إذا كان الطلب واضحاً من الآن، أرسل السياق والمدة الزمنية وأرجع لك بالخطوة الصحيحة مباشرة.",
|
||||||
|
"emailLabel": "البريد الإلكتروني",
|
||||||
|
"emailValue": "hello@moh-sass.dev"
|
||||||
|
},
|
||||||
|
"highlights": {
|
||||||
|
"eyebrow": "لمحات",
|
||||||
|
"title": "مبادئ العمل",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "3",
|
||||||
|
"label": "لغات مدعومة"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "E2E",
|
||||||
|
"label": "ملكية تسليم كاملة"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "24/7",
|
||||||
|
"label": "عقلية إنتاج"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"marquee": {
|
||||||
|
"eyebrow": "الأدوات المعتمدة",
|
||||||
|
"title": "Stack عملي مختار على أساس الصيانة والسرعة والقيمة الطويلة المدى.",
|
||||||
|
"description": "قسم Marquee يستخدم إعدادات الموقع الموجودة مسبقاً حتى تبقى الصفحة متناسقة مع الأدوات والقدرات التي تريد إبرازها."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"eyebrow": "مشاريع مختارة",
|
||||||
|
"title": "ثلاثة أمثلة على نوع الشغل الذي أحب أن أطلقه.",
|
||||||
|
"description": "الأعمال المختارة تجمع بين جودة واجهة قوية وبنية تقنية تبقى ثابتة بعد الإطلاق.",
|
||||||
|
"stackLabel": "Stack",
|
||||||
|
"cta": "فتح المشروع",
|
||||||
|
"fallbackMeta": "عمل مختار",
|
||||||
|
"stackSets": [
|
||||||
|
[
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"Design Systems"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"APIs",
|
||||||
|
"PostgreSQL",
|
||||||
|
"Automation"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Tailwind CSS",
|
||||||
|
"Prisma",
|
||||||
|
"Infrastructure"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"fallbackItems": [
|
||||||
|
{
|
||||||
|
"title": "إعادة بناء منصة منتج",
|
||||||
|
"summary": "تحديث عملي لمنصة خدمات مع UX أوضح ومعمارية أقوى ودورات تسليم أسرع.",
|
||||||
|
"stack": [
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"PostgreSQL"
|
||||||
|
],
|
||||||
|
"href": "/portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "لوحة عمليات داخلية",
|
||||||
|
"summary": "أداة داخلية تركز على تسريع القرار وتنظيف سير العمل وتقليل التنسيق اليدوي بين الفرق.",
|
||||||
|
"stack": [
|
||||||
|
"React",
|
||||||
|
"Node.js",
|
||||||
|
"Automation"
|
||||||
|
],
|
||||||
|
"href": "/portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "نظام مواقع للإطلاقات",
|
||||||
|
"summary": "أساس قابل لإعادة الاستخدام لإطلاق مواقع بسرعة بدون التنازل عن الجودة التقنية أو التحكم بالمحتوى.",
|
||||||
|
"stack": [
|
||||||
|
"Tailwind CSS",
|
||||||
|
"CMS",
|
||||||
|
"Performance"
|
||||||
|
],
|
||||||
|
"href": "/contact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"eyebrow": "القدرات",
|
||||||
|
"title": "تغطية تقنية من الطرف للطرف بدون خسارة تركيز المنتج.",
|
||||||
|
"description": "كل مجال هنا مبني على شغل إنتاجي فعلي، وليس مجرد تسميات خدمات عامة.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "Frontend Engineering",
|
||||||
|
"description": "واجهات Responsive وأنظمة تصميم وتفاصيل تفاعل ومعمارية Components قابلة للتوسع."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Backend Systems",
|
||||||
|
"description": "APIs منظمة وربط أنظمة ونمذجة بيانات ومنطق تطبيقي مصمم لتسليم يمكن الاعتماد عليه."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "DevOps & Infrastructure",
|
||||||
|
"description": "نشر وبيئات وتشغيل ومراقبة أساسية وقرارات Infrastructure تقلل العبء التشغيلي."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Automation & AI Workflows",
|
||||||
|
"description": "طبقات أتمتة عملية تقلل العمل اليدوي وتربط بين المنتج والمحتوى والعمليات."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"eyebrow": "آلية العمل",
|
||||||
|
"title": "مسار مختصر يبقي المشروع متقدماً.",
|
||||||
|
"description": "العملية مقصودة أن تكون بسيطة: نقلل الغموض مبكراً، نثبت القرارات التقنية، ثم نطلق بانضباط.",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Discover",
|
||||||
|
"description": "نحدد الأهداف والقيود والجمهور والنتيجة الحقيقية المطلوبة من المنتج."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Architect",
|
||||||
|
"description": "نرسم شكل النظام وحدوده التقنية وبنية المحتوى ومسار التنفيذ قبل الدخول في البناء الثقيل."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Build",
|
||||||
|
"description": "ننّفذ مع عناية قوية بالواجهة ومنطق تطبيق نظيف وبنية قابلة للصيانة عبر كامل الـ stack."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ship",
|
||||||
|
"description": "نطلق بثقة ونتأكد من النتيجة ونترك المنتج بحالة مستقرة للمرحلة التالية."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contactCta": {
|
||||||
|
"eyebrow": "ابدأ الحديث",
|
||||||
|
"title": "إذا المشروع يحتاج تنفيذ قوي، أقدر أدفعه للأمام.",
|
||||||
|
"description": "هات الملخص أو الاختناق الحالي أو الاتجاه العام. أقدر أساعد في ضبط النطاق وتصميم النظام وبناء المنتج.",
|
||||||
|
"contactCta": "تواصل معي",
|
||||||
|
"githubCta": "GitHub",
|
||||||
|
"emailLabel": "البريد الإلكتروني",
|
||||||
|
"emailValue": "hello@moh-sass.dev",
|
||||||
|
"availabilityLabel": "التوفر",
|
||||||
|
"availabilityValue": "مفتوح لشغل منتجات مركز",
|
||||||
|
"githubHref": "https://github.com/mohfarawati"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"portfolioPage": {
|
"portfolioPage": {
|
||||||
"title": "الأعمال",
|
"title": "الأعمال",
|
||||||
|
|||||||
+218
-17
@@ -36,23 +36,224 @@
|
|||||||
"secondaryCta": "Zur Startseite"
|
"secondaryCta": "Zur Startseite"
|
||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
"heroKicker": "Hi, ich bin Moh",
|
"meta": {
|
||||||
"heroTitle": "frontend\ndeveloper\n& designer",
|
"title": "Portfolio fuer Full-stack Entwicklung",
|
||||||
"heroText": "Ich entwickle digitale Lösungen für echte Probleme,\ndie gut aussehen und sauber funktionieren.",
|
"description": "Moderne digitale Produkte, skalierbare Systeme und mehrsprachige Erlebnisse mit starkem Produktfokus."
|
||||||
"servicesEyebrow": "Leistungen",
|
},
|
||||||
"servicesTitle": "Digitale Arbeit, die sich an deinem Geschäft orientiert und nicht an Vorlagen.",
|
"heroVisual": {
|
||||||
"servicesText": "Von Markenaufbau und Websites bis zu Shops, SEO, Tracking, WordPress Plugins und internen Systemen geht es immer um dasselbe: mehr Klarheit, bessere Wirkung und eine saubere Grundlage für Wachstum.",
|
"kicker": "Hi, ich bin Moh",
|
||||||
"servicesPrimaryCta": "Erzähl mir von deinem Projekt",
|
"title": "frontend\ndeveloper\n& designer",
|
||||||
"servicesSecondaryCta": "Meine Arbeiten ansehen",
|
"description": "Ich entwickle digitale Loesungen fuer echte Probleme,\ndie gut aussehen und sauber funktionieren."
|
||||||
"serviceBrand": "Markenaufbau mit klarer Richtung.",
|
},
|
||||||
"serviceWebsite": "Websites mit sauberer Struktur.",
|
"hero": {
|
||||||
"serviceStore": "Shops, die klarer verkaufen.",
|
"availability": "Verfuegbar fuer ausgewaehlte Projekte",
|
||||||
"serviceWordPress": "Eigene WordPress Plugins.",
|
"name": "Moh Farawati",
|
||||||
"serviceSeo": "Mehr Sichtbarkeit in Suchmaschinen.",
|
"headline": "Full-stack Produkte mit starker Frontend Umsetzung und verlaesslichen Backend Systemen.",
|
||||||
"serviceTracking": "Tracking und bessere Auswertung.",
|
"description": "Ich konzipiere und entwickle hochwertige Web-Erlebnisse fuer Teams, die Klarheit, Geschwindigkeit und produktionsreife Umsetzung von der Strategie bis zum Launch brauchen.",
|
||||||
"serviceSystem": "Interne Systeme für Teams und Firmen.",
|
"primaryCta": "Projekt starten",
|
||||||
"toPortfolio": "Portfolio ansehen",
|
"secondaryCta": "Ausgewaehlte Arbeiten",
|
||||||
"toContact": "Kontakt aufnehmen"
|
"locationLabel": "Standort",
|
||||||
|
"locationValue": "Berlin, Deutschland",
|
||||||
|
"roleLabel": "Rolle",
|
||||||
|
"roleValue": "Full-stack Entwickler",
|
||||||
|
"focusLabel": "Aktueller Fokus",
|
||||||
|
"focusValue": "Performante Produkte, Automatisierung und wartbare Plattformarbeit.",
|
||||||
|
"highlights": [
|
||||||
|
"3-sprachige Umsetzung",
|
||||||
|
"Produktorientierte Ausfuehrung",
|
||||||
|
"Technischer Partner fuer launchbereite Teams"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bento": {
|
||||||
|
"eyebrow": "Highlights",
|
||||||
|
"title": "Gebaut fuer Teams, die Qualitaet, Tempo und saubere technische Entscheidungen ernst nehmen.",
|
||||||
|
"description": "Die Startseite ist so aufgebaut, wie ich arbeite: erst Klarheit, dann starke Umsetzung, ohne unnoetige Komplexitaet dazwischen.",
|
||||||
|
"about": {
|
||||||
|
"eyebrow": "Ueber mich",
|
||||||
|
"title": "Ich arbeite ueber Produkt, Engineering und Delivery hinweg.",
|
||||||
|
"description": "Meine Arbeit verbindet Produktdenken mit direkter Umsetzung. So bekommen Gruender, Agenturen und technische Teams einen Entwickler, der von Architektur bis zum finalen Detail liefern kann, ohne an Qualitaet zu verlieren.",
|
||||||
|
"points": [
|
||||||
|
"Frontend Systeme mit klarer UX Disziplin",
|
||||||
|
"Backend Services und Integrationen fuer reale Prozesse",
|
||||||
|
"Klare Kommunikation mit technischen und nicht-technischen Stakeholdern",
|
||||||
|
"Lieferung mit Blick auf Wartbarkeit ab dem ersten Tag"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coreStack": {
|
||||||
|
"eyebrow": "Core stack",
|
||||||
|
"title": "Taegliche Werkzeuge",
|
||||||
|
"items": [
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"React",
|
||||||
|
"Node.js",
|
||||||
|
"PostgreSQL",
|
||||||
|
"Tailwind CSS",
|
||||||
|
"Prisma",
|
||||||
|
"Docker"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"availability": {
|
||||||
|
"eyebrow": "Verfuegbarkeit",
|
||||||
|
"title": "Offen fuer fokussierte Zusammenarbeit",
|
||||||
|
"description": "Am besten passend fuer Startups, Agenturen und Teams, die einen technischen Owner suchen, der die Umsetzung Ende zu Ende treiben kann.",
|
||||||
|
"status": "Neue Slots offen"
|
||||||
|
},
|
||||||
|
"currentFocus": {
|
||||||
|
"eyebrow": "Aktueller Fokus",
|
||||||
|
"title": "Woran ich gerade am meisten baue",
|
||||||
|
"items": [
|
||||||
|
"Marketing Websites mit Produktniveau in der UX",
|
||||||
|
"Admin Tools und interne Dashboards",
|
||||||
|
"Automatisierungsablaeufe fuer Content und Operations"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"eyebrow": "Schneller Kontakt",
|
||||||
|
"title": "Direkter Weg zu mir",
|
||||||
|
"description": "Wenn das Briefing schon klar ist, schick Kontext und Zeitrahmen und ich melde mich mit dem richtigen naechsten Schritt.",
|
||||||
|
"emailLabel": "E-Mail",
|
||||||
|
"emailValue": "hello@moh-sass.dev"
|
||||||
|
},
|
||||||
|
"highlights": {
|
||||||
|
"eyebrow": "Highlights",
|
||||||
|
"title": "Arbeitsprinzipien",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "3",
|
||||||
|
"label": "Unterstuetzte Sprachen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "E2E",
|
||||||
|
"label": "Delivery Ownership"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "24/7",
|
||||||
|
"label": "Produktionsdenken"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"marquee": {
|
||||||
|
"eyebrow": "Werkzeuge",
|
||||||
|
"title": "Ein Arbeits-Stack, ausgewaehlt fuer Wartbarkeit, Geschwindigkeit und langfristigen Hebel.",
|
||||||
|
"description": "Das Marquee nutzt die bestehende Site-Konfiguration, damit die Startseite mit den hervorgehobenen Tools und Kompetenzen konsistent bleibt."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"eyebrow": "Ausgewaehlte Projekte",
|
||||||
|
"title": "Drei Beispiele fuer die Art von Arbeit, die ich gerne ausliefere.",
|
||||||
|
"description": "Ausgewaehlte Projekte verbinden starke Interface-Qualitaet mit technischer Struktur, die auch nach dem Launch stabil bleibt.",
|
||||||
|
"stackLabel": "Stack",
|
||||||
|
"cta": "Projekt oeffnen",
|
||||||
|
"fallbackMeta": "Kuratiertes Projekt",
|
||||||
|
"stackSets": [
|
||||||
|
[
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"Design Systems"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"APIs",
|
||||||
|
"PostgreSQL",
|
||||||
|
"Automation"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Tailwind CSS",
|
||||||
|
"Prisma",
|
||||||
|
"Infrastructure"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"fallbackItems": [
|
||||||
|
{
|
||||||
|
"title": "Produktplattform Relaunch",
|
||||||
|
"summary": "Eine produktionsreife Erneuerung fuer eine Service-Plattform mit klarerer UX, besserer Architektur und schnelleren Lieferzyklen.",
|
||||||
|
"stack": [
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"PostgreSQL"
|
||||||
|
],
|
||||||
|
"href": "/portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Operations Dashboard",
|
||||||
|
"summary": "Interne Tools mit Fokus auf schnellere Entscheidungen, sauberere Workflows und weniger manuelle Abstimmung im Team.",
|
||||||
|
"stack": [
|
||||||
|
"React",
|
||||||
|
"Node.js",
|
||||||
|
"Automation"
|
||||||
|
],
|
||||||
|
"href": "/portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Launch Website System",
|
||||||
|
"summary": "Eine wiederverwendbare Website Grundlage fuer schnelle Launches ohne Abstriche bei technischer Qualitaet oder redaktioneller Kontrolle.",
|
||||||
|
"stack": [
|
||||||
|
"Tailwind CSS",
|
||||||
|
"CMS",
|
||||||
|
"Performance"
|
||||||
|
],
|
||||||
|
"href": "/contact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"eyebrow": "Capabilities",
|
||||||
|
"title": "Technische Abdeckung von Ende zu Ende ohne den Produktfokus zu verlieren.",
|
||||||
|
"description": "Jeder Bereich ist um echte Produktionsarbeit aufgebaut und nicht um generische Service-Begriffe.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "Frontend Engineering",
|
||||||
|
"description": "Responsive Interfaces, Design Systems, Interaktionsqualitaet und Komponentenarchitektur fuer skalierbare Produkte."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Backend Systems",
|
||||||
|
"description": "Strukturierte APIs, Integrationen, Datenmodelle und Applikationslogik fuer verlaessliche Umsetzung."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "DevOps & Infrastructure",
|
||||||
|
"description": "Deployments, Umgebungen, grundlegende Observability und Infrastrukturentscheidungen mit weniger operativer Reibung."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Automation & AI Workflows",
|
||||||
|
"description": "Praktische Automatisierungsebenen, die manuelle Arbeit reduzieren und Produkt, Content und Operations verbinden."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"eyebrow": "Prozess",
|
||||||
|
"title": "Ein kompakter Ablauf, der Projekte in Bewegung haelt.",
|
||||||
|
"description": "Der Prozess ist absichtlich einfach: Unklarheit frueh reduzieren, solide technische Entscheidungen treffen und diszipliniert ausliefern.",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Discover",
|
||||||
|
"description": "Ziele, Grenzen, Zielgruppe und das eigentliche Ergebnis klaeren, das das Produkt erreichen soll."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Architect",
|
||||||
|
"description": "Systemform, technische Grenzen, Inhaltsstruktur und Lieferweg definieren, bevor die schwere Umsetzung startet."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Build",
|
||||||
|
"description": "Mit starker UI Qualitaet, sauberer Applikationslogik und wartbarer Struktur ueber den gesamten Stack umsetzen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ship",
|
||||||
|
"description": "Sicher launchen, das Ergebnis validieren und das Produkt stabil fuer die naechste Phase hinterlassen."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contactCta": {
|
||||||
|
"eyebrow": "Gespraech starten",
|
||||||
|
"title": "Wenn das Projekt starke Umsetzung braucht, kann ich es voranbringen.",
|
||||||
|
"description": "Bring das Briefing, den Engpass oder die grobe Richtung mit. Ich kann Scope schaerfen, das System entwerfen und das Produkt bauen.",
|
||||||
|
"contactCta": "Kontakt aufnehmen",
|
||||||
|
"githubCta": "GitHub",
|
||||||
|
"emailLabel": "E-Mail",
|
||||||
|
"emailValue": "hello@moh-sass.dev",
|
||||||
|
"availabilityLabel": "Verfuegbarkeit",
|
||||||
|
"availabilityValue": "Offen fuer fokussierte Produktarbeit",
|
||||||
|
"githubHref": "https://github.com/mohfarawati"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"portfolioPage": {
|
"portfolioPage": {
|
||||||
"title": "Portfolio",
|
"title": "Portfolio",
|
||||||
|
|||||||
+218
-17
@@ -36,23 +36,224 @@
|
|||||||
"secondaryCta": "Back to homepage"
|
"secondaryCta": "Back to homepage"
|
||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
"heroKicker": "Hi, I am Moh",
|
"meta": {
|
||||||
"heroTitle": "frontend\ndeveloper\n& designer",
|
"title": "Full-stack developer portfolio",
|
||||||
"heroText": "I craft digital solutions for human problems\nthat look good and work well.",
|
"description": "Modern digital products, scalable systems, and multilingual experiences built with product focus."
|
||||||
"servicesEyebrow": "Capabilities",
|
},
|
||||||
"servicesTitle": "Digital work shaped around what your business actually needs.",
|
"heroVisual": {
|
||||||
"servicesText": "From brand direction and websites to stores, SEO, tracking, WordPress plugins, and internal systems, the goal is the same: clearer structure, better performance, and room to grow.",
|
"kicker": "Hi, I am Moh",
|
||||||
"servicesPrimaryCta": "Tell me about your project",
|
"title": "frontend\ndeveloper\n& designer",
|
||||||
"servicesSecondaryCta": "See my work",
|
"description": "I craft digital solutions for human problems\nthat look good and work well."
|
||||||
"serviceBrand": "Brand systems and visual direction.",
|
},
|
||||||
"serviceWebsite": "Business websites with clear structure.",
|
"hero": {
|
||||||
"serviceStore": "Stores built for easier selling.",
|
"availability": "Available for selected projects",
|
||||||
"serviceWordPress": "Custom WordPress plugin development.",
|
"name": "Moh Farawati",
|
||||||
"serviceSeo": "Search visibility and technical SEO.",
|
"headline": "Full-stack products with sharp frontend execution and reliable backend systems.",
|
||||||
"serviceTracking": "Tracking setup and user insights.",
|
"description": "I design and build premium web experiences for teams that need clarity, speed, and production-ready engineering from strategy to launch.",
|
||||||
"serviceSystem": "Custom internal systems for teams.",
|
"primaryCta": "Start a project",
|
||||||
"toPortfolio": "View portfolio",
|
"secondaryCta": "View selected work",
|
||||||
"toContact": "Contact me"
|
"locationLabel": "Base",
|
||||||
|
"locationValue": "Berlin, Germany",
|
||||||
|
"roleLabel": "Role",
|
||||||
|
"roleValue": "Full-stack developer",
|
||||||
|
"focusLabel": "Current focus",
|
||||||
|
"focusValue": "High-performance products, automation, and maintainable platform work.",
|
||||||
|
"highlights": [
|
||||||
|
"3-language delivery",
|
||||||
|
"Product-minded execution",
|
||||||
|
"Technical partner for launch-ready teams"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bento": {
|
||||||
|
"eyebrow": "Highlights",
|
||||||
|
"title": "Built for teams that care about quality, speed, and clean technical decisions.",
|
||||||
|
"description": "The homepage is structured around the way I work: strategy first, strong execution second, and no wasted surface area in between.",
|
||||||
|
"about": {
|
||||||
|
"eyebrow": "About me",
|
||||||
|
"title": "I work across product, engineering, and delivery.",
|
||||||
|
"description": "My work combines product thinking with hands-on implementation, so founders, agencies, and technical teams get a developer who can move from architecture to shipped detail without losing quality.",
|
||||||
|
"points": [
|
||||||
|
"Frontend systems with strong UX discipline",
|
||||||
|
"Backend services and integrations for real workflows",
|
||||||
|
"Clear communication with technical and non-technical stakeholders",
|
||||||
|
"Shipping with maintainability in mind from day one"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"coreStack": {
|
||||||
|
"eyebrow": "Core stack",
|
||||||
|
"title": "Daily tools",
|
||||||
|
"items": [
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"React",
|
||||||
|
"Node.js",
|
||||||
|
"PostgreSQL",
|
||||||
|
"Tailwind CSS",
|
||||||
|
"Prisma",
|
||||||
|
"Docker"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"availability": {
|
||||||
|
"eyebrow": "Availability",
|
||||||
|
"title": "Open for focused collaborations",
|
||||||
|
"description": "Best fit for startups, agencies, and teams that want one technical owner who can drive implementation end to end.",
|
||||||
|
"status": "New slots open"
|
||||||
|
},
|
||||||
|
"currentFocus": {
|
||||||
|
"eyebrow": "Current focus",
|
||||||
|
"title": "What I am building most",
|
||||||
|
"items": [
|
||||||
|
"Marketing sites with product-grade UX",
|
||||||
|
"Admin tooling and internal dashboards",
|
||||||
|
"Automation flows around content and operations"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"eyebrow": "Quick contact",
|
||||||
|
"title": "Short path to reach me",
|
||||||
|
"description": "If the brief is already clear, send the context and timeline and I will reply with the right next step.",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailValue": "hello@moh-sass.dev"
|
||||||
|
},
|
||||||
|
"highlights": {
|
||||||
|
"eyebrow": "Highlights",
|
||||||
|
"title": "Working principles",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "3",
|
||||||
|
"label": "Supported languages"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "E2E",
|
||||||
|
"label": "Delivery ownership"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "24/7",
|
||||||
|
"label": "Production mindset"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"marquee": {
|
||||||
|
"eyebrow": "Trusted tools",
|
||||||
|
"title": "A working stack chosen for maintainability, speed, and long-term leverage.",
|
||||||
|
"description": "The marquee pulls from the existing site configuration so the homepage stays aligned with the tools and capabilities you want to highlight."
|
||||||
|
},
|
||||||
|
"projects": {
|
||||||
|
"eyebrow": "Selected projects",
|
||||||
|
"title": "Three examples of the kind of work I like to ship.",
|
||||||
|
"description": "Featured work combines strong interface quality with technical structure that stays stable after launch.",
|
||||||
|
"stackLabel": "Stack",
|
||||||
|
"cta": "Open project",
|
||||||
|
"fallbackMeta": "Curated work",
|
||||||
|
"stackSets": [
|
||||||
|
[
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"Design Systems"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"APIs",
|
||||||
|
"PostgreSQL",
|
||||||
|
"Automation"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Tailwind CSS",
|
||||||
|
"Prisma",
|
||||||
|
"Infrastructure"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"fallbackItems": [
|
||||||
|
{
|
||||||
|
"title": "Product platform overhaul",
|
||||||
|
"summary": "A production-minded refresh for a service platform with clearer UX, stronger architecture, and faster delivery cycles.",
|
||||||
|
"stack": [
|
||||||
|
"Next.js",
|
||||||
|
"TypeScript",
|
||||||
|
"PostgreSQL"
|
||||||
|
],
|
||||||
|
"href": "/portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Operations dashboard",
|
||||||
|
"summary": "Internal tooling focused on decision speed, cleaner workflows, and less manual coordination across teams.",
|
||||||
|
"stack": [
|
||||||
|
"React",
|
||||||
|
"Node.js",
|
||||||
|
"Automation"
|
||||||
|
],
|
||||||
|
"href": "/portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Launch website system",
|
||||||
|
"summary": "A reusable website foundation for rapid launches without compromising technical quality or editorial control.",
|
||||||
|
"stack": [
|
||||||
|
"Tailwind CSS",
|
||||||
|
"CMS",
|
||||||
|
"Performance"
|
||||||
|
],
|
||||||
|
"href": "/contact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"eyebrow": "Capabilities",
|
||||||
|
"title": "End-to-end technical coverage without losing product focus.",
|
||||||
|
"description": "Each capability area is shaped around production work, not generic service labels.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "Frontend Engineering",
|
||||||
|
"description": "Responsive interfaces, design systems, interaction polish, and component architecture built for scale."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Backend Systems",
|
||||||
|
"description": "Structured APIs, integrations, data modeling, and application logic designed for dependable delivery."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "DevOps & Infrastructure",
|
||||||
|
"description": "Deployments, environments, observability basics, and infrastructure decisions that reduce operational drag."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Automation & AI Workflows",
|
||||||
|
"description": "Practical automation layers that remove manual work and connect product, content, and operational tasks."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"eyebrow": "Process",
|
||||||
|
"title": "A compact workflow that keeps projects moving.",
|
||||||
|
"description": "The process is intentionally simple: reduce ambiguity early, make sound technical decisions, and ship with discipline.",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Discover",
|
||||||
|
"description": "Clarify goals, constraints, audience, and the real outcome the product needs to drive."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Architect",
|
||||||
|
"description": "Define the system shape, technical boundaries, content structure, and delivery path before heavy implementation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Build",
|
||||||
|
"description": "Implement with strong UI detail, clean application logic, and maintainable structure across the stack."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ship",
|
||||||
|
"description": "Launch with confidence, validate the result, and leave the product in a stable state for the next phase."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contactCta": {
|
||||||
|
"eyebrow": "Start the conversation",
|
||||||
|
"title": "If the project needs strong execution, I can help move it forward.",
|
||||||
|
"description": "Bring the brief, the bottleneck, or the rough direction. I can help shape the scope, design the system, and build the product.",
|
||||||
|
"contactCta": "Contact me",
|
||||||
|
"githubCta": "GitHub",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailValue": "hello@moh-sass.dev",
|
||||||
|
"availabilityLabel": "Availability",
|
||||||
|
"availabilityValue": "Open for focused product work",
|
||||||
|
"githubHref": "https://github.com/mohfarawati"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"portfolioPage": {
|
"portfolioPage": {
|
||||||
"title": "Portfolio",
|
"title": "Portfolio",
|
||||||
|
|||||||
Reference in New Issue
Block a user