diff --git a/app/[locale]/coming-soon/page.tsx b/app/[locale]/coming-soon/page.tsx
index 6289328..86741a1 100644
--- a/app/[locale]/coming-soon/page.tsx
+++ b/app/[locale]/coming-soon/page.tsx
@@ -1,15 +1,20 @@
import type { Metadata } from "next";
import { unstable_noStore as noStore } from "next/cache";
import Link from "next/link";
-import { ArrowLeft, ArrowRight, Mail, Sparkles } from "lucide-react";
+import { ArrowLeft, ArrowRight, Mail } from "lucide-react";
import { getLocale, getTranslations } from "next-intl/server";
import { FloatingPreferences } from "@/components/layout/floating-preferences";
import { HeroContentMotion, HeroMotionItem, HeroShell, HeroTitle } from "@/components/layout/site-hero";
+import { LaunchCountdown } from "@/components/site/launch-countdown";
import { Button } from "@/components/ui/button";
import { getSiteSettings } from "@/lib/app-config";
import { buildLocalizedMetadata } from "@/lib/metadata";
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
+import { cn } from "@/lib/utils";
+
+// Target launch date for the countdown. Edit this single line to change it.
+const LAUNCH_DATE_ISO = "2026-08-28T12:00:00Z";
type ComingSoonPageProps = {
params: Promise<{
@@ -36,9 +41,7 @@ export async function generateMetadata({ params }: ComingSoonPageProps): Promise
});
}
-export default async function ComingSoonPage({
- params,
-}: ComingSoonPageProps) {
+export default async function ComingSoonPage({ params }: ComingSoonPageProps) {
noStore();
await params;
const siteSettings = await getSiteSettings();
@@ -46,60 +49,111 @@ export default async function ComingSoonPage({
const t = await getTranslations({ locale: localeKey, namespace: "comingSoon" });
const isArabic = localeKey === "ar";
const DirectionIcon = isArabic ? ArrowLeft : ArrowRight;
+
const lines = [
{
text: t("titleLineOne"),
tone: "soft" as const,
- align: isArabic ? "center" as const : "start" as const,
+ align: isArabic ? ("center" as const) : ("start" as const),
},
{
text: t("titleLineTwo"),
tone: "default" as const,
- align: isArabic ? "center" as const : "end" as const,
+ align: isArabic ? ("center" as const) : ("end" as const),
},
{
text: t("titleLineThree"),
tone: "accent" as const,
- align: isArabic ? "center" as const : "start" as const,
+ align: isArabic ? ("center" as const) : ("start" as const),
},
];
+ const trackedEyebrow = isArabic ? "tracking-normal" : "uppercase tracking-[0.28em]";
+ const trackedLabel = isArabic ? "tracking-normal" : "uppercase tracking-[0.24em]";
+ const trackedPill = isArabic ? "tracking-normal" : "uppercase tracking-[0.12em]";
+
return (
-
-
-
-
- {t("kicker")}
-
-
+
+
+ {/* top hairline highlight */}
+
+ {/* soft brand glow */}
+
-
+
+
+
+
+
+
+
+ {t("status")}
+
+
-
-
- {t("description")}
-
-
+
+
+ {t("kicker")}
+
+
-
-
-
-
+
+
+
+
+ {t("description")}
+
+
+
+
+
+ {t("countdownLabel")}
+
+
+
+
+
+
+
+
+
+
diff --git a/components/site/launch-countdown.tsx b/components/site/launch-countdown.tsx
new file mode 100644
index 0000000..036253c
--- /dev/null
+++ b/components/site/launch-countdown.tsx
@@ -0,0 +1,131 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+import { cn } from "@/lib/utils";
+
+type CountdownLabels = {
+ days: string;
+ hours: string;
+ minutes: string;
+ seconds: string;
+};
+
+type LaunchCountdownProps = {
+ /** Target launch date as an ISO string. */
+ targetIso: string;
+ labels: CountdownLabels;
+ /** Shown once the target date has passed. */
+ launchedLabel: string;
+ /** Arabic script: drop uppercase/letter-spacing on labels. */
+ arabic?: boolean;
+ className?: string;
+};
+
+type Remaining = {
+ days: number;
+ hours: number;
+ minutes: number;
+ seconds: number;
+ done: boolean;
+};
+
+function computeRemaining(target: number): Remaining {
+ const diff = target - Date.now();
+
+ if (diff <= 0) {
+ return { days: 0, hours: 0, minutes: 0, seconds: 0, done: true };
+ }
+
+ const totalSeconds = Math.floor(diff / 1000);
+
+ return {
+ days: Math.floor(totalSeconds / 86400),
+ hours: Math.floor((totalSeconds % 86400) / 3600),
+ minutes: Math.floor((totalSeconds % 3600) / 60),
+ seconds: totalSeconds % 60,
+ done: false,
+ };
+}
+
+function pad(value: number): string {
+ return value.toString().padStart(2, "0");
+}
+
+export function LaunchCountdown({
+ targetIso,
+ labels,
+ launchedLabel,
+ arabic = false,
+ className,
+}: LaunchCountdownProps) {
+ // Start null so the server and first client render match (no hydration
+ // mismatch); the real value is filled in on mount and every second after.
+ const [remaining, setRemaining] = useState
(null);
+
+ useEffect(() => {
+ const target = new Date(targetIso).getTime();
+
+ if (Number.isNaN(target)) {
+ return;
+ }
+
+ setRemaining(computeRemaining(target));
+
+ const interval = window.setInterval(() => {
+ setRemaining(computeRemaining(target));
+ }, 1000);
+
+ return () => window.clearInterval(interval);
+ }, [targetIso]);
+
+ if (remaining?.done) {
+ return (
+
+ {launchedLabel}
+
+ );
+ }
+
+ const units = [
+ { key: "days", label: labels.days, value: remaining?.days },
+ { key: "hours", label: labels.hours, value: remaining?.hours },
+ { key: "minutes", label: labels.minutes, value: remaining?.minutes },
+ { key: "seconds", label: labels.seconds, value: remaining?.seconds },
+ ];
+
+ return (
+
+ {units.map((unit) => (
+
+
+
+
+ {unit.value === undefined ? "--" : pad(unit.value)}
+
+
+
+ {unit.label}
+
+
+ ))}
+
+ );
+}
diff --git a/messages/ar.json b/messages/ar.json
index aebf217..accce7e 100644
--- a/messages/ar.json
+++ b/messages/ar.json
@@ -28,7 +28,14 @@
"titleLineThree": "وسيعود قريباً",
"description": "أعيد بناء الموقع ليعرض الخدمات، الأعمال المختارة، وطريقة التعاون بشكل مباشر ومنظم.",
"primaryCta": "ابدأ مشروعاً",
- "secondaryCta": "العودة للرئيسية"
+ "secondaryCta": "العودة للرئيسية",
+ "status": "قيد التطوير · يعود قريباً",
+ "countdownLabel": "الإطلاق خلال",
+ "unitDays": "أيام",
+ "unitHours": "ساعات",
+ "unitMinutes": "دقائق",
+ "unitSeconds": "ثوانٍ",
+ "launched": "نُطلق قريباً جداً"
},
"homepage": {
"meta": {
diff --git a/messages/de.json b/messages/de.json
index 87eee30..0d3fed9 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -28,7 +28,14 @@
"titleLineThree": "bald wieder online",
"description": "Ich baue die Website neu auf, damit Leistungen, ausgewählte Arbeiten und Zusammenarbeit klarer, direkter und ohne Wiederholungen sichtbar werden.",
"primaryCta": "Projekt starten",
- "secondaryCta": "Zur Startseite"
+ "secondaryCta": "Zur Startseite",
+ "status": "In Entwicklung · bald zurück",
+ "countdownLabel": "Start in",
+ "unitDays": "Tage",
+ "unitHours": "Stunden",
+ "unitMinutes": "Minuten",
+ "unitSeconds": "Sekunden",
+ "launched": "Wir starten sehr bald"
},
"homepage": {
"meta": {
diff --git a/messages/en.json b/messages/en.json
index a650bc6..4f6ff79 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -28,7 +28,14 @@
"titleLineThree": "returning soon",
"description": "I am rebuilding the site to present services, selected work, and collaboration details with sharper structure and less noise.",
"primaryCta": "Start a project",
- "secondaryCta": "Back to homepage"
+ "secondaryCta": "Back to homepage",
+ "status": "In development · back soon",
+ "countdownLabel": "Launching in",
+ "unitDays": "Days",
+ "unitHours": "Hours",
+ "unitMinutes": "Minutes",
+ "unitSeconds": "Seconds",
+ "launched": "Launching very soon"
},
"homepage": {
"meta": {