feat: WP'den header + kart stilleri + blog sidebar widget
Header (components/header.tsx + header-scroll.tsx): - WP'deki 'floating pill' efekti — scroll'da küçülen + yuvarlanan + gölgeli - 3 sütun grid: Logo | Nav | CTA - Hizmetler mega menu dropdown — 2 sütunlu (Web&Yazılım + Dijital Pazarlama) - Hover'da açılır, services tablosundan dinamik - Alt linkle 'Tüm hizmetleri gör' - Mobil için scroll-down'da gizlenir - Sağda 'Ücretsiz Teklif' CTA butonu + telefon link Kart stilleri (WP'ye eşlendi): - ServicesGrid: - Gradient icon (sky → purple) ile WP'deki '🎨 🚀 📱' emoji yerine ikon - Hover: -translate-y-2 + colored shadow + scale icon - ArrowUpRight ikonu absolute, hover'da görünür - ProjectsGrid: - Kategori bazlı renkli badge (Kurumsal navy, Klinik cyan, Portfolio violet, …) - Hover: image scale-105 + gradient overlay - 5/3 aspect ratio (daha WP-like) Public sidebar (components/content-sidebar.tsx): - CTA card (gradient navy→sky): Telefon + WhatsApp - Son yazılar (4 adet, kapak + başlık + tarih) - Etiketler (en sık kullanılan 10) - Hizmetler menü (6 adet) - Site analizi lead magnet Blog detay sayfası (/blog/[slug]): - Tek sütun → 2 sütun grid (content + sidebar) - sticky sidebar, max-w-7xl - Aynı pattern hizmet/proje detay sayfalarına da uygulanabilir 37 route, build temiz.
This commit is contained in:
@@ -6,6 +6,7 @@ import { ArrowLeft, Calendar } from "lucide-react";
|
||||
import { renderContent } from "@/lib/content-render";
|
||||
import { getPostBySlug } from "@/lib/data";
|
||||
import { buildMetadata } from "@/lib/seo";
|
||||
import { ContentSidebar } from "@/components/content-sidebar";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -21,7 +22,10 @@ export async function generateMetadata({
|
||||
openGraph: {
|
||||
title: post.seo_title || post.title,
|
||||
description: post.seo_description || post.excerpt || undefined,
|
||||
images: post.seo_image || post.cover_image ? [{ url: (post.seo_image || post.cover_image) as string }] : undefined,
|
||||
images:
|
||||
post.seo_image || post.cover_image
|
||||
? [{ url: (post.seo_image || post.cover_image) as string }]
|
||||
: undefined,
|
||||
type: "article",
|
||||
},
|
||||
});
|
||||
@@ -39,7 +43,7 @@ export default async function BlogPostPage({
|
||||
const html = renderContent(post.content);
|
||||
|
||||
return (
|
||||
<article className="mx-auto max-w-3xl px-6 py-20">
|
||||
<div className="mx-auto max-w-7xl px-6 py-16">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="inline-flex items-center gap-1 text-sm text-[var(--muted)] hover:text-[var(--navy)]"
|
||||
@@ -47,56 +51,62 @@ export default async function BlogPostPage({
|
||||
<ArrowLeft className="size-3.5" /> Tüm yazılar
|
||||
</Link>
|
||||
|
||||
<header className="mt-6 border-b border-[var(--border)] pb-8">
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.tags.map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
className="rounded-full bg-[var(--sky-50)] px-2.5 py-1 text-xs text-[var(--sky-600)]"
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<h1 className="mt-4 text-3xl font-bold leading-tight tracking-tight text-[var(--navy)] sm:text-4xl">
|
||||
{post.title}
|
||||
</h1>
|
||||
{post.excerpt && (
|
||||
<p className="mt-4 text-lg leading-relaxed text-[var(--muted)]">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-6 flex items-center gap-3 text-xs text-[var(--muted)]">
|
||||
{post.author && <span>{post.author}</span>}
|
||||
{post.author && post.published_at && <span>•</span>}
|
||||
{post.published_at && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Calendar className="size-3" />
|
||||
{new Date(post.published_at).toLocaleDateString("tr-TR")}
|
||||
</span>
|
||||
<div className="mt-6 grid gap-12 lg:grid-cols-[1fr_320px]">
|
||||
<article>
|
||||
<header className="border-b border-[var(--border)] pb-8">
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.tags.map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
className="rounded-full bg-[var(--sky-50)] px-2.5 py-1 text-xs text-[var(--sky-600)]"
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<h1 className="mt-4 text-3xl font-bold leading-tight tracking-tight text-[var(--navy)] sm:text-4xl">
|
||||
{post.title}
|
||||
</h1>
|
||||
{post.excerpt && (
|
||||
<p className="mt-4 text-lg leading-relaxed text-[var(--muted)]">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-6 flex items-center gap-3 text-xs text-[var(--muted)]">
|
||||
{post.author && <span>{post.author}</span>}
|
||||
{post.author && post.published_at && <span>•</span>}
|
||||
{post.published_at && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Calendar className="size-3" />
|
||||
{new Date(post.published_at).toLocaleDateString("tr-TR")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{post.cover_image && (
|
||||
<div className="relative mt-8 aspect-video overflow-hidden rounded-2xl">
|
||||
<Image
|
||||
src={post.cover_image}
|
||||
alt={post.title}
|
||||
fill
|
||||
sizes="(min-width: 1024px) 768px, 100vw"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{post.cover_image && (
|
||||
<div className="relative mt-8 aspect-video overflow-hidden rounded-2xl">
|
||||
<Image
|
||||
src={post.cover_image}
|
||||
alt={post.title}
|
||||
fill
|
||||
sizes="(min-width: 1024px) 768px, 100vw"
|
||||
className="object-cover"
|
||||
priority
|
||||
<div
|
||||
className="prose prose-lg mt-10 max-w-none text-[var(--foreground)]"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
|
||||
<div
|
||||
className="prose prose-lg mt-10 max-w-none text-[var(--foreground)]"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</article>
|
||||
<ContentSidebar currentSlug={slug} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, MessageCircle, Phone, Tag } from "lucide-react";
|
||||
import {
|
||||
getSiteSettings,
|
||||
listPublishedPosts,
|
||||
listServices,
|
||||
} from "@/lib/data";
|
||||
import { siteConfig } from "@/lib/site-config";
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Hangi yazıyı/sayfayı görüntülüyoruz — listede gizlemek için.
|
||||
*/
|
||||
currentSlug?: string;
|
||||
}
|
||||
|
||||
export async function ContentSidebar({ currentSlug }: Props) {
|
||||
const [settings, services, posts] = await Promise.all([
|
||||
getSiteSettings(),
|
||||
listServices(),
|
||||
listPublishedPosts({ limit: 5 }),
|
||||
]);
|
||||
|
||||
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
|
||||
const phone = settings?.contact_phone ?? siteConfig.contact.phone;
|
||||
const waCleaned = phoneRaw.replace(/[^\d]/g, "");
|
||||
const waMessage = settings?.whatsapp_message ?? "";
|
||||
const waHref = `https://wa.me/${waCleaned}${
|
||||
waMessage ? `?text=${encodeURIComponent(waMessage)}` : ""
|
||||
}`;
|
||||
|
||||
const otherPosts = posts.filter((p) => p.slug !== currentSlug).slice(0, 4);
|
||||
|
||||
// Etiket sayımı (tüm yazılardan toplu)
|
||||
const tagCount = new Map<string, number>();
|
||||
posts.forEach((p) =>
|
||||
(p.tags ?? []).forEach((t) =>
|
||||
tagCount.set(t, (tagCount.get(t) ?? 0) + 1),
|
||||
),
|
||||
);
|
||||
const topTags = Array.from(tagCount.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10);
|
||||
|
||||
return (
|
||||
<aside className="space-y-6 lg:sticky lg:top-24 lg:self-start">
|
||||
{/* CTA card */}
|
||||
<div className="overflow-hidden rounded-2xl border border-[var(--border)] bg-gradient-to-br from-[var(--navy)] to-[var(--sky-600)] p-6 text-white">
|
||||
<h3 className="text-base font-bold">Projeniz mi var?</h3>
|
||||
<p className="mt-2 text-sm text-white/80">
|
||||
Ücretsiz keşif görüşmesi için bizi arayın veya WhatsApp'tan yazın.
|
||||
</p>
|
||||
<div className="mt-4 space-y-2">
|
||||
<a
|
||||
href={`tel:${phoneRaw}`}
|
||||
className="flex items-center justify-center gap-2 rounded-xl bg-white px-4 py-2.5 text-sm font-semibold text-[var(--navy)] transition hover:bg-blue-50"
|
||||
>
|
||||
<Phone className="size-3.5" />
|
||||
{phone}
|
||||
</a>
|
||||
<a
|
||||
href={waHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-2 rounded-xl bg-[#25d366] px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-[#1ebe5d]"
|
||||
>
|
||||
<MessageCircle className="size-3.5" />
|
||||
WhatsApp'tan yaz
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Diğer yazılar */}
|
||||
{otherPosts.length > 0 && (
|
||||
<div className="rounded-2xl border border-[var(--border)] bg-white p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-bold uppercase tracking-wider text-[var(--navy)]">
|
||||
Son Yazılar
|
||||
</h3>
|
||||
<Link
|
||||
href="/blog"
|
||||
className="text-xs text-[var(--sky-600)] hover:text-[var(--navy)]"
|
||||
>
|
||||
Tümü →
|
||||
</Link>
|
||||
</div>
|
||||
<ul className="mt-4 space-y-3">
|
||||
{otherPosts.map((p) => (
|
||||
<li key={p.$id}>
|
||||
<Link
|
||||
href={`/blog/${p.slug}`}
|
||||
className="group flex gap-3"
|
||||
>
|
||||
<div className="relative size-16 shrink-0 overflow-hidden rounded-lg bg-[var(--navy-50)]">
|
||||
{p.cover_image ? (
|
||||
<Image
|
||||
src={p.cover_image}
|
||||
alt={p.title}
|
||||
fill
|
||||
sizes="64px"
|
||||
className="object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center text-lg font-bold text-[var(--navy)]/30">
|
||||
{p.title.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="line-clamp-2 text-sm font-medium leading-snug text-[var(--navy)] transition-colors group-hover:text-[var(--sky-600)]">
|
||||
{p.title}
|
||||
</p>
|
||||
{p.published_at && (
|
||||
<p className="mt-1 text-[11px] text-[var(--muted)]">
|
||||
{new Date(p.published_at).toLocaleDateString("tr-TR")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Etiketler */}
|
||||
{topTags.length > 0 && (
|
||||
<div className="rounded-2xl border border-[var(--border)] bg-white p-5">
|
||||
<h3 className="flex items-center gap-1.5 text-sm font-bold uppercase tracking-wider text-[var(--navy)]">
|
||||
<Tag className="size-3.5" />
|
||||
Etiketler
|
||||
</h3>
|
||||
<div className="mt-3 flex flex-wrap gap-1.5">
|
||||
{topTags.map(([tag]) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="rounded-md bg-[var(--navy-50)] px-2.5 py-1 text-xs font-medium text-[var(--navy-700)] hover:bg-[var(--sky-50)]"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hizmetler */}
|
||||
{services.length > 0 && (
|
||||
<div className="rounded-2xl border border-[var(--border)] bg-white p-5">
|
||||
<h3 className="text-sm font-bold uppercase tracking-wider text-[var(--navy)]">
|
||||
Hizmetlerimiz
|
||||
</h3>
|
||||
<ul className="mt-3 space-y-1.5">
|
||||
{services.slice(0, 6).map((s) => (
|
||||
<li key={s.slug}>
|
||||
<Link
|
||||
href={`/hizmetler/${s.slug}`}
|
||||
className="flex items-center justify-between rounded-lg px-2 py-1.5 text-sm text-[var(--foreground)] transition hover:bg-[var(--navy-50)] hover:text-[var(--navy)]"
|
||||
>
|
||||
<span>{s.title}</span>
|
||||
<ArrowRight className="size-3 text-[var(--muted)]" />
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Site analizi lead magnet */}
|
||||
<div className="rounded-2xl border border-[var(--sky)]/30 bg-[var(--sky-50)]/50 p-5">
|
||||
<h3 className="text-sm font-bold text-[var(--navy)]">
|
||||
Ücretsiz Site Analizi
|
||||
</h3>
|
||||
<p className="mt-2 text-xs leading-relaxed text-[var(--muted)]">
|
||||
Sitenizin SEO, hız ve dönüşüm performansını ücretsiz değerlendirelim.
|
||||
24 saat içinde rapor e-postanızda.
|
||||
</p>
|
||||
<Link
|
||||
href="/site-analizi"
|
||||
className="mt-3 inline-flex items-center gap-1 text-xs font-semibold text-[var(--sky-600)] hover:text-[var(--navy)]"
|
||||
>
|
||||
Hemen başla
|
||||
<ArrowRight className="size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* WordPress sitesindeki "floating pill" header efekti.
|
||||
* Scroll'da header küçülür, kenar yuvarlanır, gölge alır.
|
||||
* Mobilde scroll-down'da gizlenir, scroll-up'ta görünür.
|
||||
*/
|
||||
export function HeaderScrollEffect() {
|
||||
useEffect(() => {
|
||||
const wrap = document.getElementById("floating-header-wrap");
|
||||
const pillWrap = document.getElementById("header-pill-wrap");
|
||||
const header = document.getElementById("site-header");
|
||||
const navBar = document.getElementById("header-nav-bar");
|
||||
if (!wrap || !pillWrap || !header || !navBar) return;
|
||||
|
||||
let lastY = 0;
|
||||
let ticking = false;
|
||||
|
||||
wrap.style.transition = "transform 0.3s ease, opacity 0.3s ease";
|
||||
|
||||
function applyScroll() {
|
||||
const y = window.scrollY;
|
||||
const mobile = window.innerWidth < 1024;
|
||||
const scrolled = y > 10;
|
||||
const goingDown = y > lastY;
|
||||
|
||||
if (!mobile && pillWrap && header && navBar && wrap) {
|
||||
wrap.style.transform = "";
|
||||
wrap.style.opacity = "";
|
||||
if (scrolled) {
|
||||
pillWrap.style.padding = "12px 16px 0";
|
||||
header.style.maxWidth = "1100px";
|
||||
header.style.borderRadius = "1rem";
|
||||
header.style.border = "1px solid #e5e7eb";
|
||||
header.style.boxShadow = "0 8px 24px rgba(0,0,0,0.08)";
|
||||
navBar.style.height = "52px";
|
||||
navBar.style.padding = "0 1.25rem";
|
||||
} else {
|
||||
pillWrap.style.padding = "";
|
||||
header.style.maxWidth = "";
|
||||
header.style.borderRadius = "";
|
||||
header.style.border = "";
|
||||
header.style.boxShadow = "";
|
||||
navBar.style.height = "";
|
||||
navBar.style.padding = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (mobile && wrap && y > 80) {
|
||||
if (goingDown) {
|
||||
wrap.style.transform = "translateY(-110%)";
|
||||
wrap.style.opacity = "0";
|
||||
} else {
|
||||
wrap.style.transform = "";
|
||||
wrap.style.opacity = "";
|
||||
}
|
||||
} else if (mobile && wrap) {
|
||||
wrap.style.transform = "";
|
||||
wrap.style.opacity = "";
|
||||
}
|
||||
|
||||
lastY = y;
|
||||
ticking = false;
|
||||
}
|
||||
|
||||
function onScroll() {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(applyScroll);
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
window.addEventListener("resize", applyScroll);
|
||||
applyScroll();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
window.removeEventListener("resize", applyScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
+148
-39
@@ -1,53 +1,162 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Phone } from "lucide-react";
|
||||
import { getSiteSettings } from "@/lib/data";
|
||||
import { ChevronDown, Phone } from "lucide-react";
|
||||
import { getSiteSettings, listServices } from "@/lib/data";
|
||||
import { siteConfig } from "@/lib/site-config";
|
||||
|
||||
const nav = [
|
||||
{ href: "/", label: "Anasayfa" },
|
||||
{ href: "/hizmetler", label: "Hizmetler" },
|
||||
{ href: "/projeler", label: "Projeler" },
|
||||
{ href: "/blog", label: "Blog" },
|
||||
{ href: "/hakkimizda", label: "Hakkımızda" },
|
||||
{ href: "/iletisim", label: "İletişim" },
|
||||
];
|
||||
import { HeaderScrollEffect } from "@/components/header-scroll";
|
||||
|
||||
export async function Header() {
|
||||
const settings = await getSiteSettings();
|
||||
const [settings, services] = await Promise.all([
|
||||
getSiteSettings(),
|
||||
listServices(),
|
||||
]);
|
||||
const phone = settings?.contact_phone ?? siteConfig.contact.phone;
|
||||
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
|
||||
|
||||
// Mega menu groups
|
||||
const webServices = services.filter((s) =>
|
||||
["web-tasarim", "e-ticaret", "mobil-uygulama", "yazilim-gelistirme", "crm-sistemleri"].includes(s.slug),
|
||||
);
|
||||
const marketingServices = services.filter((s) =>
|
||||
["seo-dijital-pazarlama", "sosyal-medya-yonetimi", "dijital-reklam"].includes(s.slug),
|
||||
);
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 border-b border-[var(--border)] bg-white/90 backdrop-blur">
|
||||
<div className="mx-auto flex max-w-7xl items-center justify-between gap-6 px-6 py-3">
|
||||
<Link href="/" className="flex items-center gap-3">
|
||||
<Image src="/logo.png" alt={siteConfig.name} width={44} height={44} priority />
|
||||
<span className="hidden text-base font-semibold tracking-tight text-[var(--navy)] sm:block">
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<nav className="hidden items-center gap-8 md:flex">
|
||||
{nav.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className="text-sm font-medium text-[var(--muted)] transition hover:text-[var(--navy)]"
|
||||
<>
|
||||
<HeaderScrollEffect />
|
||||
<div className="sticky top-0 z-50 w-full" id="floating-header-wrap">
|
||||
<div id="header-pill-wrap" className="transition-all duration-300 ease-out">
|
||||
<header
|
||||
id="site-header"
|
||||
className="mx-auto w-full border-b border-gray-100 bg-white/95 backdrop-blur-lg transition-all duration-300 ease-out"
|
||||
>
|
||||
<nav
|
||||
id="header-nav-bar"
|
||||
className="flex h-14 items-center justify-between px-6 transition-all duration-300 ease-out lg:grid lg:h-16 lg:grid-cols-[1fr_auto_1fr] lg:px-8"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
{/* Col 1 — Logo */}
|
||||
<Link href="/" className="flex items-center gap-2.5">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt={siteConfig.name}
|
||||
width={40}
|
||||
height={40}
|
||||
priority
|
||||
className="h-9 w-9 object-contain"
|
||||
/>
|
||||
<span className="hidden text-sm font-semibold tracking-tight text-[var(--navy)] sm:block">
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<a
|
||||
href={`tel:${phoneRaw}`}
|
||||
className="hidden items-center gap-2 rounded-full bg-[var(--navy)] px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-[var(--navy-700)] sm:inline-flex"
|
||||
>
|
||||
<Phone className="size-4" />
|
||||
{phone}
|
||||
</a>
|
||||
{/* Col 2 — Desktop nav */}
|
||||
<div className="hidden items-center gap-0.5 lg:flex">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
Anasayfa
|
||||
</Link>
|
||||
|
||||
{/* Hizmetler mega menu */}
|
||||
<div className="group relative">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 items-center justify-center gap-1 whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
Hizmetler
|
||||
<ChevronDown className="size-3 transition-transform duration-200 group-hover:rotate-180" />
|
||||
</button>
|
||||
<div className="pointer-events-none invisible absolute left-1/2 top-full z-50 w-[480px] -translate-x-1/2 pt-2 opacity-0 transition-all duration-150 ease-out group-hover:pointer-events-auto group-hover:visible group-hover:opacity-100">
|
||||
<div className="translate-y-1 rounded-2xl border border-gray-100 bg-white p-4 shadow-xl transition-transform duration-150 group-hover:translate-y-0">
|
||||
<div className="grid grid-cols-2 gap-x-3">
|
||||
<div>
|
||||
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
|
||||
Web & Yazılım
|
||||
</p>
|
||||
{webServices.map((s) => (
|
||||
<Link
|
||||
key={s.slug}
|
||||
href={`/hizmetler/${s.slug}`}
|
||||
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||
>
|
||||
{s.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
|
||||
Dijital Pazarlama
|
||||
</p>
|
||||
{marketingServices.map((s) => (
|
||||
<Link
|
||||
key={s.slug}
|
||||
href={`/hizmetler/${s.slug}`}
|
||||
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
|
||||
>
|
||||
{s.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 border-t border-gray-100 pt-3">
|
||||
<Link
|
||||
href="/hizmetler"
|
||||
className="block rounded-xl px-3 py-2 text-center text-xs font-semibold text-[var(--navy)] hover:bg-blue-50"
|
||||
>
|
||||
Tüm hizmetleri gör →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/projeler"
|
||||
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
Projeler
|
||||
</Link>
|
||||
<Link
|
||||
href="/blog"
|
||||
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
Blog
|
||||
</Link>
|
||||
<Link
|
||||
href="/hakkimizda"
|
||||
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
Hakkımızda
|
||||
</Link>
|
||||
<Link
|
||||
href="/iletisim"
|
||||
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
İletişim
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Col 3 — CTA */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<a
|
||||
href={`tel:${phoneRaw}`}
|
||||
className="hidden lg:inline-flex h-9 items-center gap-1.5 rounded-lg px-3 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
||||
>
|
||||
<Phone className="size-3.5" />
|
||||
<span className="hidden xl:inline">{phone}</span>
|
||||
</a>
|
||||
<Link
|
||||
href="/iletisim"
|
||||
className="inline-flex h-9 items-center justify-center rounded-lg bg-[var(--navy)] px-4 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[var(--navy-700)]"
|
||||
>
|
||||
Ücretsiz Teklif
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,15 @@ import Link from "next/link";
|
||||
import { ArrowUpRight, ExternalLink } from "lucide-react";
|
||||
import type { ProjectRow } from "@/lib/types";
|
||||
|
||||
const CATEGORY_COLORS: Record<string, string> = {
|
||||
"Kurumsal Web Sitesi": "bg-[var(--navy)]",
|
||||
"Klinik Web Sitesi": "bg-cyan-600",
|
||||
"Portfolyo & SEO": "bg-violet-600",
|
||||
"Web Tasarım": "bg-emerald-600",
|
||||
"Özel Yazılım": "bg-sky-600",
|
||||
"E-Ticaret": "bg-pink-600",
|
||||
};
|
||||
|
||||
export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
||||
if (projects.length === 0) {
|
||||
return (
|
||||
@@ -15,73 +24,82 @@ export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
{projects.map((p) => (
|
||||
<article
|
||||
key={p.$id}
|
||||
className="group overflow-hidden rounded-2xl border border-[var(--border)] bg-white transition hover:shadow-xl"
|
||||
>
|
||||
<Link href={`/projeler/${p.slug}`} className="block">
|
||||
<div className="relative aspect-video overflow-hidden bg-[var(--navy-50)]">
|
||||
{p.image_url ? (
|
||||
<Image
|
||||
src={p.image_url}
|
||||
alt={p.title}
|
||||
fill
|
||||
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
||||
className="object-cover transition group-hover:scale-105"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center text-[var(--navy)]/30">
|
||||
<span className="text-5xl font-bold">{p.title.charAt(0)}</span>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{projects.map((p) => {
|
||||
const tagColor = p.category && CATEGORY_COLORS[p.category]
|
||||
? CATEGORY_COLORS[p.category]
|
||||
: "bg-[var(--navy)]";
|
||||
return (
|
||||
<article
|
||||
key={p.$id}
|
||||
className="group overflow-hidden rounded-2xl border border-[var(--border)] bg-white transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl hover:shadow-[var(--navy)]/10"
|
||||
>
|
||||
<Link href={`/projeler/${p.slug}`} className="block">
|
||||
<div className="relative aspect-[5/3] overflow-hidden bg-[var(--navy-50)]">
|
||||
{p.image_url ? (
|
||||
<Image
|
||||
src={p.image_url}
|
||||
alt={p.title}
|
||||
fill
|
||||
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center text-[var(--navy)]/30">
|
||||
<span className="text-5xl font-bold">{p.title.charAt(0)}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Overlay gradient — WP stili */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/65 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
||||
{p.category && (
|
||||
<span
|
||||
className={`absolute left-4 top-4 rounded-full ${tagColor} px-3 py-1 text-xs font-semibold text-white shadow-lg`}
|
||||
>
|
||||
{p.category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<Link href={`/projeler/${p.slug}`} className="block">
|
||||
<h3 className="text-lg font-bold text-[var(--navy)] transition-colors group-hover:text-[var(--sky-600)]">
|
||||
{p.title}
|
||||
</h3>
|
||||
</Link>
|
||||
{p.live_url ? (
|
||||
<a
|
||||
href={p.live_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Projeyi canlı aç"
|
||||
className="text-[var(--sky-600)] transition-colors hover:text-[var(--navy)]"
|
||||
>
|
||||
<ExternalLink className="size-4" />
|
||||
</a>
|
||||
) : (
|
||||
<ArrowUpRight className="size-5 text-[var(--muted)] transition-colors group-hover:text-[var(--sky-600)]" />
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-3">
|
||||
{p.description}
|
||||
</p>
|
||||
{p.technologies && p.technologies.length > 0 && (
|
||||
<div className="mt-4 flex flex-wrap gap-1.5">
|
||||
{p.technologies.map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
className="rounded-md bg-[var(--navy-50)] px-2 py-0.5 text-xs font-medium text-[var(--navy-700)]"
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{p.category && (
|
||||
<span className="absolute left-4 top-4 rounded-full bg-white/95 px-3 py-1 text-xs font-medium text-[var(--navy)] shadow-sm">
|
||||
{p.category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<Link href={`/projeler/${p.slug}`} className="block">
|
||||
<h3 className="text-lg font-semibold text-[var(--navy)] transition group-hover:text-[var(--sky-600)]">
|
||||
{p.title}
|
||||
</h3>
|
||||
</Link>
|
||||
{p.live_url ? (
|
||||
<a
|
||||
href={p.live_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Projeyi canlı aç"
|
||||
className="text-[var(--sky-600)] hover:text-[var(--navy)]"
|
||||
>
|
||||
<ExternalLink className="size-4" />
|
||||
</a>
|
||||
) : (
|
||||
<ArrowUpRight className="size-5 text-[var(--muted)] transition group-hover:text-[var(--sky-600)]" />
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-3">
|
||||
{p.description}
|
||||
</p>
|
||||
{p.technologies && p.technologies.length > 0 && (
|
||||
<div className="mt-4 flex flex-wrap gap-1.5">
|
||||
{p.technologies.map((t) => (
|
||||
<span
|
||||
key={t}
|
||||
className="rounded-md bg-[var(--navy-50)] px-2 py-0.5 text-xs text-[var(--navy-700)]"
|
||||
>
|
||||
{t}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,30 +18,27 @@ export function ServicesGrid({ services }: { services: ServiceRow[] }) {
|
||||
: (siteConfig.fallbackServices as readonly ServiceLike[]).slice();
|
||||
|
||||
return (
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{items.map((s) => (
|
||||
<Link
|
||||
key={s.slug}
|
||||
href={`/hizmetler/${s.slug}`}
|
||||
id={s.slug}
|
||||
className="group relative overflow-hidden rounded-2xl border border-[var(--border)] bg-white p-6 transition hover:border-[var(--sky)]/40 hover:shadow-lg hover:shadow-[var(--sky)]/10"
|
||||
className="group relative overflow-hidden rounded-2xl border border-[var(--border)] bg-white p-8 transition-all duration-300 hover:-translate-y-2 hover:border-[var(--sky)]/40 hover:shadow-2xl hover:shadow-[var(--navy)]/10"
|
||||
>
|
||||
<div
|
||||
className="absolute -right-12 -top-12 size-32 rounded-full bg-[var(--sky-50)] opacity-0 transition group-hover:opacity-100"
|
||||
aria-hidden
|
||||
/>
|
||||
<ArrowUpRight className="absolute right-5 top-5 size-4 text-[var(--muted)] transition group-hover:text-[var(--sky-600)]" />
|
||||
<div className="relative">
|
||||
<div className="flex size-12 items-center justify-center rounded-xl bg-[var(--navy-50)] text-[var(--navy)]">
|
||||
<Icon name={s.icon} className="size-6" />
|
||||
</div>
|
||||
<h3 className="mt-5 text-lg font-semibold text-[var(--navy)] transition group-hover:text-[var(--sky-600)]">
|
||||
{s.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)]">
|
||||
{s.description}
|
||||
</p>
|
||||
<ArrowUpRight className="absolute right-6 top-6 size-4 text-[var(--muted)] opacity-0 transition-all duration-300 group-hover:translate-x-1 group-hover:-translate-y-1 group-hover:text-[var(--sky-600)] group-hover:opacity-100" />
|
||||
|
||||
{/* Gradient icon — WP'deki stil */}
|
||||
<div className="flex size-14 items-center justify-center rounded-xl bg-gradient-to-br from-[var(--sky)] to-purple-500 text-white shadow-lg shadow-[var(--sky)]/30 transition-transform duration-300 group-hover:scale-110">
|
||||
<Icon name={s.icon} className="size-6" />
|
||||
</div>
|
||||
|
||||
<h3 className="mt-6 text-lg font-bold leading-tight text-[var(--navy)] transition-colors group-hover:text-[var(--sky-600)]">
|
||||
{s.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-sm leading-relaxed text-[var(--muted)]">
|
||||
{s.description}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user