diff --git a/app/[locale]/(site)/page.tsx b/app/[locale]/(site)/page.tsx index 1d8c35a..98af420 100644 --- a/app/[locale]/(site)/page.tsx +++ b/app/[locale]/(site)/page.tsx @@ -1,9 +1,13 @@ import type { Metadata } from "next"; import { ArrowRight, + BadgeCheck, + Blocks, BriefcaseBusiness, - Boxes, - Mail, + ChartNoAxesCombined, + Globe, + Package2, + Search, } from "lucide-react"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; @@ -17,11 +21,6 @@ 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 { - getLocalizedValue, - getPublishedPortfolioProjects, -} from "@/lib/portfolio"; -import { pickText, productItems } from "@/lib/site-data"; type HomePageProps = { params: { @@ -29,6 +28,16 @@ type HomePageProps = { }; }; +const serviceIcons = [ + BriefcaseBusiness, + Globe, + Package2, + Blocks, + Search, + ChartNoAxesCombined, + BadgeCheck, +]; + export const dynamic = "force-dynamic"; export async function generateMetadata({ @@ -51,9 +60,16 @@ export async function generateMetadata({ export default async function HomePage({ params: { locale } }: HomePageProps) { 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 serviceItems = [ + t("serviceBrand"), + t("serviceWebsite"), + t("serviceStore"), + t("serviceWordPress"), + t("serviceSeo"), + t("serviceTracking"), + t("serviceSystem"), + ]; return ( <> @@ -66,116 +82,64 @@ export default async function HomePage({ params: { locale } }: HomePageProps) { contactLabel={t("toContact")} /> - + - - -
-
-

- {t("portfolioTitle")} -

-

- {t("portfolioText")} -

-
- -
-
- {featuredProjects.map((item) => ( - - - -

- {getLocalizedValue(item.category.name, localeKey)} - {item.projectYear} -

-

- {getLocalizedValue(item.title, localeKey)} -

-

- {getLocalizedValue(item.summary, localeKey)} -

- - {t("toPortfolio")} + + +
+
+

+ {t("servicesEyebrow")} +

+

+ {t("servicesTitle")} +

+

+ {t("servicesText")} +

+
+
- - - - - - - -
-
-

- {t("productsTitle")} -

-

- {t("productsText")} -

-
- -
-
- {featuredProducts.map((item) => ( - - - + +
-
-
-
+ + {t("servicesSecondaryCta")} + + +
+
- - - -
-
-

- - {t("ctaTitle")} -

-

- {t("ctaText")} -

+
+ {serviceItems.map((item, index) => { + const Icon = serviceIcons[index]; + + return ( +
+ +

+ {item} +

+
+ ); + })} +
- -
-
-
+
+
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 23be21c..64be88e 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -34,10 +34,13 @@ export default async function LocaleLayout({ setRequestLocale(locale); const messages = await getMessages(); + const localeClassName = locale === "ar" ? "font-arabic" : "font-latin"; return ( -
{children}
+
+ {children} +
); } diff --git a/app/fonts/Dubai-Bold.woff2 b/app/fonts/Dubai-Bold.woff2 new file mode 100755 index 0000000..5a036a9 Binary files /dev/null and b/app/fonts/Dubai-Bold.woff2 differ diff --git a/app/fonts/Dubai-Light.woff2 b/app/fonts/Dubai-Light.woff2 new file mode 100755 index 0000000..0affb76 Binary files /dev/null and b/app/fonts/Dubai-Light.woff2 differ diff --git a/app/fonts/Dubai-Medium.woff2 b/app/fonts/Dubai-Medium.woff2 new file mode 100755 index 0000000..2bb1a8a Binary files /dev/null and b/app/fonts/Dubai-Medium.woff2 differ diff --git a/app/fonts/Dubai-Regular.woff2 b/app/fonts/Dubai-Regular.woff2 new file mode 100755 index 0000000..7e1b36c Binary files /dev/null and b/app/fonts/Dubai-Regular.woff2 differ diff --git a/app/fonts/MuseoSansRounded100.woff b/app/fonts/MuseoSansRounded100.woff new file mode 100644 index 0000000..1babb3f Binary files /dev/null and b/app/fonts/MuseoSansRounded100.woff differ diff --git a/app/fonts/MuseoSansRounded100.woff2 b/app/fonts/MuseoSansRounded100.woff2 new file mode 100644 index 0000000..7afeb21 Binary files /dev/null and b/app/fonts/MuseoSansRounded100.woff2 differ diff --git a/app/fonts/MuseoSansRounded1000.woff b/app/fonts/MuseoSansRounded1000.woff new file mode 100644 index 0000000..2d21b92 Binary files /dev/null and b/app/fonts/MuseoSansRounded1000.woff differ diff --git a/app/fonts/MuseoSansRounded1000.woff2 b/app/fonts/MuseoSansRounded1000.woff2 new file mode 100644 index 0000000..480d50b Binary files /dev/null and b/app/fonts/MuseoSansRounded1000.woff2 differ diff --git a/app/fonts/MuseoSansRounded300.woff b/app/fonts/MuseoSansRounded300.woff new file mode 100644 index 0000000..3679ffc Binary files /dev/null and b/app/fonts/MuseoSansRounded300.woff differ diff --git a/app/fonts/MuseoSansRounded300.woff2 b/app/fonts/MuseoSansRounded300.woff2 new file mode 100644 index 0000000..6fddc0b Binary files /dev/null and b/app/fonts/MuseoSansRounded300.woff2 differ diff --git a/app/fonts/MuseoSansRounded500.woff b/app/fonts/MuseoSansRounded500.woff new file mode 100644 index 0000000..db54026 Binary files /dev/null and b/app/fonts/MuseoSansRounded500.woff differ diff --git a/app/fonts/MuseoSansRounded500.woff2 b/app/fonts/MuseoSansRounded500.woff2 new file mode 100644 index 0000000..1df1ef9 Binary files /dev/null and b/app/fonts/MuseoSansRounded500.woff2 differ diff --git a/app/fonts/MuseoSansRounded700.woff b/app/fonts/MuseoSansRounded700.woff new file mode 100644 index 0000000..fbc067a Binary files /dev/null and b/app/fonts/MuseoSansRounded700.woff differ diff --git a/app/fonts/MuseoSansRounded700.woff2 b/app/fonts/MuseoSansRounded700.woff2 new file mode 100644 index 0000000..f2d063d Binary files /dev/null and b/app/fonts/MuseoSansRounded700.woff2 differ diff --git a/app/fonts/MuseoSansRounded900.woff b/app/fonts/MuseoSansRounded900.woff new file mode 100644 index 0000000..8678e32 Binary files /dev/null and b/app/fonts/MuseoSansRounded900.woff differ diff --git a/app/fonts/MuseoSansRounded900.woff2 b/app/fonts/MuseoSansRounded900.woff2 new file mode 100644 index 0000000..8cf8fa9 Binary files /dev/null and b/app/fonts/MuseoSansRounded900.woff2 differ diff --git a/app/globals.css b/app/globals.css index 50cb926..2fbf28c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -40,6 +40,11 @@ --brand-primary: 9 73% 50%; --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-secondary-alpha: 0.04; --ambient-grid-alpha: 0; @@ -114,6 +119,11 @@ --brand-primary: 9 78% 56%; --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-secondary-alpha: 0.05; --ambient-grid-alpha: 0; @@ -139,12 +149,25 @@ html { color-scheme: light; background-color: hsl(var(--background)); + font-family: var(--font-museo), sans-serif; } html.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 { * { @apply border-border; @@ -154,11 +177,19 @@ html.dark { @apply min-h-screen text-foreground antialiased; background-color: hsl(var(--background)); background-image: none; + font-family: inherit; } a { @apply transition-colors; } + + button, + input, + select, + textarea { + font: inherit; + } } ::selection { @@ -214,4 +245,188 @@ html.dark { 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; + } } diff --git a/app/layout.tsx b/app/layout.tsx index 8a330b3..333a575 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,11 +1,76 @@ import type { Metadata } from "next"; import { unstable_noStore as noStore } from "next/cache"; +import localFont from "next/font/local"; import { getLocale } from "next-intl/server"; import { ThemeProvider } from "@/components/theme-provider"; import { buildAppMetadata } from "@/lib/metadata"; import { getDirection } from "@/lib/locale"; 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 revalidate = 0; @@ -26,7 +91,7 @@ export default async function RootLayout({ return ( - + {children} diff --git a/app/root/layout.tsx b/app/root/layout.tsx index 0f69049..ed8aec2 100644 --- a/app/root/layout.tsx +++ b/app/root/layout.tsx @@ -21,5 +21,5 @@ export const metadata: Metadata = { }; export default function RootLayout({ children }: RootLayoutProps) { - return children; + return
{children}
; } diff --git a/components/layout/hero-motion-backdrop.tsx b/components/layout/hero-motion-backdrop.tsx new file mode 100644 index 0000000..18fe1c7 --- /dev/null +++ b/components/layout/hero-motion-backdrop.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { motion } from "framer-motion"; + +type HeroMotionBackdropProps = { + compact?: boolean; +}; + +export function HeroMotionBackdrop({ compact = false }: HeroMotionBackdropProps) { + return ( +
+
+ + + + + + + + + + +
+
+
+ ); +} diff --git a/components/layout/home-hero.tsx b/components/layout/home-hero.tsx index 500fc25..ebfdb5b 100644 --- a/components/layout/home-hero.tsx +++ b/components/layout/home-hero.tsx @@ -1,8 +1,7 @@ import { ArrowRight } from "lucide-react"; import Link from "next/link"; -import { HeroAtmosphere } from "@/components/layout/hero-atmosphere"; -import { MotionFade } from "@/components/motion-fade"; +import { HeroMotionBackdrop } from "@/components/layout/hero-motion-backdrop"; import { Button } from "@/components/ui/button"; import { getLocalizedPath } from "@/lib/locale"; @@ -23,34 +22,60 @@ export function HomeHero({ portfolioLabel, contactLabel, }: HomeHeroProps) { + const titleLines = title.split("\n").filter(Boolean); + const isArabic = locale === "ar"; + return ( -
- +
+ +
+
- -
-

- {kicker} -

-

- {title} -

-

- {description} -

-
- - -
+
+

+ {kicker} +

+

+ {titleLines.map((line, index) => ( + + {line} + + ))} +

+

+ {description} +

+
+ +
- +
); diff --git a/components/layout/locale-toggle.tsx b/components/layout/locale-toggle.tsx index a74bf18..e54bc01 100644 --- a/components/layout/locale-toggle.tsx +++ b/components/layout/locale-toggle.tsx @@ -1,6 +1,6 @@ "use client"; -import { Languages } from "lucide-react"; +import Image from "next/image"; import { usePathname } from "next/navigation"; 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 currentPath = stripLocalePrefix(pathname); 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 = { + de: { src: "/flags/de.svg", alt: "German" }, + en: { src: "/flags/us.svg", alt: "English" }, + ar: { src: "/flags/sy.svg", alt: "Arabic" }, + }; return ( - + - - {locales.map((targetLocale) => ( - + + {availableLocales.map((targetLocale) => ( + - {targetLocale.toUpperCase()} - {targetLocale === currentLocale ? ( - Active - ) : null} + {localeFlags[targetLocale].alt} ))} diff --git a/components/layout/page-hero.tsx b/components/layout/page-hero.tsx index 54ef439..cd59fb5 100644 --- a/components/layout/page-hero.tsx +++ b/components/layout/page-hero.tsx @@ -1,5 +1,4 @@ -import { HeroAtmosphere } from "@/components/layout/hero-atmosphere"; -import { MotionFade } from "@/components/motion-fade"; +import { HeroMotionBackdrop } from "@/components/layout/hero-motion-backdrop"; type PageHeroProps = { title: string; @@ -8,22 +7,22 @@ type PageHeroProps = { export function PageHero({ title, description }: PageHeroProps) { return ( -
- +
+
- -

+

+

{title}

-

+

{title}

{description ? ( -

+

{description}

) : null} - +
); diff --git a/components/layout/site-header.tsx b/components/layout/site-header.tsx index b18fe54..86e7503 100644 --- a/components/layout/site-header.tsx +++ b/components/layout/site-header.tsx @@ -61,9 +61,11 @@ function NavLinks({ key={item.key} href={getLocalizedPath(locale, itemPath)} className={cn( - "relative z-10 rounded-[var(--radius-pill)] text-sm font-medium transition-colors", - mobile ? "px-4 py-3" : "px-3.5 py-2 xl:px-4", - isActive ? "text-primary-foreground" : "text-muted-foreground hover:text-foreground", + "relative z-10 rounded-[var(--radius-pill)] transition-colors", + 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-[hsl(var(--hero-ink)/0.62)] hover:text-[hsl(var(--hero-ink))] dark:text-muted-foreground dark:hover:text-foreground", )} onClick={onNavigate} > @@ -92,21 +94,23 @@ export function SiteHeader({ const pathname = usePathname(); const locale = useLocale(); const t = useTranslations("navigation"); + const isArabic = locale === "ar"; return (
-
+
@@ -116,7 +120,10 @@ export function SiteHeader({ initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} 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]", + )} > @@ -127,15 +134,15 @@ export function SiteHeader({ initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} 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)]" > {isAdmin ? ( -
@@ -154,7 +161,7 @@ export function SiteHeader({ onClick={() => setIsOpen((open) => !open)} variant="ghost" 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")} > setIsOpen(false)} > @@ -243,17 +250,17 @@ export function SiteHeader({ animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 8 }} 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))]" >
{isAdmin ? ( -
diff --git a/components/layout/site-logo.tsx b/components/layout/site-logo.tsx index 83314ea..f8a9f51 100644 --- a/components/layout/site-logo.tsx +++ b/components/layout/site-logo.tsx @@ -1,14 +1,18 @@ "use client"; +import { motion } from "framer-motion"; import Image from "next/image"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; + type SiteLogoProps = { lightLogoUrl?: string | null; darkLogoUrl?: string | null; alt: string; priority?: boolean; + className?: string; }; export function SiteLogo({ @@ -16,6 +20,7 @@ export function SiteLogo({ darkLogoUrl, alt, priority = false, + className, }: SiteLogoProps) { const { theme, resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); @@ -30,16 +35,23 @@ export function SiteLogo({ const src = isDark ? darkLogoUrl || lightLogoUrl || fallbackSrc : lightLogoUrl || darkLogoUrl || fallbackSrc; return ( - + {alt} - +
); } diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx index e51abbd..afa3f74 100644 --- a/components/theme-toggle.tsx +++ b/components/theme-toggle.tsx @@ -36,9 +36,9 @@ export function ThemeToggle({ variant={variant} size={label ? "sm" : "icon"} aria-label={ariaLabel} - className={cn(label ? "justify-start" : undefined, className)} + className={cn("group cursor-pointer", label ? "justify-start" : undefined, className)} > - + {label ? {label} : null} ); @@ -54,9 +54,13 @@ export function ThemeToggle({ size={label ? "sm" : "icon"} onClick={() => setTheme(isDark ? "light" : "dark")} aria-label={ariaLabel} - className={cn(label ? "justify-start" : undefined, className)} + className={cn("group cursor-pointer", label ? "justify-start" : undefined, className)} > - {isDark ? : } + {isDark ? ( + + ) : ( + + )} {label ? {label} : null} ); diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx index f3b9756..2dc3c51 100644 --- a/components/ui/dropdown-menu.tsx +++ b/components/ui/dropdown-menu.tsx @@ -60,7 +60,7 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} 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, )} {...props} diff --git a/messages/ar.json b/messages/ar.json index dad1307..550e459 100644 --- a/messages/ar.json +++ b/messages/ar.json @@ -1,10 +1,10 @@ { "navigation": { - "home": "الرئيسية", - "portfolio": "الأعمال", - "products": "المنتجات", - "about": "من أنا", - "contact": "تواصل", + "home": "هومبيج", + "portfolio": "معرض أعمالي", + "products": "منتجاتي", + "about": "معلومات عني", + "contact": "تواصل معي", "root": "Root", "openMenu": "فتح القائمة", "closeMenu": "إغلاق القائمة", @@ -22,20 +22,24 @@ "note": "شكراً لصبرك. النسخة الجديدة ستصل قريباً بهوية أنضج وحضور أدق." }, "homepage": { - "heroKicker": "مرحباً، أنا مو", - "heroTitle": "frontend\n developer\n& designer", - "heroText": "أصمم وأبني تجارب رقمية نظيفة مع اهتمام دقيق بالتفاصيل والحركة وسهولة الاستخدام.", - "portfolioTitle": "أعمال مميزة", - "portfolioText": "منطقة تجريبية لعرض مشاريع مختارة.", - "productsTitle": "منتجات مميزة", - "productsText": "منطقة تجريبية لأهم العروض والمنتجات.", - "ctaTitle": "جاهز للخطوة التالية؟", - "ctaText": "فيك تراسلني لنحدد النطاق الأنسب لمشروعك.", + "heroKicker": "مرحبااا، أنا محمد", + "heroTitle": "مطور واجهات ويب\nومصمم جرافيك", + "heroText": "بشتغل على حلول رقمية لمشاكل حقيقية\nبتطلع ممتازة وبتشتغل بكفاءة.", + "servicesEyebrow": "كيف بقدر ساعدك", + "servicesTitle": "أقدر أساعدك تبني حضور رقمي مرتب وفعّال.", + "servicesText": "إذا بدك تطلق براند، موقع، متجر، إضافة ووردبريس، أو بدك ترتب السيو والتراكنغ وتبني سستم خاص لشغلك، بقدر ساعدك من الفكرة للتنفيذ بشكل واضح وعملي.", + "servicesPrimaryCta": "احكيلي عن مشروعك", + "servicesSecondaryCta": "شوف شغلي", + "serviceBrand": "بقدر ساعدك تبني براند واضح ومرتب.", + "serviceWebsite": "بقدر ساعدك تبني موقع إلكتروني يليق بشغلك.", + "serviceStore": "بقدر ساعدك تبني متجر يبيع بشكل أوضح وأسهل.", + "serviceWordPress": "بقدر ساعدك تبني إضافة WordPress خاصة فيك.", + "serviceSeo": "بقدر ساعدك تحسن ظهور موقعك بمحركات البحث.", + "serviceTracking": "بقدر ساعدك تضبط التراكنغ وتفهم حركة الزوار.", + "serviceSystem": "بقدر ساعدك تبني سستم خاص لشركتك أو لموظفينك.", "toPortfolio": "عرض الأعمال", "toProducts": "عرض المنتجات", - "toContact": "تواصل معي", - "heroCardTitle": "إطلاق سريع", - "heroCardText": "هيكل ومحتوى ومكونات تساعد على نمو سريع وواضح." + "toContact": "تواصل معي" }, "portfolioPage": { "title": "الأعمال", diff --git a/messages/de.json b/messages/de.json index a3e69cb..d0c89f5 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1,6 +1,6 @@ { "navigation": { - "home": "Start", + "home": "Startseite", "portfolio": "Portfolio", "products": "Produkte", "about": "Über mich", @@ -23,19 +23,23 @@ }, "homepage": { "heroKicker": "Hi, ich bin Moh", - "heroTitle": "frontend\n developer\n& designer", - "heroText": "Ich entwerfe und entwickle klare digitale Erlebnisse mit Fokus auf Details, Motion und starke Interfaces.", - "portfolioTitle": "Featured Projects", - "portfolioText": "Platzhalter fuer ausgewaehlte Kundenprojekte.", - "productsTitle": "Featured Products", - "productsText": "Platzhalter fuer die wichtigsten Produktangebote.", - "ctaTitle": "Bereit fuer den naechsten Schritt?", - "ctaText": "Wir planen zusammen den passenden Scope fuer dein Projekt.", + "heroTitle": "frontend\ndeveloper\n& designer", + "heroText": "Ich entwickle digitale Loesungen fuer echte Probleme,\ndie gut aussehen und sauber funktionieren.", + "servicesEyebrow": "Wobei ich helfen kann", + "servicesTitle": "Ich kann dir helfen, den richtigen digitalen Aufbau fuer dein Business zu schaffen.", + "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.", + "servicesPrimaryCta": "Erzaehl mir von deinem Projekt", + "servicesSecondaryCta": "Meine Arbeiten ansehen", + "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", "toProducts": "Produkte ansehen", - "toContact": "Kontakt aufnehmen", - "heroCardTitle": "Schneller Rollout", - "heroCardText": "Struktur, Content und Komponenten fuer schnelles Wachstum." + "toContact": "Kontakt aufnehmen" }, "portfolioPage": { "title": "Portfolio", diff --git a/messages/en.json b/messages/en.json index 8db1a4f..26956ce 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,9 +1,9 @@ { "navigation": { "home": "Home", - "portfolio": "Portfolio", + "portfolio": "My Portfolio", "products": "Products", - "about": "About", + "about": "About Me", "contact": "Contact", "root": "Root", "openMenu": "Open menu", @@ -23,19 +23,23 @@ }, "homepage": { "heroKicker": "Hi, I am Moh", - "heroTitle": "frontend\ndelevoper\n& designer", - "heroText": "I design and build clean digital experiences with a sharp eye for detail, motion, and usable interfaces.", - "portfolioTitle": "Featured Projects", - "portfolioText": "Placeholder area for highlighted client projects.", - "productsTitle": "Featured Products", - "productsText": "Placeholder area for top product offerings.", - "ctaTitle": "Ready for your next step?", - "ctaText": "We can shape the right project scope together.", + "heroTitle": "frontend\ndeveloper\n& designer", + "heroText": "I craft digital solutions for human problems\nthat look good and work well.", + "servicesEyebrow": "How I Can Help", + "servicesTitle": "I can help you build the right digital setup for your business.", + "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.", + "servicesPrimaryCta": "Tell me about your project", + "servicesSecondaryCta": "See my work", + "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", "toProducts": "View products", - "toContact": "Contact me", - "heroCardTitle": "Fast rollout", - "heroCardText": "Structure, content, and components for rapid growth." + "toContact": "Contact me" }, "portfolioPage": { "title": "Portfolio", diff --git a/public/flags/de.svg b/public/flags/de.svg new file mode 100644 index 0000000..05a0a69 --- /dev/null +++ b/public/flags/de.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/flags/sy.svg b/public/flags/sy.svg new file mode 100644 index 0000000..b2fdc2f --- /dev/null +++ b/public/flags/sy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/flags/us.svg b/public/flags/us.svg new file mode 100644 index 0000000..a722047 --- /dev/null +++ b/public/flags/us.svg @@ -0,0 +1,9 @@ + + + + + + + + +