diff --git a/components/layout/floating-controls.tsx b/components/layout/floating-controls.tsx deleted file mode 100644 index b3f05ec..0000000 --- a/components/layout/floating-controls.tsx +++ /dev/null @@ -1,149 +0,0 @@ -"use client"; - -import { AnimatePresence, motion, useReducedMotion } from "framer-motion"; -import { Plus, ShieldCheck } from "lucide-react"; -import { useLocale, 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/85 text-foreground/80 shadow-panel backdrop-blur-chrome transition-colors hover:bg-accent hover:text-foreground"; - -/** Radius (px) of the arc the items fan out along. */ -const RADIUS = 92; - -export function FloatingControls({ - locale, - defaultLocale, - isSuperAdmin = false, -}: FloatingControlsProps) { - const [open, setOpen] = useState(false); - const t = useTranslations("navigation"); - const activeLocale = useLocale(); - const isRtl = activeLocale === "ar"; - const reducedMotion = useReducedMotion(); - - const controls: ReactNode[] = [ - , - , - , - ]; - - if (isSuperAdmin) { - controls.push( - - - , - ); - } - - const count = controls.length; - // Fan the items across a quarter arc from "up" (90°) to "sideways" (180°), - // mirrored for RTL so they always spread away from the corner into the page. - const startAngle = 94; - const endAngle = 176; - const positionFor = (index: number) => { - const angle = count === 1 ? 135 : startAngle + ((endAngle - startAngle) * index) / (count - 1); - const radians = (angle * Math.PI) / 180; - return { - x: (isRtl ? -1 : 1) * Math.cos(radians) * RADIUS, - y: -Math.sin(radians) * RADIUS, - }; - }; - - return ( -
-
- - {open ? ( - <> - {/* click-away scrim */} - setOpen(false)} - initial={{ opacity: 0 }} - animate={{ opacity: 1 }} - exit={{ opacity: 0 }} - className="fixed inset-0 -z-10 cursor-default bg-transparent" - /> - {controls.map((control, index) => { - const { x, y } = positionFor(index); - return ( - - {control} - - ); - })} - - ) : null} - - - {/* trigger */} - -
-
- ); -} diff --git a/components/layout/site-dock.tsx b/components/layout/site-dock.tsx index aea5c41..3ee148b 100644 --- a/components/layout/site-dock.tsx +++ b/components/layout/site-dock.tsx @@ -1,12 +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 { useLocale, useTranslations } from "next-intl"; -import { FloatingControls } from "@/components/layout/floating-controls"; +import { LocaleToggle } from "@/components/layout/locale-toggle"; +import { SoundToggle } from "@/components/sound-toggle"; +import { ThemeToggle } from "@/components/theme-toggle"; 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"; @@ -37,6 +41,13 @@ const navItems: NavItem[] = [ { key: "contact", path: "/contact" }, ]; +// Graphite squircle that matches the SVG page icons, used for the live control +// tiles (theme / sound / language / admin) so the whole dock reads as one set. +const controlTile = + "group relative overflow-visible rounded-[26%] bg-gradient-to-b from-[#3f4551] to-[#191d24] shadow-[0_6px_14px_-6px_rgba(0,0,0,0.5)] ring-1 ring-white/10 transition-shadow duration-200 hover:shadow-[0_10px_22px_-8px_rgba(0,0,0,0.6)]"; +const controlButton = + "h-full w-full rounded-[inherit] border-0 bg-transparent text-white hover:bg-white/10 hover:text-white"; + function isNavItemActive(currentPath: string, itemPath: string) { if (itemPath === "/") { return currentPath === "/"; @@ -53,96 +64,148 @@ export function SiteDock({ defaultLocale, isSuperAdmin = false }: SiteDockProps) const homeHref = getLocalizedPath(locale, "/", defaultLocale); return ( - <> - {/* ===== Bottom dock (Magic UI) — primary navigation ===== */} -
+ - - {/* Logo — first icon in the dock (fixed src, no theme swap → no flash) */} - - + + mohfarawati + + + + + + + {/* ── Pages ── */} + {navItems.map(({ key, path, disabled }) => { + const itemPath = path || "/"; + const isActive = isNavItemActive(currentPath, itemPath); + const label = t(key); + const src = `/dock/${key}${isActive ? "-active" : ""}.svg`; + + const icon = ( + {label} + ); + + return ( + - mohfarawati - - + {disabled ? ( + + {icon} + + ) : ( + + {icon} + + )} + + + ); + })} + + + + {/* ── Settings (live controls) ── */} + + + + + + + + + + + + + + + + + + + {isSuperAdmin ? ( + + + + + + + ) : null} + +
+ ); +} -
+function DockSeparator() { + return
; +} - {navItems.map(({ key, path, disabled }) => { - const itemPath = path || "/"; - const isActive = isNavItemActive(currentPath, itemPath); - const label = t(key); - const src = `/dock/${key}${isActive ? "-active" : ""}.svg`; - - const icon = ( - {label} - ); - - return ( - - {disabled ? ( - - {icon} - - ) : ( - - {icon} - - )} - - - - ); - })} - -
- - {/* ===== Corner controls — theme / language / sound / admin ===== */} - - +function TileGloss() { + return ( + ); }