feat: implement footer and brand logo components with internationalization support

This commit is contained in:
Yolando
2026-04-03 19:39:43 +07:00
parent 43c3bf45d1
commit 0f50a60084
9 changed files with 66 additions and 20 deletions

View File

@@ -163,6 +163,6 @@
"quickLinksTitle": "Quick Links", "quickLinksTitle": "Quick Links",
"connectTitle": "Connect", "connectTitle": "Connect",
"backToTop": "Back to top", "backToTop": "Back to top",
"copyright": "Yolando. All Rights Reserved." "copyright": "Simanullang Dev. All Rights Reserved."
} }
} }

View File

@@ -163,6 +163,6 @@
"quickLinksTitle": "Tautan Cepat", "quickLinksTitle": "Tautan Cepat",
"connectTitle": "Terhubung", "connectTitle": "Terhubung",
"backToTop": "Kembali ke atas", "backToTop": "Kembali ke atas",
"copyright": "Yolando. Semua Hak Cipta Dilindungi." "copyright": "Simanullang Dev. Semua Hak Cipta Dilindungi."
} }
} }

1
public/brand/.gitkeep Normal file
View File

@@ -0,0 +1 @@

BIN
public/brand/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -0,0 +1,53 @@
"use client";
import Image from "next/image";
import { useState } from "react";
type BrandLogoProps = {
href?: string;
className?: string;
iconClassName?: string;
textClassName?: string;
iconSize?: number;
priority?: boolean;
};
export function BrandLogo({
href = "#",
className = "",
iconClassName = "",
textClassName = "font-mono font-bold text-lg tracking-tight",
iconSize = 36,
priority = false,
}: BrandLogoProps) {
const [imageFailed, setImageFailed] = useState(false);
return (
<a href={href} className={`flex items-center gap-2 group ${className}`.trim()}>
<div
className={`relative overflow-hidden rounded-lg bg-gradient-to-br from-accent to-purple-500 text-white shadow-lg shadow-accent/25 ${iconClassName}`.trim()}
style={{ width: iconSize, height: iconSize }}
>
{imageFailed ? (
<div className="flex h-full w-full items-center justify-center font-bold text-sm">
S
</div>
) : (
<Image
src="/brand/icon.png"
alt="Simanullang Dev logo"
fill
sizes={`${iconSize}px`}
className="object-cover"
priority={priority}
onError={() => setImageFailed(true)}
/>
)}
</div>
<span className={textClassName}>
simanullang<span className="text-accent">.dev</span>
</span>
</a>
);
}

View File

@@ -1,5 +1,6 @@
import { GitFork, Link2, Mail, ArrowUp, Heart, MapPin } from "lucide-react"; import { GitFork, Link2, Mail, ArrowUp, Heart, MapPin } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { BrandLogo } from "@/shared/components/brand-logo";
const quickLinks = [ const quickLinks = [
{ href: "#experience", label: "Experience" }, { href: "#experience", label: "Experience" },
@@ -21,13 +22,8 @@ export function Footer() {
<div className="grid grid-cols-1 md:grid-cols-3 gap-10"> <div className="grid grid-cols-1 md:grid-cols-3 gap-10">
{/* Brand & tagline */} {/* Brand & tagline */}
<div className="md:col-span-1"> <div className="md:col-span-1">
<div className="flex items-center gap-2 mb-4"> <div className="mb-4">
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-accent to-purple-500 flex items-center justify-center text-white font-bold text-sm shadow-lg shadow-accent/25"> <BrandLogo />
A
</div>
<span className="font-mono font-bold text-lg tracking-tight">
ando<span className="text-accent">.dev</span>
</span>
</div> </div>
<p className="text-sm text-muted-foreground leading-relaxed mb-4 max-w-xs"> <p className="text-sm text-muted-foreground leading-relaxed mb-4 max-w-xs">
{t("description")} {t("description")}

View File

@@ -4,6 +4,7 @@ import { useState, useEffect, useTransition } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { Menu, X, Languages } from "lucide-react"; import { Menu, X, Languages } from "lucide-react";
import { ThemeToggle } from "@/shared/components/theme-toggle"; import { ThemeToggle } from "@/shared/components/theme-toggle";
import { BrandLogo } from "@/shared/components/brand-logo";
import { useTranslations, useLocale } from "next-intl"; import { useTranslations, useLocale } from "next-intl";
import { usePathname, useRouter } from "@/i18n/routing"; import { usePathname, useRouter } from "@/i18n/routing";
@@ -72,15 +73,10 @@ export function Navbar() {
}`} }`}
> >
<nav className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between"> <nav className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
{/* #12 Logo with hover glow */} <BrandLogo
<a href="#" className="flex items-center gap-2 group"> priority
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-accent to-purple-500 flex items-center justify-center text-white font-bold text-sm shadow-lg shadow-accent/25 group-hover:shadow-accent/50 group-hover:scale-110 transition-all duration-300"> iconClassName="group-hover:shadow-accent/50 group-hover:scale-110 transition-all duration-300"
A />
</div>
<span className="font-mono font-bold text-lg tracking-tight">
ando<span className="text-accent">.dev</span>
</span>
</a>
{/* Desktop Nav with scroll spy */} {/* Desktop Nav with scroll spy */}
<div className="hidden md:flex items-center gap-1"> <div className="hidden md:flex items-center gap-1">