diff --git a/components/layout/dock-app-icon.tsx b/components/layout/dock-app-icon.tsx
deleted file mode 100644
index 568558d..0000000
--- a/components/layout/dock-app-icon.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-"use client";
-
-import { useId } from "react";
-
-export type DockSymbol = "home" | "about" | "portfolio" | "products" | "contact";
-
-/**
- * macOS Big Sur–style squircle app icon, drawn entirely in SVG so it stays
- * crisp at every magnification step. Inactive icons use a neutral graphite
- * gradient; the active icon lights up in the brand coral.
- */
-export function DockAppIcon({
- symbol,
- active = false,
- disabled = false,
-}: {
- symbol: DockSymbol;
- active?: boolean;
- disabled?: boolean;
-}) {
- const uid = useId().replace(/[:]/g, "");
- const bg = `bg-${uid}`;
- const gloss = `gloss-${uid}`;
-
- return (
-
- );
-}
diff --git a/components/layout/floating-controls.tsx b/components/layout/floating-controls.tsx
new file mode 100644
index 0000000..5b69e97
--- /dev/null
+++ b/components/layout/floating-controls.tsx
@@ -0,0 +1,114 @@
+"use client";
+
+import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
+import { ShieldCheck, SlidersHorizontal, X } from "lucide-react";
+import { useTranslations } from "next-intl";
+import { useState, type ReactNode } from "react";
+
+import { LocaleToggle } from "@/components/layout/locale-toggle";
+import { SoundToggle } from "@/components/sound-toggle";
+import { ThemeToggle } from "@/components/theme-toggle";
+import { buildAdminUrl } from "@/lib/admin-routing";
+import type { AppLocale } from "@/lib/locale";
+import { cn } from "@/lib/utils";
+
+type FloatingControlsProps = {
+ locale: string;
+ defaultLocale: AppLocale;
+ isSuperAdmin?: boolean;
+};
+
+const pill =
+ "flex h-11 w-11 items-center justify-center rounded-full border border-border/60 bg-background/80 text-foreground/80 shadow-panel backdrop-blur-chrome transition-colors hover:bg-accent hover:text-foreground";
+
+const itemVariants = {
+ hidden: { opacity: 0, y: 12, scale: 0.8 },
+ show: { opacity: 1, y: 0, scale: 1 },
+};
+
+export function FloatingControls({
+ locale,
+ defaultLocale,
+ isSuperAdmin = false,
+}: FloatingControlsProps) {
+ const [open, setOpen] = useState(false);
+ const t = useTranslations("navigation");
+ const reducedMotion = useReducedMotion();
+
+ const controls: ReactNode[] = [
+
,
+
,
+
,
+ ];
+
+ if (isSuperAdmin) {
+ controls.push(
+
+
+ ,
+ );
+ }
+
+ return (
+
+
+ {open ? (
+
+ {controls.map((control, index) => (
+
+ {control}
+
+ ))}
+
+ ) : null}
+
+
+
+
+ );
+}
diff --git a/components/layout/site-dock.tsx b/components/layout/site-dock.tsx
index 5b0be07..aea5c41 100644
--- a/components/layout/site-dock.tsx
+++ b/components/layout/site-dock.tsx
@@ -1,25 +1,16 @@
"use client";
-import { ShieldCheck } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
-import { useTheme } from "next-themes";
import { useLocale, useTranslations } from "next-intl";
-import { useEffect, useState } from "react";
-import { DockAppIcon, type DockSymbol } from "@/components/layout/dock-app-icon";
-import { LocaleToggle } from "@/components/layout/locale-toggle";
-import { SoundToggle } from "@/components/sound-toggle";
-import { ThemeToggle } from "@/components/theme-toggle";
+import { FloatingControls } from "@/components/layout/floating-controls";
import { Dock, DockIcon } from "@/components/ui/dock";
-import { buildAdminUrl } from "@/lib/admin-routing";
import { getLocalizedPath, stripLocalePrefix, type AppLocale } from "@/lib/locale";
import { cn } from "@/lib/utils";
type SiteDockProps = {
- lightLogoUrl?: string | null;
- darkLogoUrl?: string | null;
defaultLocale: AppLocale;
/**
* Server-computed via `isSuperAdmin()` in the parent layout. This client
@@ -30,8 +21,10 @@ type SiteDockProps = {
isSuperAdmin?: boolean;
};
+type NavKey = "home" | "about" | "portfolio" | "products" | "contact";
+
type NavItem = {
- key: DockSymbol;
+ key: NavKey;
path: string;
disabled?: boolean;
};
@@ -51,134 +44,16 @@ function isNavItemActive(currentPath: string, itemPath: string) {
return currentPath === itemPath || currentPath.startsWith(`${itemPath}/`);
}
-function MenuClock({ locale }: { locale: string }) {
- const [time, setTime] = useState("");
-
- useEffect(() => {
- const format = () =>
- setTime(
- new Date().toLocaleTimeString(locale === "ar" ? "ar" : locale, {
- hour: "2-digit",
- minute: "2-digit",
- }),
- );
- format();
- const id = setInterval(format, 20_000);
- return () => clearInterval(id);
- }, [locale]);
-
- return (
-
- {time || "--:--"}
-
- );
-}
-
-export function SiteDock({
- lightLogoUrl,
- darkLogoUrl,
- defaultLocale,
- isSuperAdmin = false,
-}: SiteDockProps) {
+export function SiteDock({ defaultLocale, isSuperAdmin = false }: SiteDockProps) {
const locale = useLocale();
const pathname = usePathname();
const t = useTranslations("navigation");
- const { theme, resolvedTheme } = useTheme();
- const [mounted, setMounted] = useState(false);
- useEffect(() => {
- setMounted(true);
- }, []);
-
- const isArabic = locale === "ar";
const currentPath = stripLocalePrefix(pathname);
const homeHref = getLocalizedPath(locale, "/", defaultLocale);
- const adminHref = buildAdminUrl("/");
-
- const activeTheme = theme === "system" ? resolvedTheme : theme;
- const isDark = mounted && activeTheme === "dark";
- const logoSrc = isDark
- ? darkLogoUrl || lightLogoUrl || "/logos/dark-primary.svg"
- : lightLogoUrl || darkLogoUrl || "/logos/light-primary.svg";
-
- const brandSubtitle = isArabic
- ? "مطوّر ويب · فريلانسر"
- : "Webdesigner Bremen · Freelancer";
-
- const controlButtonClassName =
- "h-8 w-8 rounded-full border border-transparent text-foreground/70 hover:bg-accent hover:text-foreground";
return (
<>
- {/* ===== Top menu bar (macOS-style utility strip) ===== */}
-
-
-
-
-
- {isArabic ? (
- <>
- محمد{" "}
- فرواتي
- >
- ) : (
- <>
- MOH
- FARAWATI
- >
- )}
-
-
-
-
- {brandSubtitle}
-
-
-
-
-
-
-
- {isSuperAdmin ? (
-
-
- Admin
-
- ) : null}
-
-
-
-
{/* ===== Bottom dock (Magic UI) — primary navigation ===== */}
- {/* Logo — first icon in the dock */}
+ {/* Logo — first icon in the dock (fixed src, no theme swap → no flash) */}
@@ -220,8 +92,18 @@ export function SiteDock({
const itemPath = path || "/";
const isActive = isNavItemActive(currentPath, itemPath);
const label = t(key);
+ const src = `/dock/${key}${isActive ? "-active" : ""}.svg`;
- const icon = ;
+ const icon = (
+
+ );
return (
+
+ {/* ===== Corner controls — theme / language / sound / admin ===== */}
+
>
);
}
diff --git a/components/ui/dock.tsx b/components/ui/dock.tsx
index 0b5d53d..7dd36d6 100644
--- a/components/ui/dock.tsx
+++ b/components/ui/dock.tsx
@@ -107,7 +107,7 @@ const DockIcon = ({
...props
}: DockIconProps) => {
const ref = useRef
(null);
- const padding = Math.max(2, size * 0.05);
+ const padding = 0;
const defaultMouseX = useMotionValue(Infinity);
const distanceCalc = useTransform(mouseX ?? defaultMouseX, (val: number) => {
diff --git a/public/dock/about-active.svg b/public/dock/about-active.svg
new file mode 100644
index 0000000..8619f15
--- /dev/null
+++ b/public/dock/about-active.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/public/dock/about.svg b/public/dock/about.svg
new file mode 100644
index 0000000..d38b0be
--- /dev/null
+++ b/public/dock/about.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/public/dock/contact-active.svg b/public/dock/contact-active.svg
new file mode 100644
index 0000000..7018726
--- /dev/null
+++ b/public/dock/contact-active.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/public/dock/contact.svg b/public/dock/contact.svg
new file mode 100644
index 0000000..710dc97
--- /dev/null
+++ b/public/dock/contact.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/public/dock/home-active.svg b/public/dock/home-active.svg
new file mode 100644
index 0000000..d9eb81c
--- /dev/null
+++ b/public/dock/home-active.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/public/dock/home.svg b/public/dock/home.svg
new file mode 100644
index 0000000..09fb01f
--- /dev/null
+++ b/public/dock/home.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/public/dock/portfolio-active.svg b/public/dock/portfolio-active.svg
new file mode 100644
index 0000000..7e170fa
--- /dev/null
+++ b/public/dock/portfolio-active.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/public/dock/portfolio.svg b/public/dock/portfolio.svg
new file mode 100644
index 0000000..e289f12
--- /dev/null
+++ b/public/dock/portfolio.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/public/dock/products-active.svg b/public/dock/products-active.svg
new file mode 100644
index 0000000..6c4db6c
--- /dev/null
+++ b/public/dock/products-active.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/public/dock/products.svg b/public/dock/products.svg
new file mode 100644
index 0000000..e77e661
--- /dev/null
+++ b/public/dock/products.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file