Refactor site hero and localized branding UI
This commit is contained in:
+71
-107
@@ -1,9 +1,13 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
BadgeCheck,
|
||||||
|
Blocks,
|
||||||
BriefcaseBusiness,
|
BriefcaseBusiness,
|
||||||
Boxes,
|
ChartNoAxesCombined,
|
||||||
Mail,
|
Globe,
|
||||||
|
Package2,
|
||||||
|
Search,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
@@ -17,11 +21,6 @@ import { AppCard } from "@/components/ui/app-card";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { CardContent } from "@/components/ui/card";
|
import { CardContent } from "@/components/ui/card";
|
||||||
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
||||||
import {
|
|
||||||
getLocalizedValue,
|
|
||||||
getPublishedPortfolioProjects,
|
|
||||||
} from "@/lib/portfolio";
|
|
||||||
import { pickText, productItems } from "@/lib/site-data";
|
|
||||||
|
|
||||||
type HomePageProps = {
|
type HomePageProps = {
|
||||||
params: {
|
params: {
|
||||||
@@ -29,6 +28,16 @@ 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({
|
export async function generateMetadata({
|
||||||
@@ -51,9 +60,16 @@ export async function generateMetadata({
|
|||||||
|
|
||||||
export default async function HomePage({ params: { locale } }: HomePageProps) {
|
export default async function HomePage({ params: { locale } }: HomePageProps) {
|
||||||
const localeKey = resolveLocale(locale);
|
const localeKey = resolveLocale(locale);
|
||||||
const featuredProjects = (await getPublishedPortfolioProjects()).slice(0, 3);
|
|
||||||
const featuredProducts = productItems.slice(0, 3);
|
|
||||||
const t = await getTranslations({ locale: localeKey, namespace: "homepage" });
|
const t = await getTranslations({ locale: localeKey, namespace: "homepage" });
|
||||||
|
const serviceItems = [
|
||||||
|
t("serviceBrand"),
|
||||||
|
t("serviceWebsite"),
|
||||||
|
t("serviceStore"),
|
||||||
|
t("serviceWordPress"),
|
||||||
|
t("serviceSeo"),
|
||||||
|
t("serviceTracking"),
|
||||||
|
t("serviceSystem"),
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -66,113 +82,61 @@ export default async function HomePage({ params: { locale } }: HomePageProps) {
|
|||||||
contactLabel={t("toContact")}
|
contactLabel={t("toContact")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Container className="flex flex-col gap-section pb-12 lg:pb-16">
|
<Container className="relative z-10 -mt-6 flex flex-col gap-section pb-12 lg:-mt-8 lg:pb-16">
|
||||||
<MotionFade delay={0.05}>
|
<MotionFade delay={0.05}>
|
||||||
<AppCard>
|
<AppCard
|
||||||
<CardContent className="p-6 lg:p-8">
|
level="inverse"
|
||||||
<div className="mb-6 flex items-center justify-between gap-4">
|
className="overflow-hidden border border-white/6 bg-[linear-gradient(145deg,hsl(var(--foreground)),hsl(221_42%_10%))] text-primary-foreground"
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-semibold text-foreground">
|
|
||||||
{t("portfolioTitle")}
|
|
||||||
</h2>
|
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
|
||||||
{t("portfolioText")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<BriefcaseBusiness className="h-5 w-5 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
|
||||||
{featuredProjects.map((item) => (
|
|
||||||
<AppCard key={item.slug} interactive>
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<Link
|
|
||||||
href={getLocalizedPath(localeKey, `/portfolio/${item.slug}`)}
|
|
||||||
className="group block"
|
|
||||||
>
|
>
|
||||||
<p className="text-sm text-muted-foreground/80">
|
<CardContent className="p-6 lg:p-10">
|
||||||
{getLocalizedValue(item.category.name, localeKey)} - {item.projectYear}
|
<div className="grid gap-8 lg:grid-cols-[minmax(0,1.05fr)_minmax(0,1fr)] lg:items-start">
|
||||||
</p>
|
|
||||||
<h3 className="mt-2 text-lg font-semibold text-foreground">
|
|
||||||
{getLocalizedValue(item.title, localeKey)}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
|
||||||
{getLocalizedValue(item.summary, localeKey)}
|
|
||||||
</p>
|
|
||||||
<span className="mt-4 inline-flex items-center gap-2 text-sm font-medium text-foreground/80 group-hover:text-foreground">
|
|
||||||
{t("toPortfolio")}
|
|
||||||
<ArrowRight className="h-4 w-4" />
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</CardContent>
|
|
||||||
</AppCard>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</AppCard>
|
|
||||||
</MotionFade>
|
|
||||||
|
|
||||||
<MotionFade delay={0.1}>
|
|
||||||
<AppCard>
|
|
||||||
<CardContent className="p-6 lg:p-8">
|
|
||||||
<div className="mb-6 flex items-center justify-between gap-4">
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-semibold text-foreground">
|
<p className="text-sm font-medium tracking-[0.08em] text-primary-foreground/62">
|
||||||
{t("productsTitle")}
|
{t("servicesEyebrow")}
|
||||||
|
</p>
|
||||||
|
<h2 className="mt-4 max-w-[12ch] text-balance text-4xl font-semibold leading-[0.92] tracking-[-0.06em] text-primary-foreground sm:text-5xl lg:text-6xl">
|
||||||
|
{t("servicesTitle")}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
<p className="mt-5 max-w-[34rem] text-sm leading-7 text-primary-foreground/76 sm:text-base">
|
||||||
{t("productsText")}
|
{t("servicesText")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<div className="mt-7 flex flex-wrap gap-3">
|
||||||
<Boxes className="h-5 w-5 text-muted-foreground" />
|
<Button asChild size="lg" variant="secondary" className="min-w-[11rem]">
|
||||||
</div>
|
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
|
||||||
{featuredProducts.map((item) => (
|
|
||||||
<AppCard key={item.slug} interactive>
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<Link
|
|
||||||
href={getLocalizedPath(localeKey, `/products/${item.slug}`)}
|
|
||||||
className="group block"
|
|
||||||
>
|
|
||||||
<p className="text-sm text-muted-foreground/80">
|
|
||||||
{pickText(item.segment, localeKey)}
|
|
||||||
</p>
|
|
||||||
<h3 className="mt-2 text-lg font-semibold text-foreground">
|
|
||||||
{pickText(item.name, localeKey)}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
|
||||||
{pickText(item.summary, localeKey)}
|
|
||||||
</p>
|
|
||||||
<p className="mt-3 text-sm font-medium text-foreground/80">
|
|
||||||
{pickText(item.price, localeKey)}
|
|
||||||
</p>
|
|
||||||
</Link>
|
|
||||||
</CardContent>
|
|
||||||
</AppCard>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</AppCard>
|
|
||||||
</MotionFade>
|
|
||||||
|
|
||||||
<MotionFade delay={0.15}>
|
|
||||||
<AppCard level="inverse">
|
|
||||||
<CardContent className="p-6 lg:p-8">
|
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="inline-flex items-center gap-2 text-sm font-medium text-primary-foreground/80">
|
|
||||||
<Mail className="h-4 w-4" />
|
|
||||||
{t("ctaTitle")}
|
|
||||||
</p>
|
|
||||||
<p className="mt-2 max-w-2xl text-sm text-primary-foreground/88 sm:text-base">
|
|
||||||
{t("ctaText")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button asChild variant="secondary">
|
|
||||||
<Link href={getLocalizedPath(localeKey, "/contact")}>
|
<Link href={getLocalizedPath(localeKey, "/contact")}>
|
||||||
{t("toContact")}
|
{t("servicesPrimaryCta")}
|
||||||
<ArrowRight className="h-4 w-4" />
|
<ArrowRight className="h-4 w-4" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
className="min-w-[11rem] border-white/16 bg-white/6 text-primary-foreground hover:bg-white/10 hover:text-primary-foreground"
|
||||||
|
>
|
||||||
|
<Link href={getLocalizedPath(localeKey, "/portfolio")}>
|
||||||
|
{t("servicesSecondaryCta")}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
|
{serviceItems.map((item, index) => {
|
||||||
|
const Icon = serviceIcons[index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item}
|
||||||
|
className="rounded-nested border border-white/10 bg-white/6 p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] backdrop-blur-sm transition-colors hover:bg-white/8"
|
||||||
|
>
|
||||||
|
<Icon className="h-5 w-5 text-primary-foreground/72" />
|
||||||
|
<p className="mt-4 text-sm font-medium leading-6 text-primary-foreground/88 sm:text-base">
|
||||||
|
{item}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</AppCard>
|
</AppCard>
|
||||||
|
|||||||
@@ -34,10 +34,13 @@ export default async function LocaleLayout({
|
|||||||
setRequestLocale(locale);
|
setRequestLocale(locale);
|
||||||
|
|
||||||
const messages = await getMessages();
|
const messages = await getMessages();
|
||||||
|
const localeClassName = locale === "ar" ? "font-arabic" : "font-latin";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<div lang={locale} dir={getDirection(locale)}>{children}</div>
|
<div lang={locale} dir={getDirection(locale)} className={localeClassName}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+215
@@ -40,6 +40,11 @@
|
|||||||
|
|
||||||
--brand-primary: 9 73% 50%;
|
--brand-primary: 9 73% 50%;
|
||||||
--brand-secondary: 12 64% 58%;
|
--brand-secondary: 12 64% 58%;
|
||||||
|
--hero-base: 260 22% 95%;
|
||||||
|
--hero-ink: 226 43% 11%;
|
||||||
|
--hero-left: 226 100% 85%;
|
||||||
|
--hero-right: 334 33% 91%;
|
||||||
|
--hero-bottom: 205 61% 88%;
|
||||||
--ambient-primary-alpha: 0.06;
|
--ambient-primary-alpha: 0.06;
|
||||||
--ambient-secondary-alpha: 0.04;
|
--ambient-secondary-alpha: 0.04;
|
||||||
--ambient-grid-alpha: 0;
|
--ambient-grid-alpha: 0;
|
||||||
@@ -114,6 +119,11 @@
|
|||||||
|
|
||||||
--brand-primary: 9 78% 56%;
|
--brand-primary: 9 78% 56%;
|
||||||
--brand-secondary: 12 74% 64%;
|
--brand-secondary: 12 74% 64%;
|
||||||
|
--hero-base: 222 24% 10%;
|
||||||
|
--hero-ink: 210 20% 96%;
|
||||||
|
--hero-left: 223 45% 24%;
|
||||||
|
--hero-right: 293 18% 22%;
|
||||||
|
--hero-bottom: 212 35% 20%;
|
||||||
--ambient-primary-alpha: 0.08;
|
--ambient-primary-alpha: 0.08;
|
||||||
--ambient-secondary-alpha: 0.05;
|
--ambient-secondary-alpha: 0.05;
|
||||||
--ambient-grid-alpha: 0;
|
--ambient-grid-alpha: 0;
|
||||||
@@ -139,12 +149,25 @@
|
|||||||
html {
|
html {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
background-color: hsl(var(--background));
|
background-color: hsl(var(--background));
|
||||||
|
font-family: var(--font-museo), sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark {
|
html.dark {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html[lang="ar"] {
|
||||||
|
font-family: var(--font-dubai), sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-latin {
|
||||||
|
font-family: var(--font-museo), sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-arabic {
|
||||||
|
font-family: var(--font-dubai), sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
@@ -154,11 +177,19 @@ html.dark {
|
|||||||
@apply min-h-screen text-foreground antialiased;
|
@apply min-h-screen text-foreground antialiased;
|
||||||
background-color: hsl(var(--background));
|
background-color: hsl(var(--background));
|
||||||
background-image: none;
|
background-image: none;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@apply transition-colors;
|
@apply transition-colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
@@ -214,4 +245,188 @@ html.dark {
|
|||||||
transparent 32%
|
transparent 32%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hero-surface {
|
||||||
|
background: linear-gradient(180deg, hsl(var(--hero-base)), hsl(var(--background)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-home {
|
||||||
|
color: hsl(var(--hero-ink));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-page {
|
||||||
|
background: linear-gradient(180deg, hsl(var(--hero-base) / 0.96), hsl(var(--background)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sheet {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sheet-base {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 14% 18%, hsl(var(--hero-left) / 0.42), transparent 26%),
|
||||||
|
radial-gradient(circle at 84% 14%, hsl(var(--hero-right) / 0.28), transparent 24%),
|
||||||
|
radial-gradient(circle at 52% 84%, hsl(var(--hero-bottom) / 0.24), transparent 32%),
|
||||||
|
linear-gradient(180deg, hsl(var(--hero-base)), hsl(var(--background)) 88%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 9999px;
|
||||||
|
filter: blur(110px);
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob-left {
|
||||||
|
left: -8%;
|
||||||
|
top: 8%;
|
||||||
|
height: 24rem;
|
||||||
|
width: 24rem;
|
||||||
|
background: radial-gradient(circle, hsl(var(--hero-left) / 0.48), transparent 68%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob-right {
|
||||||
|
right: -10%;
|
||||||
|
top: 2%;
|
||||||
|
height: 22rem;
|
||||||
|
width: 22rem;
|
||||||
|
background: radial-gradient(circle, hsl(var(--hero-right) / 0.36), transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob-bottom {
|
||||||
|
left: 18%;
|
||||||
|
bottom: -10%;
|
||||||
|
height: 24rem;
|
||||||
|
width: 38rem;
|
||||||
|
background: radial-gradient(circle, hsl(var(--hero-bottom) / 0.32), transparent 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sheet-glow {
|
||||||
|
position: absolute;
|
||||||
|
inset: -10% -8%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 24%, hsl(var(--hero-left) / 0.24), transparent 18%),
|
||||||
|
radial-gradient(circle at 86% 18%, hsl(var(--hero-right) / 0.16), transparent 18%),
|
||||||
|
radial-gradient(circle at 50% 78%, hsl(var(--hero-bottom) / 0.14), transparent 24%);
|
||||||
|
filter: blur(36px);
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sheet-wave {
|
||||||
|
position: absolute;
|
||||||
|
inset: -16% -10%;
|
||||||
|
background:
|
||||||
|
conic-gradient(
|
||||||
|
from 180deg at 50% 50%,
|
||||||
|
transparent 0deg,
|
||||||
|
hsl(var(--hero-left) / 0.08) 58deg,
|
||||||
|
transparent 120deg,
|
||||||
|
transparent 228deg,
|
||||||
|
hsl(var(--hero-right) / 0.1) 300deg,
|
||||||
|
transparent 360deg
|
||||||
|
);
|
||||||
|
filter: blur(90px);
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sheet-noise {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)),
|
||||||
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23noise)' opacity='0.1'/%3E%3C/svg%3E");
|
||||||
|
background-size: auto, 160px 160px;
|
||||||
|
mix-blend-mode: soft-light;
|
||||||
|
opacity: 0.22;
|
||||||
|
will-change: background-position, opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sheet-veil {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.025) 44%, rgba(255, 255, 255, 0));
|
||||||
|
backdrop-filter: blur(20px) saturate(112%);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(112%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sheet-fade {
|
||||||
|
position: absolute;
|
||||||
|
inset: auto 0 0 0;
|
||||||
|
height: 7rem;
|
||||||
|
background: linear-gradient(180deg, transparent, hsl(var(--background)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-header-bridge {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 3.5rem;
|
||||||
|
height: 11rem;
|
||||||
|
width: min(34rem, 72vw);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 0%, hsl(var(--hero-left) / 0.18), transparent 56%),
|
||||||
|
linear-gradient(180deg, hsl(var(--foreground) / 0.06), transparent 72%);
|
||||||
|
filter: blur(24px);
|
||||||
|
opacity: 0.9;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-content-bridge {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 2.5rem;
|
||||||
|
height: 9rem;
|
||||||
|
width: min(44rem, 82vw);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 50%, hsl(var(--hero-bottom) / 0.2), transparent 58%),
|
||||||
|
linear-gradient(180deg, transparent, hsl(var(--background) / 0.86));
|
||||||
|
filter: blur(20px);
|
||||||
|
opacity: 0.92;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.hero-blob-left {
|
||||||
|
height: 30rem;
|
||||||
|
width: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob-right {
|
||||||
|
height: 28rem;
|
||||||
|
width: 28rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob-bottom {
|
||||||
|
height: 28rem;
|
||||||
|
width: 44rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.hero-blob-left {
|
||||||
|
left: -4%;
|
||||||
|
top: 10%;
|
||||||
|
height: 36rem;
|
||||||
|
width: 36rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob-right {
|
||||||
|
right: -4%;
|
||||||
|
top: 0;
|
||||||
|
height: 34rem;
|
||||||
|
width: 34rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-blob-bottom {
|
||||||
|
left: 14%;
|
||||||
|
bottom: -16%;
|
||||||
|
height: 32rem;
|
||||||
|
width: 52rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-1
@@ -1,11 +1,76 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { unstable_noStore as noStore } from "next/cache";
|
import { unstable_noStore as noStore } from "next/cache";
|
||||||
|
import localFont from "next/font/local";
|
||||||
import { getLocale } from "next-intl/server";
|
import { getLocale } from "next-intl/server";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
import { buildAppMetadata } from "@/lib/metadata";
|
import { buildAppMetadata } from "@/lib/metadata";
|
||||||
import { getDirection } from "@/lib/locale";
|
import { getDirection } from "@/lib/locale";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
|
const museo = localFont({
|
||||||
|
src: [
|
||||||
|
{
|
||||||
|
path: "./fonts/MuseoSansRounded100.woff2",
|
||||||
|
weight: "100",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/MuseoSansRounded300.woff2",
|
||||||
|
weight: "300",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/MuseoSansRounded500.woff2",
|
||||||
|
weight: "500",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/MuseoSansRounded700.woff2",
|
||||||
|
weight: "700",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/MuseoSansRounded900.woff2",
|
||||||
|
weight: "900",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/MuseoSansRounded1000.woff2",
|
||||||
|
weight: "1000",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variable: "--font-museo",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
|
const dubai = localFont({
|
||||||
|
src: [
|
||||||
|
{
|
||||||
|
path: "./fonts/Dubai-Light.woff2",
|
||||||
|
weight: "300",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/Dubai-Regular.woff2",
|
||||||
|
weight: "400",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/Dubai-Medium.woff2",
|
||||||
|
weight: "500",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./fonts/Dubai-Bold.woff2",
|
||||||
|
weight: "700",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variable: "--font-dubai",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
export const revalidate = 0;
|
export const revalidate = 0;
|
||||||
|
|
||||||
@@ -26,7 +91,7 @@ export default async function RootLayout({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale} dir={getDirection(locale)} suppressHydrationWarning>
|
<html lang={locale} dir={getDirection(locale)} suppressHydrationWarning>
|
||||||
<body>
|
<body className={`${museo.variable} ${dubai.variable}`}>
|
||||||
<ThemeProvider>{children}</ThemeProvider>
|
<ThemeProvider>{children}</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+1
-1
@@ -21,5 +21,5 @@ export const metadata: Metadata = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({ children }: RootLayoutProps) {
|
export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
return children;
|
return <div className="font-latin">{children}</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
type HeroMotionBackdropProps = {
|
||||||
|
compact?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function HeroMotionBackdrop({ compact = false }: HeroMotionBackdropProps) {
|
||||||
|
return (
|
||||||
|
<div className="hero-sheet pointer-events-none absolute inset-0 overflow-hidden">
|
||||||
|
<div className="hero-sheet-base" />
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
x: compact ? [0, 18, -10, 0] : [0, 44, -24, 0],
|
||||||
|
y: compact ? [0, -16, 10, 0] : [0, -34, 18, 0],
|
||||||
|
scale: compact ? [1, 1.03, 0.99, 1] : [1, 1.08, 0.96, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: compact ? 18 : 26,
|
||||||
|
repeat: Number.POSITIVE_INFINITY,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
className="hero-blob hero-blob-left"
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
x: compact ? [0, -14, 8, 0] : [0, -36, 18, 0],
|
||||||
|
y: compact ? [0, 10, -8, 0] : [0, 22, -14, 0],
|
||||||
|
scale: compact ? [1, 0.98, 1.04, 1] : [1, 0.94, 1.08, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: compact ? 20 : 30,
|
||||||
|
repeat: Number.POSITIVE_INFINITY,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
className="hero-blob hero-blob-right"
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
x: compact ? [0, 12, -8, 0] : [0, 26, -14, 0],
|
||||||
|
y: compact ? [0, -6, 8, 0] : [0, -18, 16, 0],
|
||||||
|
scale: compact ? [1, 1.02, 0.98, 1] : [1, 1.06, 0.97, 1],
|
||||||
|
opacity: compact ? [0.36, 0.5, 0.4, 0.36] : [0.34, 0.54, 0.4, 0.34],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: compact ? 16 : 24,
|
||||||
|
repeat: Number.POSITIVE_INFINITY,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
className="hero-blob hero-blob-bottom"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
opacity: compact ? [0.18, 0.26, 0.2, 0.18] : [0.2, 0.3, 0.22, 0.2],
|
||||||
|
scale: compact ? [1, 1.01, 1, 1] : [1, 1.02, 1, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: compact ? 16 : 22,
|
||||||
|
repeat: Number.POSITIVE_INFINITY,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
className="hero-sheet-glow"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
x: compact ? [0, 10, -8, 0] : [0, 18, -14, 0],
|
||||||
|
y: compact ? [0, -8, 6, 0] : [0, -18, 10, 0],
|
||||||
|
rotate: compact ? [0, 2, -1, 0] : [0, 4, -2, 0],
|
||||||
|
opacity: compact ? [0.12, 0.2, 0.14, 0.12] : [0.14, 0.24, 0.16, 0.14],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: compact ? 20 : 28,
|
||||||
|
repeat: Number.POSITIVE_INFINITY,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
className="hero-sheet-wave"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
backgroundPosition: compact
|
||||||
|
? ["0 0, 0 0", "40px 24px, 0 0", "0 0, 0 0"]
|
||||||
|
: ["0 0, 0 0", "64px 40px, 0 0", "0 0, 0 0"],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: compact ? 20 : 28,
|
||||||
|
repeat: Number.POSITIVE_INFINITY,
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
className="hero-sheet-noise"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="hero-sheet-veil" />
|
||||||
|
<div className="hero-sheet-fade" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import { HeroAtmosphere } from "@/components/layout/hero-atmosphere";
|
import { HeroMotionBackdrop } from "@/components/layout/hero-motion-backdrop";
|
||||||
import { MotionFade } from "@/components/motion-fade";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { getLocalizedPath } from "@/lib/locale";
|
import { getLocalizedPath } from "@/lib/locale";
|
||||||
|
|
||||||
@@ -23,34 +22,60 @@ export function HomeHero({
|
|||||||
portfolioLabel,
|
portfolioLabel,
|
||||||
contactLabel,
|
contactLabel,
|
||||||
}: HomeHeroProps) {
|
}: HomeHeroProps) {
|
||||||
|
const titleLines = title.split("\n").filter(Boolean);
|
||||||
|
const isArabic = locale === "ar";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative isolate flex min-h-[85vh] items-center overflow-hidden">
|
<section className="hero-surface hero-home relative isolate flex min-h-[88vh] items-center justify-center overflow-hidden">
|
||||||
<HeroAtmosphere />
|
<HeroMotionBackdrop />
|
||||||
|
<div className="hero-header-bridge" />
|
||||||
|
<div className="hero-content-bridge" />
|
||||||
<div className="relative z-10 mx-auto flex w-full max-w-[72rem] flex-col items-center justify-center px-4 pb-12 pt-24 text-center sm:px-6 lg:px-8 lg:pb-16 lg:pt-28">
|
<div className="relative z-10 mx-auto flex w-full max-w-[72rem] flex-col items-center justify-center px-4 pb-12 pt-24 text-center sm:px-6 lg:px-8 lg:pb-16 lg:pt-28">
|
||||||
<MotionFade className="w-full">
|
<div className="mx-auto flex max-w-[46rem] flex-col items-center">
|
||||||
<div className="mx-auto max-w-[54rem]">
|
<p className="text-base font-medium tracking-[-0.03em] text-[hsl(var(--hero-ink)/0.76)] drop-shadow-[0_10px_24px_rgba(15,23,42,0.16)] sm:text-lg">
|
||||||
<p className="text-sm font-medium tracking-[0.08em] text-foreground/62">
|
|
||||||
{kicker}
|
{kicker}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="mt-6 whitespace-pre-line text-balance text-[3rem] font-semibold leading-[0.92] tracking-[-0.06em] text-foreground sm:text-[4.5rem] lg:text-[6.75rem]">
|
<h1
|
||||||
{title}
|
className={
|
||||||
|
isArabic
|
||||||
|
? "mt-5 flex w-full max-w-[40rem] flex-col text-[3rem] font-bold leading-[1.12] tracking-normal text-[hsl(var(--hero-ink))] drop-shadow-[0_18px_40px_rgba(15,23,42,0.16)] sm:text-[4.25rem] lg:max-w-[48rem] lg:text-[5.35rem]"
|
||||||
|
: "mt-4 flex w-full max-w-[36rem] flex-col gap-y-1 text-[3.25rem] font-semibold leading-[0.9] tracking-[-0.085em] text-[hsl(var(--hero-ink))] drop-shadow-[0_18px_40px_rgba(15,23,42,0.22)] sm:max-w-[38rem] sm:text-[4.9rem] lg:max-w-[39rem] lg:gap-y-2 lg:text-[6.15rem]"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{titleLines.map((line, index) => (
|
||||||
|
<span
|
||||||
|
key={`${locale}-${index}-${line}`}
|
||||||
|
className={
|
||||||
|
isArabic
|
||||||
|
? index === 1
|
||||||
|
? "self-center whitespace-nowrap"
|
||||||
|
: "self-center whitespace-nowrap bg-[linear-gradient(135deg,hsl(var(--hero-ink)),hsl(var(--hero-right)/0.78))] bg-clip-text text-transparent"
|
||||||
|
: index === 1
|
||||||
|
? "self-end"
|
||||||
|
: index === titleLines.length - 1
|
||||||
|
? "self-start bg-[linear-gradient(135deg,hsl(var(--hero-ink)),hsl(var(--hero-right)/0.9))] bg-clip-text text-transparent"
|
||||||
|
: "self-start bg-[linear-gradient(135deg,hsl(var(--hero-ink)),hsl(var(--hero-left)/0.9))] bg-clip-text text-transparent"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{line}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mx-auto mt-6 max-w-[30rem] text-sm leading-6 text-foreground/66 sm:text-base">
|
<p className="mx-auto mt-8 max-w-[30rem] whitespace-pre-line text-sm leading-7 text-[hsl(var(--hero-ink)/0.72)] drop-shadow-[0_8px_18px_rgba(15,23,42,0.12)] sm:text-base">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
|
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
|
||||||
<Button asChild size="lg" className="px-6">
|
<Button asChild size="lg" className="min-w-[10.5rem] px-6">
|
||||||
<Link href={getLocalizedPath(locale, "/portfolio")}>
|
<Link href={getLocalizedPath(locale, "/portfolio")}>
|
||||||
{portfolioLabel}
|
{portfolioLabel}
|
||||||
<ArrowRight className="h-4 w-4" />
|
<ArrowRight className="h-4 w-4" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="px-6">
|
<Button asChild variant="outline" size="lg" className="min-w-[10.5rem] border-white/40 bg-white/35 px-6 backdrop-blur-sm hover:bg-white/55 dark:border-white/14 dark:bg-white/6 dark:hover:bg-white/10">
|
||||||
<Link href={getLocalizedPath(locale, "/contact")}>{contactLabel}</Link>
|
<Link href={getLocalizedPath(locale, "/contact")}>{contactLabel}</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MotionFade>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Languages } from "lucide-react";
|
import Image from "next/image";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -24,9 +24,16 @@ export function LocaleToggle({ locale, className, showLabel = false }: LocaleTog
|
|||||||
const locales: AppLocale[] = ["de", "en", "ar"];
|
const locales: AppLocale[] = ["de", "en", "ar"];
|
||||||
const currentPath = stripLocalePrefix(pathname);
|
const currentPath = stripLocalePrefix(pathname);
|
||||||
const currentLocale = (["de", "en", "ar"].includes(locale) ? locale : "de") as AppLocale;
|
const currentLocale = (["de", "en", "ar"].includes(locale) ? locale : "de") as AppLocale;
|
||||||
|
const availableLocales = locales.filter((targetLocale) => targetLocale !== currentLocale);
|
||||||
|
const contentFontClassName = currentLocale === "ar" ? "font-arabic" : "font-latin";
|
||||||
|
const localeFlags: Record<AppLocale, { src: string; alt: string }> = {
|
||||||
|
de: { src: "/flags/de.svg", alt: "German" },
|
||||||
|
en: { src: "/flags/us.svg", alt: "English" },
|
||||||
|
ar: { src: "/flags/sy.svg", alt: "Arabic" },
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu modal={false}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -34,26 +41,41 @@ export function LocaleToggle({ locale, className, showLabel = false }: LocaleTog
|
|||||||
size={showLabel ? "sm" : "icon"}
|
size={showLabel ? "sm" : "icon"}
|
||||||
aria-label={`Locale: ${currentLocale.toUpperCase()}`}
|
aria-label={`Locale: ${currentLocale.toUpperCase()}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-pill border border-transparent text-muted-foreground hover:border-border/70 hover:bg-background/80 hover:text-foreground",
|
"group cursor-pointer rounded-pill border border-transparent text-muted-foreground hover:border-border/70 hover:bg-background/80 hover:text-foreground",
|
||||||
showLabel ? "gap-2 px-3" : undefined,
|
showLabel ? "gap-2 px-3" : undefined,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Languages className="h-4 w-4" />
|
<Image
|
||||||
{showLabel ? <span className="text-xs font-semibold uppercase tracking-[0.22em]">{currentLocale}</span> : null}
|
src={localeFlags[currentLocale].src}
|
||||||
|
alt={localeFlags[currentLocale].alt}
|
||||||
|
width={18}
|
||||||
|
height={18}
|
||||||
|
className="h-[18px] w-[18px] rounded-full object-cover transition-transform duration-300 ease-out group-hover:scale-110"
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="min-w-[8rem]">
|
<DropdownMenuContent align="end" className={cn("w-auto min-w-0", contentFontClassName)}>
|
||||||
{locales.map((targetLocale) => (
|
{availableLocales.map((targetLocale) => (
|
||||||
<DropdownMenuItem key={targetLocale} asChild>
|
<DropdownMenuItem
|
||||||
|
key={targetLocale}
|
||||||
|
asChild
|
||||||
|
className={cn(
|
||||||
|
"cursor-pointer",
|
||||||
|
targetLocale === "ar" ? "font-arabic" : "font-latin",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href={getLocalizedPath(targetLocale, currentPath)}
|
href={getLocalizedPath(targetLocale, currentPath)}
|
||||||
className="flex items-center justify-between gap-4"
|
className="group flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<span>{targetLocale.toUpperCase()}</span>
|
<Image
|
||||||
{targetLocale === currentLocale ? (
|
src={localeFlags[targetLocale].src}
|
||||||
<span className="text-xs text-muted-foreground">Active</span>
|
alt={localeFlags[targetLocale].alt}
|
||||||
) : null}
|
width={18}
|
||||||
|
height={18}
|
||||||
|
className="h-[18px] w-[18px] rounded-full object-cover transition-transform duration-300 ease-out group-hover:scale-110"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { HeroAtmosphere } from "@/components/layout/hero-atmosphere";
|
import { HeroMotionBackdrop } from "@/components/layout/hero-motion-backdrop";
|
||||||
import { MotionFade } from "@/components/motion-fade";
|
|
||||||
|
|
||||||
type PageHeroProps = {
|
type PageHeroProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -8,22 +7,22 @@ type PageHeroProps = {
|
|||||||
|
|
||||||
export function PageHero({ title, description }: PageHeroProps) {
|
export function PageHero({ title, description }: PageHeroProps) {
|
||||||
return (
|
return (
|
||||||
<section className="relative isolate flex min-h-[42vh] items-end overflow-hidden">
|
<section className="hero-surface hero-page relative isolate flex min-h-[42vh] items-end overflow-hidden">
|
||||||
<HeroAtmosphere compact />
|
<HeroMotionBackdrop compact />
|
||||||
<div className="relative z-10 mx-auto w-full max-w-[72rem] px-4 pb-10 pt-24 sm:px-6 lg:px-8 lg:pb-12 lg:pt-28">
|
<div className="relative z-10 mx-auto w-full max-w-[72rem] px-4 pb-10 pt-24 sm:px-6 lg:px-8 lg:pb-12 lg:pt-28">
|
||||||
<MotionFade className="max-w-[44rem]">
|
<div className="mx-auto max-w-[42rem] text-center">
|
||||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-foreground/48">
|
<p className="text-sm font-medium tracking-[-0.02em] text-foreground/58">
|
||||||
{title}
|
{title}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="mt-4 text-balance text-4xl font-semibold leading-[0.95] tracking-[-0.05em] text-foreground sm:text-5xl lg:text-6xl">
|
<h1 className="mt-4 text-balance text-4xl font-semibold leading-[0.9] tracking-[-0.07em] text-foreground sm:text-5xl lg:text-[4.5rem]">
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="mt-5 max-w-[36rem] text-sm leading-6 text-foreground/66 sm:text-base">
|
<p className="mx-auto mt-5 max-w-[32rem] text-sm leading-6 text-foreground/64 sm:text-base">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</MotionFade>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ function NavLinks({
|
|||||||
key={item.key}
|
key={item.key}
|
||||||
href={getLocalizedPath(locale, itemPath)}
|
href={getLocalizedPath(locale, itemPath)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-10 rounded-[var(--radius-pill)] text-sm font-medium transition-colors",
|
"relative z-10 rounded-[var(--radius-pill)] transition-colors",
|
||||||
mobile ? "px-4 py-3" : "px-3.5 py-2 xl:px-4",
|
mobile ? "px-4 py-3 text-sm font-medium" : "px-3.5 py-2 text-sm font-medium xl:px-4",
|
||||||
isActive ? "text-primary-foreground" : "text-muted-foreground hover:text-foreground",
|
isActive
|
||||||
|
? "text-primary-foreground"
|
||||||
|
: "text-[hsl(var(--hero-ink)/0.62)] hover:text-[hsl(var(--hero-ink))] dark:text-muted-foreground dark:hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
onClick={onNavigate}
|
onClick={onNavigate}
|
||||||
>
|
>
|
||||||
@@ -92,21 +94,23 @@ export function SiteHeader({
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const t = useTranslations("navigation");
|
const t = useTranslations("navigation");
|
||||||
|
const isArabic = locale === "ar";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="fixed inset-x-0 top-3 z-40 bg-transparent">
|
<header className="fixed inset-x-0 top-3 z-40 bg-transparent">
|
||||||
<Container className="max-w-[88rem]">
|
<Container className="max-w-[88rem]">
|
||||||
<div className="relative grid min-h-[4.5rem] grid-cols-[auto_1fr_auto] items-center gap-3 lg:min-h-[5rem]">
|
<div className="relative grid min-h-[4.5rem] grid-cols-[auto_1fr_auto] items-center gap-3 lg:min-h-[5rem]">
|
||||||
<div className="flex items-center">
|
<div className="col-start-1 flex items-center justify-self-start">
|
||||||
<Link
|
<Link
|
||||||
href={getLocalizedPath(locale)}
|
href={getLocalizedPath(locale)}
|
||||||
className="inline-flex items-center py-1 sm:py-1.5 lg:py-2"
|
className="inline-flex items-center rounded-[var(--radius-pill)] px-1 py-1 sm:py-1.5 lg:py-2"
|
||||||
>
|
>
|
||||||
<SiteLogo
|
<SiteLogo
|
||||||
lightLogoUrl={lightLogoUrl}
|
lightLogoUrl={lightLogoUrl}
|
||||||
darkLogoUrl={darkLogoUrl}
|
darkLogoUrl={darkLogoUrl}
|
||||||
alt="mohfarawati"
|
alt="mohfarawati"
|
||||||
priority
|
priority
|
||||||
|
className="h-7 opacity-88 sm:h-8 lg:h-9"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +120,10 @@ export function SiteHeader({
|
|||||||
initial={{ opacity: 0, y: -8 }}
|
initial={{ opacity: 0, y: -8 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.28, ease: "easeOut" }}
|
transition={{ duration: 0.28, ease: "easeOut" }}
|
||||||
className="pointer-events-auto inline-flex min-w-[27rem] max-w-[42rem] items-center justify-center gap-1 rounded-[var(--radius-pill)] border border-white/18 bg-white/10 p-1.5 shadow-[0_18px_44px_-28px_rgba(15,23,42,0.24)] backdrop-blur-[24px] dark:border-white/10 dark:bg-white/5 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]"
|
className={cn(
|
||||||
|
"pointer-events-auto inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1.5 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]",
|
||||||
|
isArabic ? "w-auto max-w-none" : "min-w-[27rem] max-w-[42rem]",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<NavLinks />
|
<NavLinks />
|
||||||
</motion.nav>
|
</motion.nav>
|
||||||
@@ -127,15 +134,15 @@ export function SiteHeader({
|
|||||||
initial={{ opacity: 0, y: -8 }}
|
initial={{ opacity: 0, y: -8 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.28, ease: "easeOut", delay: 0.03 }}
|
transition={{ duration: 0.28, ease: "easeOut", delay: 0.03 }}
|
||||||
className="inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-white/18 bg-white/10 p-1 shadow-[0_18px_44px_-28px_rgba(15,23,42,0.24)] backdrop-blur-[24px] dark:border-white/10 dark:bg-white/5 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]"
|
className="inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]"
|
||||||
>
|
>
|
||||||
<LocaleToggle
|
<LocaleToggle
|
||||||
locale={locale}
|
locale={locale}
|
||||||
showLabel
|
showLabel
|
||||||
className="h-9 rounded-[var(--radius-pill)] px-3 text-foreground hover:bg-white/34 dark:hover:bg-white/10"
|
className="h-9 rounded-[var(--radius-pill)] px-3 text-[hsl(var(--hero-ink)/0.76)] hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/10"
|
||||||
/>
|
/>
|
||||||
{isAdmin ? (
|
{isAdmin ? (
|
||||||
<Button asChild variant="ghost" size="icon" className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-foreground hover:border-white/12 hover:bg-white/34 dark:hover:border-white/10 dark:hover:bg-white/10">
|
<Button asChild variant="ghost" size="icon" className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-[hsl(var(--hero-ink)/0.76)] hover:border-black/5 hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:border-white/10 dark:hover:bg-white/10">
|
||||||
<Link href="/root" aria-label={t("root")}>
|
<Link href="/root" aria-label={t("root")}>
|
||||||
<LayoutDashboard className="h-4 w-4" />
|
<LayoutDashboard className="h-4 w-4" />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -144,7 +151,7 @@ export function SiteHeader({
|
|||||||
<ThemeToggle
|
<ThemeToggle
|
||||||
ariaLabel={t("themeToggle")}
|
ariaLabel={t("themeToggle")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-foreground hover:border-white/12 hover:bg-white/34 dark:hover:border-white/10 dark:hover:bg-white/10"
|
className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent text-[hsl(var(--hero-ink)/0.76)] hover:border-black/5 hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:border-white/10 dark:hover:bg-white/10"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +161,7 @@ export function SiteHeader({
|
|||||||
onClick={() => setIsOpen((open) => !open)}
|
onClick={() => setIsOpen((open) => !open)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="ml-auto h-11 w-11 rounded-[var(--radius-pill)] border border-white/18 bg-white/12 text-foreground shadow-[0_18px_44px_-28px_rgba(15,23,42,0.24)] backdrop-blur-[24px] hover:bg-white/18 dark:border-white/10 dark:bg-white/5 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)] dark:hover:bg-white/10 lg:hidden"
|
className="col-start-3 ml-auto h-11 w-11 justify-self-end rounded-[var(--radius-pill)] border border-black/6 bg-white/24 text-[hsl(var(--hero-ink))] shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] hover:bg-white/34 dark:border-white/10 dark:bg-white/4 dark:text-foreground dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)] dark:hover:bg-white/10 lg:hidden"
|
||||||
aria-label={isOpen ? t("closeMenu") : t("openMenu")}
|
aria-label={isOpen ? t("closeMenu") : t("openMenu")}
|
||||||
>
|
>
|
||||||
<motion.span
|
<motion.span
|
||||||
@@ -183,7 +190,7 @@ export function SiteHeader({
|
|||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
exit={{ opacity: 0, scale: 0.98 }}
|
exit={{ opacity: 0, scale: 0.98 }}
|
||||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||||
className="overflow-hidden rounded-surface border border-white/24 bg-[linear-gradient(180deg,rgba(255,255,255,0.24),rgba(255,255,255,0.1))] shadow-[inset_0_1px_0_rgba(255,255,255,0.46),inset_0_-1px_0_rgba(255,255,255,0.08),0_18px_44px_-28px_rgba(15,23,42,0.32)] backdrop-blur-[28px] dark:border-white/12 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.09),rgba(255,255,255,0.03))] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.12),inset_0_-1px_0_rgba(255,255,255,0.03),0_20px_48px_-28px_rgba(0,0,0,0.72)]"
|
className="overflow-hidden rounded-surface border border-black/6 bg-[linear-gradient(180deg,rgba(255,255,255,0.46),rgba(255,255,255,0.24))] shadow-[inset_0_1px_0_rgba(255,255,255,0.54),0_16px_34px_-24px_rgba(15,23,42,0.24)] backdrop-blur-[20px] dark:border-white/12 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.09),rgba(255,255,255,0.03))] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.12),inset_0_-1px_0_rgba(255,255,255,0.03),0_20px_48px_-28px_rgba(0,0,0,0.72)]"
|
||||||
>
|
>
|
||||||
<motion.nav
|
<motion.nav
|
||||||
initial="closed"
|
initial="closed"
|
||||||
@@ -223,8 +230,8 @@ export function SiteHeader({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-between rounded-nested px-4 py-3 text-sm font-medium transition-colors",
|
"flex items-center justify-between rounded-nested px-4 py-3 text-sm font-medium transition-colors",
|
||||||
isActive
|
isActive
|
||||||
? "bg-primary text-primary-foreground"
|
? "bg-white/72 text-[hsl(var(--hero-ink))] shadow-[0_10px_20px_-18px_rgba(15,23,42,0.24)] dark:bg-white/10 dark:text-foreground"
|
||||||
: "text-foreground hover:bg-accent hover:text-accent-foreground",
|
: "text-[hsl(var(--hero-ink)/0.78)] hover:bg-white/40 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/6",
|
||||||
)}
|
)}
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
>
|
>
|
||||||
@@ -243,17 +250,17 @@ export function SiteHeader({
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: 8 }}
|
exit={{ opacity: 0, y: 8 }}
|
||||||
transition={{ duration: 0.18, ease: "easeOut", delay: 0.04 }}
|
transition={{ duration: 0.18, ease: "easeOut", delay: 0.04 }}
|
||||||
className="border-t border-white/18 bg-[linear-gradient(180deg,rgba(255,255,255,0.14),rgba(255,255,255,0.06))] p-2 dark:border-white/8 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))]"
|
className="border-t border-black/6 bg-[linear-gradient(180deg,rgba(255,255,255,0.2),rgba(255,255,255,0.08))] p-2 dark:border-white/8 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))]"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<LocaleToggle
|
<LocaleToggle
|
||||||
locale={locale}
|
locale={locale}
|
||||||
showLabel
|
showLabel
|
||||||
className="h-10 rounded-nested px-3 text-foreground hover:bg-white/34 dark:hover:bg-white/10"
|
className="h-10 rounded-nested px-3 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/10"
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isAdmin ? (
|
{isAdmin ? (
|
||||||
<Button asChild variant="ghost" size="icon" className="h-10 w-10 rounded-nested border border-white/18 bg-white/18 text-foreground hover:bg-white/34 dark:border-white/10 dark:bg-white/6 dark:hover:bg-white/10">
|
<Button asChild variant="ghost" size="icon" className="h-10 w-10 rounded-nested border border-black/6 bg-white/32 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/42 hover:text-[hsl(var(--hero-ink))] dark:border-white/10 dark:bg-white/6 dark:text-foreground dark:hover:bg-white/10">
|
||||||
<Link href="/root" aria-label={t("root")} onClick={() => setIsOpen(false)}>
|
<Link href="/root" aria-label={t("root")} onClick={() => setIsOpen(false)}>
|
||||||
<LayoutDashboard className="h-4 w-4" />
|
<LayoutDashboard className="h-4 w-4" />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -262,7 +269,7 @@ export function SiteHeader({
|
|||||||
<ThemeToggle
|
<ThemeToggle
|
||||||
ariaLabel={t("themeToggle")}
|
ariaLabel={t("themeToggle")}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-10 w-10 rounded-nested border border-white/18 bg-white/18 text-foreground hover:bg-white/34 dark:border-white/10 dark:bg-white/6 dark:hover:bg-white/10"
|
className="h-10 w-10 rounded-nested border border-black/6 bg-white/32 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/42 hover:text-[hsl(var(--hero-ink))] dark:border-white/10 dark:bg-white/6 dark:text-foreground dark:hover:bg-white/10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type SiteLogoProps = {
|
type SiteLogoProps = {
|
||||||
lightLogoUrl?: string | null;
|
lightLogoUrl?: string | null;
|
||||||
darkLogoUrl?: string | null;
|
darkLogoUrl?: string | null;
|
||||||
alt: string;
|
alt: string;
|
||||||
priority?: boolean;
|
priority?: boolean;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SiteLogo({
|
export function SiteLogo({
|
||||||
@@ -16,6 +20,7 @@ export function SiteLogo({
|
|||||||
darkLogoUrl,
|
darkLogoUrl,
|
||||||
alt,
|
alt,
|
||||||
priority = false,
|
priority = false,
|
||||||
|
className,
|
||||||
}: SiteLogoProps) {
|
}: SiteLogoProps) {
|
||||||
const { theme, resolvedTheme } = useTheme();
|
const { theme, resolvedTheme } = useTheme();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -30,16 +35,23 @@ export function SiteLogo({
|
|||||||
const src = isDark ? darkLogoUrl || lightLogoUrl || fallbackSrc : lightLogoUrl || darkLogoUrl || fallbackSrc;
|
const src = isDark ? darkLogoUrl || lightLogoUrl || fallbackSrc : lightLogoUrl || darkLogoUrl || fallbackSrc;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="block">
|
<motion.span
|
||||||
|
className="block"
|
||||||
|
whileHover={{ scale: 1.04, y: -1 }}
|
||||||
|
transition={{ duration: 0.28, ease: "easeOut" }}
|
||||||
|
>
|
||||||
<Image
|
<Image
|
||||||
src={src}
|
src={src}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
width={360}
|
width={360}
|
||||||
height={92}
|
height={92}
|
||||||
sizes="(min-width: 1280px) 360px, (min-width: 1024px) 320px, (min-width: 640px) 280px, 240px"
|
sizes="(min-width: 1280px) 360px, (min-width: 1024px) 320px, (min-width: 640px) 280px, 240px"
|
||||||
className="h-9 w-auto object-contain object-left sm:h-10 lg:h-11 xl:h-12"
|
className={cn(
|
||||||
|
"h-9 w-auto object-contain object-left drop-shadow-[0_10px_24px_rgba(15,23,42,0.12)] transition-[filter,opacity,transform] duration-300 ease-out sm:h-10 lg:h-11 xl:h-12",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
priority={priority}
|
priority={priority}
|
||||||
/>
|
/>
|
||||||
</span>
|
</motion.span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ export function ThemeToggle({
|
|||||||
variant={variant}
|
variant={variant}
|
||||||
size={label ? "sm" : "icon"}
|
size={label ? "sm" : "icon"}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
className={cn(label ? "justify-start" : undefined, className)}
|
className={cn("group cursor-pointer", label ? "justify-start" : undefined, className)}
|
||||||
>
|
>
|
||||||
<Moon className={cn("h-4 w-4", iconClassName)} />
|
<Moon className={cn("h-4 w-4 transition-transform duration-700 ease-out group-hover:scale-110 group-hover:rotate-[360deg]", iconClassName)} />
|
||||||
{label ? <span>{label}</span> : null}
|
{label ? <span>{label}</span> : null}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -54,9 +54,13 @@ export function ThemeToggle({
|
|||||||
size={label ? "sm" : "icon"}
|
size={label ? "sm" : "icon"}
|
||||||
onClick={() => setTheme(isDark ? "light" : "dark")}
|
onClick={() => setTheme(isDark ? "light" : "dark")}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
className={cn(label ? "justify-start" : undefined, className)}
|
className={cn("group cursor-pointer", label ? "justify-start" : undefined, className)}
|
||||||
>
|
>
|
||||||
{isDark ? <Sun className={cn("h-4 w-4", iconClassName)} /> : <Moon className={cn("h-4 w-4", iconClassName)} />}
|
{isDark ? (
|
||||||
|
<Sun className={cn("h-4 w-4 transition-transform duration-700 ease-out group-hover:scale-110 group-hover:rotate-[360deg]", iconClassName)} />
|
||||||
|
) : (
|
||||||
|
<Moon className={cn("h-4 w-4 transition-transform duration-700 ease-out group-hover:scale-110 group-hover:rotate-[360deg]", iconClassName)} />
|
||||||
|
)}
|
||||||
{label ? <span>{label}</span> : null}
|
{label ? <span>{label}</span> : null}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 min-w-[12rem] overflow-hidden rounded-surface border border-border/80 bg-popover p-1 text-popover-foreground shadow-panel",
|
"z-50 min-w-[12rem] overflow-hidden rounded-surface border border-border/80 bg-popover p-1 text-popover-foreground shadow-panel data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
+21
-17
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"home": "الرئيسية",
|
"home": "هومبيج",
|
||||||
"portfolio": "الأعمال",
|
"portfolio": "معرض أعمالي",
|
||||||
"products": "المنتجات",
|
"products": "منتجاتي",
|
||||||
"about": "من أنا",
|
"about": "معلومات عني",
|
||||||
"contact": "تواصل",
|
"contact": "تواصل معي",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"openMenu": "فتح القائمة",
|
"openMenu": "فتح القائمة",
|
||||||
"closeMenu": "إغلاق القائمة",
|
"closeMenu": "إغلاق القائمة",
|
||||||
@@ -22,20 +22,24 @@
|
|||||||
"note": "شكراً لصبرك. النسخة الجديدة ستصل قريباً بهوية أنضج وحضور أدق."
|
"note": "شكراً لصبرك. النسخة الجديدة ستصل قريباً بهوية أنضج وحضور أدق."
|
||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
"heroKicker": "مرحباً، أنا مو",
|
"heroKicker": "مرحبااا، أنا محمد",
|
||||||
"heroTitle": "frontend\n developer\n& designer",
|
"heroTitle": "مطور واجهات ويب\nومصمم جرافيك",
|
||||||
"heroText": "أصمم وأبني تجارب رقمية نظيفة مع اهتمام دقيق بالتفاصيل والحركة وسهولة الاستخدام.",
|
"heroText": "بشتغل على حلول رقمية لمشاكل حقيقية\nبتطلع ممتازة وبتشتغل بكفاءة.",
|
||||||
"portfolioTitle": "أعمال مميزة",
|
"servicesEyebrow": "كيف بقدر ساعدك",
|
||||||
"portfolioText": "منطقة تجريبية لعرض مشاريع مختارة.",
|
"servicesTitle": "أقدر أساعدك تبني حضور رقمي مرتب وفعّال.",
|
||||||
"productsTitle": "منتجات مميزة",
|
"servicesText": "إذا بدك تطلق براند، موقع، متجر، إضافة ووردبريس، أو بدك ترتب السيو والتراكنغ وتبني سستم خاص لشغلك، بقدر ساعدك من الفكرة للتنفيذ بشكل واضح وعملي.",
|
||||||
"productsText": "منطقة تجريبية لأهم العروض والمنتجات.",
|
"servicesPrimaryCta": "احكيلي عن مشروعك",
|
||||||
"ctaTitle": "جاهز للخطوة التالية؟",
|
"servicesSecondaryCta": "شوف شغلي",
|
||||||
"ctaText": "فيك تراسلني لنحدد النطاق الأنسب لمشروعك.",
|
"serviceBrand": "بقدر ساعدك تبني براند واضح ومرتب.",
|
||||||
|
"serviceWebsite": "بقدر ساعدك تبني موقع إلكتروني يليق بشغلك.",
|
||||||
|
"serviceStore": "بقدر ساعدك تبني متجر يبيع بشكل أوضح وأسهل.",
|
||||||
|
"serviceWordPress": "بقدر ساعدك تبني إضافة WordPress خاصة فيك.",
|
||||||
|
"serviceSeo": "بقدر ساعدك تحسن ظهور موقعك بمحركات البحث.",
|
||||||
|
"serviceTracking": "بقدر ساعدك تضبط التراكنغ وتفهم حركة الزوار.",
|
||||||
|
"serviceSystem": "بقدر ساعدك تبني سستم خاص لشركتك أو لموظفينك.",
|
||||||
"toPortfolio": "عرض الأعمال",
|
"toPortfolio": "عرض الأعمال",
|
||||||
"toProducts": "عرض المنتجات",
|
"toProducts": "عرض المنتجات",
|
||||||
"toContact": "تواصل معي",
|
"toContact": "تواصل معي"
|
||||||
"heroCardTitle": "إطلاق سريع",
|
|
||||||
"heroCardText": "هيكل ومحتوى ومكونات تساعد على نمو سريع وواضح."
|
|
||||||
},
|
},
|
||||||
"portfolioPage": {
|
"portfolioPage": {
|
||||||
"title": "الأعمال",
|
"title": "الأعمال",
|
||||||
|
|||||||
+16
-12
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"home": "Start",
|
"home": "Startseite",
|
||||||
"portfolio": "Portfolio",
|
"portfolio": "Portfolio",
|
||||||
"products": "Produkte",
|
"products": "Produkte",
|
||||||
"about": "Über mich",
|
"about": "Über mich",
|
||||||
@@ -23,19 +23,23 @@
|
|||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
"heroKicker": "Hi, ich bin Moh",
|
"heroKicker": "Hi, ich bin Moh",
|
||||||
"heroTitle": "frontend\n developer\n& designer",
|
"heroTitle": "frontend\ndeveloper\n& designer",
|
||||||
"heroText": "Ich entwerfe und entwickle klare digitale Erlebnisse mit Fokus auf Details, Motion und starke Interfaces.",
|
"heroText": "Ich entwickle digitale Loesungen fuer echte Probleme,\ndie gut aussehen und sauber funktionieren.",
|
||||||
"portfolioTitle": "Featured Projects",
|
"servicesEyebrow": "Wobei ich helfen kann",
|
||||||
"portfolioText": "Platzhalter fuer ausgewaehlte Kundenprojekte.",
|
"servicesTitle": "Ich kann dir helfen, den richtigen digitalen Aufbau fuer dein Business zu schaffen.",
|
||||||
"productsTitle": "Featured Products",
|
"servicesText": "Von Brand Aufbau und Websites bis zu Shops, WordPress Plugins, SEO, Tracking und internen Systemen helfe ich dir dabei, die passende digitale Loesung klar und sauber umzusetzen.",
|
||||||
"productsText": "Platzhalter fuer die wichtigsten Produktangebote.",
|
"servicesPrimaryCta": "Erzaehl mir von deinem Projekt",
|
||||||
"ctaTitle": "Bereit fuer den naechsten Schritt?",
|
"servicesSecondaryCta": "Meine Arbeiten ansehen",
|
||||||
"ctaText": "Wir planen zusammen den passenden Scope fuer dein Projekt.",
|
"serviceBrand": "Ich kann dir helfen, eine klare und starke Marke aufzubauen.",
|
||||||
|
"serviceWebsite": "Ich kann dir helfen, eine Website fuer dein Business zu bauen.",
|
||||||
|
"serviceStore": "Ich kann dir helfen, einen Shop mit besserer Klarheit aufzubauen.",
|
||||||
|
"serviceWordPress": "Ich kann dir helfen, ein eigenes WordPress Plugin zu entwickeln.",
|
||||||
|
"serviceSeo": "Ich kann dir helfen, deine Sichtbarkeit in Suchmaschinen zu verbessern.",
|
||||||
|
"serviceTracking": "Ich kann dir helfen, Tracking sauber aufzusetzen und auszuwerten.",
|
||||||
|
"serviceSystem": "Ich kann dir helfen, ein eigenes System fuer deine Firma oder dein Team zu bauen.",
|
||||||
"toPortfolio": "Portfolio ansehen",
|
"toPortfolio": "Portfolio ansehen",
|
||||||
"toProducts": "Produkte ansehen",
|
"toProducts": "Produkte ansehen",
|
||||||
"toContact": "Kontakt aufnehmen",
|
"toContact": "Kontakt aufnehmen"
|
||||||
"heroCardTitle": "Schneller Rollout",
|
|
||||||
"heroCardText": "Struktur, Content und Komponenten fuer schnelles Wachstum."
|
|
||||||
},
|
},
|
||||||
"portfolioPage": {
|
"portfolioPage": {
|
||||||
"title": "Portfolio",
|
"title": "Portfolio",
|
||||||
|
|||||||
+17
-13
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"portfolio": "Portfolio",
|
"portfolio": "My Portfolio",
|
||||||
"products": "Products",
|
"products": "Products",
|
||||||
"about": "About",
|
"about": "About Me",
|
||||||
"contact": "Contact",
|
"contact": "Contact",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"openMenu": "Open menu",
|
"openMenu": "Open menu",
|
||||||
@@ -23,19 +23,23 @@
|
|||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
"heroKicker": "Hi, I am Moh",
|
"heroKicker": "Hi, I am Moh",
|
||||||
"heroTitle": "frontend\ndelevoper\n& designer",
|
"heroTitle": "frontend\ndeveloper\n& designer",
|
||||||
"heroText": "I design and build clean digital experiences with a sharp eye for detail, motion, and usable interfaces.",
|
"heroText": "I craft digital solutions for human problems\nthat look good and work well.",
|
||||||
"portfolioTitle": "Featured Projects",
|
"servicesEyebrow": "How I Can Help",
|
||||||
"portfolioText": "Placeholder area for highlighted client projects.",
|
"servicesTitle": "I can help you build the right digital setup for your business.",
|
||||||
"productsTitle": "Featured Products",
|
"servicesText": "From brand foundations and websites to stores, WordPress plugins, SEO, tracking, and internal systems, I can help shape, build, and refine the setup your business actually needs.",
|
||||||
"productsText": "Placeholder area for top product offerings.",
|
"servicesPrimaryCta": "Tell me about your project",
|
||||||
"ctaTitle": "Ready for your next step?",
|
"servicesSecondaryCta": "See my work",
|
||||||
"ctaText": "We can shape the right project scope together.",
|
"serviceBrand": "I can help you build a clear and reliable brand.",
|
||||||
|
"serviceWebsite": "I can help you build a website that fits your business.",
|
||||||
|
"serviceStore": "I can help you launch an online store that sells more clearly.",
|
||||||
|
"serviceWordPress": "I can help you build a custom WordPress plugin.",
|
||||||
|
"serviceSeo": "I can help you improve your visibility in search engines.",
|
||||||
|
"serviceTracking": "I can help you set up tracking and understand user behavior.",
|
||||||
|
"serviceSystem": "I can help you build a custom system for your company or team.",
|
||||||
"toPortfolio": "View portfolio",
|
"toPortfolio": "View portfolio",
|
||||||
"toProducts": "View products",
|
"toProducts": "View products",
|
||||||
"toContact": "Contact me",
|
"toContact": "Contact me"
|
||||||
"heroCardTitle": "Fast rollout",
|
|
||||||
"heroCardText": "Structure, content, and components for rapid growth."
|
|
||||||
},
|
},
|
||||||
"portfolioPage": {
|
"portfolioPage": {
|
||||||
"title": "Portfolio",
|
"title": "Portfolio",
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fc0" d="M0 341.3h512V512H0z"/>
|
||||||
|
<path fill="#000001" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="red" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 229 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="flag-icons-sy" viewBox="0 0 512 512">
|
||||||
|
<path d="M0 0h512v512H0Z"/>
|
||||||
|
<path fill="#fff" d="M0 0h512v341.3H0Z"/>
|
||||||
|
<path fill="#007a3d" d="M0 0h512v170.7H0Z"/>
|
||||||
|
<path fill="#ce1126" d="M26.3 317.9 67.2 192 108 317.9 1 240h132.4m270.5 77.8L445 192l40.8 125.9-107-77.8H511m-295.9 77.8L256 192l40.9 125.9-107-77.8h132.3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 392 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-us" viewBox="0 0 512 512">
|
||||||
|
<path fill="#bd3d44" d="M0 0h512v512H0"/>
|
||||||
|
<path stroke="#fff" stroke-width="40" d="M0 58h512M0 137h512M0 216h512M0 295h512M0 374h512M0 453h512"/>
|
||||||
|
<path fill="#192f5d" d="M0 0h390v275H0z"/>
|
||||||
|
<marker id="us-a" markerHeight="30" markerWidth="30">
|
||||||
|
<path fill="#fff" d="m15 0 9.3 28.6L0 11h30L5.7 28.6"/>
|
||||||
|
</marker>
|
||||||
|
<path fill="none" marker-mid="url(#us-a)" d="m0 0 18 11h65 65 65 65 66L51 39h65 65 65 65L18 66h65 65 65 65 66L51 94h65 65 65 65L18 121h65 65 65 65 66L51 149h65 65 65 65L18 177h65 65 65 65 66L51 205h65 65 65 65L18 232h65 65 65 65 66z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 650 B |
Reference in New Issue
Block a user