From 985fbe17453887d5d5750e4ab2a0644576073a6f Mon Sep 17 00:00:00 2001 From: MOH Date: Sat, 7 Mar 2026 20:08:59 +0100 Subject: [PATCH] Refine coming soon experience and global ambient UI --- app/[locale]/coming-soon/page.tsx | 70 ++++-------------- app/globals.css | 39 +++++++++- components/layout/animated-logo.tsx | 83 ++++++++++++++++++++++ components/layout/floating-preferences.tsx | 28 ++++++++ components/layout/locale-toggle.tsx | 43 +++++++---- messages/ar.json | 12 ++-- messages/de.json | 10 +-- messages/en.json | 10 +-- 8 files changed, 210 insertions(+), 85 deletions(-) create mode 100644 components/layout/animated-logo.tsx create mode 100644 components/layout/floating-preferences.tsx diff --git a/app/[locale]/coming-soon/page.tsx b/app/[locale]/coming-soon/page.tsx index 3946ece..9283dce 100644 --- a/app/[locale]/coming-soon/page.tsx +++ b/app/[locale]/coming-soon/page.tsx @@ -1,17 +1,15 @@ import type { Metadata } from "next"; import { Sparkles } from "lucide-react"; -import Image from "next/image"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; +import { AnimatedLogo } from "@/components/layout/animated-logo"; import { Container } from "@/components/layout/container"; +import { FloatingPreferences } from "@/components/layout/floating-preferences"; import { MotionFade } from "@/components/motion-fade"; -import { ThemeToggle } from "@/components/theme-toggle"; import { getSiteSettings } from "@/lib/app-config"; import { buildLocalizedMetadata } from "@/lib/metadata"; -import { getLocalizedPath, resolveLocale } from "@/lib/locale"; +import { resolveLocale } from "@/lib/locale"; import { AppCard } from "@/components/ui/app-card"; -import { Button } from "@/components/ui/button"; import { CardContent } from "@/components/ui/card"; type ComingSoonPageProps = { @@ -40,73 +38,33 @@ export default async function ComingSoonPage({ params: { locale }, }: ComingSoonPageProps) { const localeKey = resolveLocale(locale); - const localeOptions = ["de", "en", "ar"] as const; const t = await getTranslations({ locale: localeKey, namespace: "comingSoon" }); - const tNav = await getTranslations({ locale: localeKey, namespace: "navigation" }); return ( -
-
-
+
+ - + - -
-
+ +
+
-
- -
- {localeOptions.map((targetLocale) => ( - - ))} -
-
-

{t("badge")}

-
- mohfarawati - mohfarawati +
+
-

+

{t("titleStart")}{" "} {t("titleAccent")}{" "} {t("titleEnd")} @@ -116,7 +74,7 @@ export default async function ComingSoonPage({ {t("description")}

-

+

{t("note")}

diff --git a/app/globals.css b/app/globals.css index 7f25cc3..ec0dee6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -40,6 +40,9 @@ --brand-primary: 214 84% 42%; --brand-secondary: 191 78% 42%; + --ambient-primary-alpha: 0.18; + --ambient-secondary-alpha: 0.14; + --ambient-grid-alpha: 0.4; --radius-surface: 0rem; --radius-nested: 0rem; @@ -111,6 +114,9 @@ --brand-primary: 205 88% 64%; --brand-secondary: 190 76% 57%; + --ambient-primary-alpha: 0.22; + --ambient-secondary-alpha: 0.18; + --ambient-grid-alpha: 0.32; --shadow-xs: 0 1px 2px hsl(220 50% 2% / 0.42); --shadow-sm: 0 14px 30px -20px hsl(220 50% 2% / 0.48); @@ -145,7 +151,24 @@ html.dark { } body { - @apply min-h-screen bg-background text-foreground antialiased; + @apply min-h-screen text-foreground antialiased; + background-color: hsl(var(--background)); + background-image: + radial-gradient( + circle at top, + hsl(var(--brand-primary) / var(--ambient-primary-alpha)), + transparent 34% + ), + radial-gradient( + circle at bottom right, + hsl(var(--brand-secondary) / var(--ambient-secondary-alpha)), + transparent 30% + ), + linear-gradient(hsl(var(--border) / var(--ambient-grid-alpha)) 1px, transparent 1px), + linear-gradient(90deg, hsl(var(--border) / var(--ambient-grid-alpha)) 1px, transparent 1px); + background-size: auto, auto, 24px 24px, 24px 24px; + background-position: center top, right bottom, center center, center center; + background-attachment: fixed; } a { @@ -187,4 +210,18 @@ html.dark { background-size: 24px 24px; background-position: center center; } + + .surface-ambient { + background-image: + radial-gradient( + circle at top, + hsl(var(--brand-primary) / calc(var(--ambient-primary-alpha) + 0.04)), + transparent 36% + ), + radial-gradient( + circle at bottom right, + hsl(var(--brand-secondary) / calc(var(--ambient-secondary-alpha) + 0.03)), + transparent 32% + ); + } } diff --git a/components/layout/animated-logo.tsx b/components/layout/animated-logo.tsx new file mode 100644 index 0000000..dcd8038 --- /dev/null +++ b/components/layout/animated-logo.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { motion, useMotionTemplate, useMotionValue, useSpring } from "framer-motion"; +import Image from "next/image"; + +export function AnimatedLogo() { + const rotateX = useMotionValue(0); + const rotateY = useMotionValue(0); + const springX = useSpring(rotateX, { stiffness: 180, damping: 18 }); + const springY = useSpring(rotateY, { stiffness: 180, damping: 18 }); + const glowX = useMotionTemplate`${springY}px`; + const glowY = useMotionTemplate`${springX}px`; + + function handlePointerMove(event: React.PointerEvent) { + const rect = event.currentTarget.getBoundingClientRect(); + const offsetX = event.clientX - rect.left - rect.width / 2; + const offsetY = event.clientY - rect.top - rect.height / 2; + + rotateX.set((-offsetY / rect.height) * 14); + rotateY.set((offsetX / rect.width) * 14); + } + + function handlePointerLeave() { + rotateX.set(0); + rotateY.set(0); + } + + return ( + + + + mohfarawati + mohfarawati + + ); +} diff --git a/components/layout/floating-preferences.tsx b/components/layout/floating-preferences.tsx new file mode 100644 index 0000000..4271707 --- /dev/null +++ b/components/layout/floating-preferences.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +import { LocaleToggle } from "@/components/layout/locale-toggle"; +import { ThemeToggle } from "@/components/theme-toggle"; +import { cn } from "@/lib/utils"; + +type FloatingPreferencesProps = { + locale: string; + className?: string; +}; + +export function FloatingPreferences({ + locale, + className, +}: FloatingPreferencesProps) { + const t = useTranslations("navigation"); + + return ( +
+
+ + +
+
+ ); +} diff --git a/components/layout/locale-toggle.tsx b/components/layout/locale-toggle.tsx index 7250fc7..5d161b0 100644 --- a/components/layout/locale-toggle.tsx +++ b/components/layout/locale-toggle.tsx @@ -1,8 +1,15 @@ "use client"; +import { Languages } from "lucide-react"; import { usePathname } from "next/navigation"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { AppLocale, getLocalizedPath, stripLocalePrefix } from "@/lib/locale"; type LocaleToggleProps = { @@ -16,20 +23,32 @@ export function LocaleToggle({ locale }: LocaleToggleProps) { const currentLocale = (["de", "en", "ar"].includes(locale) ? locale : "de") as AppLocale; return ( -
- {locales.map((targetLocale) => ( + + - ))} -
+ + + {locales.map((targetLocale) => ( + + + {targetLocale.toUpperCase()} + {targetLocale === currentLocale ? ( + Active + ) : null} + + + ))} + + ); } diff --git a/messages/ar.json b/messages/ar.json index 63ebbfc..b708679 100644 --- a/messages/ar.json +++ b/messages/ar.json @@ -14,12 +14,12 @@ "copyright": "© {year} moh-sass. جميع الحقوق محفوظة." }, "comingSoon": { - "badge": "Coming Soon", - "titleStart": "شيء", - "titleAccent": "جديد", - "titleEnd": "في الطريق.", - "description": "الموقع حالياً في وضع الصيانة. عم أنهي المحتوى واللمسات الأخيرة قبل الإطلاق.", - "note": "شكراً على صبرك. قريباً ستنطلق نسخة أوضح وأسرع وأقوى." + "badge": "قريباً", + "titleStart": "تجربة", + "titleAccent": "أقوى", + "titleEnd": "قيد الإطلاق.", + "description": "نعمل حالياً على تحديث الموقع وتحسين التفاصيل البصرية والمحتوى لنقدّم تجربة أوضح، أسرع، وأكثر احترافية.", + "note": "شكراً لصبرك. النسخة الجديدة ستصل قريباً بهوية أنضج وحضور أدق." }, "homepage": { "heroKicker": "Digital Studio", diff --git a/messages/de.json b/messages/de.json index 7bf200d..35b9962 100644 --- a/messages/de.json +++ b/messages/de.json @@ -15,11 +15,11 @@ }, "comingSoon": { "badge": "Coming Soon", - "titleStart": "Etwas", - "titleAccent": "Neues", - "titleEnd": "ist auf dem Weg.", - "description": "Meine Website ist aktuell im Wartungsmodus. Ich finalisiere Inhalte und den letzten Feinschliff vor dem Launch.", - "note": "Danke fuer deine Geduld. Bald geht eine klarere, schnellere und staerkere Version live." + "titleStart": "Ein praeziserer", + "titleAccent": "digitaler Auftritt", + "titleEnd": "entsteht gerade.", + "description": "Die Website wird aktuell in Inhalt, Gestaltung und Gesamtwirkung verfeinert, damit der Launch klarer, hochwertiger und stimmiger wird.", + "note": "Danke fuer deine Geduld. Die naechste Version geht bald mit mehr Klarheit, Tempo und Charakter live." }, "homepage": { "heroKicker": "Digital Studio", diff --git a/messages/en.json b/messages/en.json index 50d9ec6..1462253 100644 --- a/messages/en.json +++ b/messages/en.json @@ -15,11 +15,11 @@ }, "comingSoon": { "badge": "Coming Soon", - "titleStart": "Something", - "titleAccent": "new", - "titleEnd": "is on the way.", - "description": "My website is currently in maintenance mode. I am finalizing content and polish before launch.", - "note": "Thank you for your patience. A cleaner, faster, and stronger version is launching soon." + "titleStart": "A sharper", + "titleAccent": "digital presence", + "titleEnd": "is taking shape.", + "description": "The site is being refined with stronger storytelling, cleaner visuals, and a more polished experience before launch.", + "note": "Thank you for your patience. The next version is arriving soon with more clarity, speed, and intent." }, "homepage": { "heroKicker": "Digital Studio",