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 { renderContent } from "@/lib/content-render";
|
||||||
import { getPostBySlug } from "@/lib/data";
|
import { getPostBySlug } from "@/lib/data";
|
||||||
import { buildMetadata } from "@/lib/seo";
|
import { buildMetadata } from "@/lib/seo";
|
||||||
|
import { ContentSidebar } from "@/components/content-sidebar";
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
@@ -21,7 +22,10 @@ export async function generateMetadata({
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
title: post.seo_title || post.title,
|
title: post.seo_title || post.title,
|
||||||
description: post.seo_description || post.excerpt || undefined,
|
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",
|
type: "article",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -39,7 +43,7 @@ export default async function BlogPostPage({
|
|||||||
const html = renderContent(post.content);
|
const html = renderContent(post.content);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className="mx-auto max-w-3xl px-6 py-20">
|
<div className="mx-auto max-w-7xl px-6 py-16">
|
||||||
<Link
|
<Link
|
||||||
href="/blog"
|
href="/blog"
|
||||||
className="inline-flex items-center gap-1 text-sm text-[var(--muted)] hover:text-[var(--navy)]"
|
className="inline-flex items-center gap-1 text-sm text-[var(--muted)] hover:text-[var(--navy)]"
|
||||||
@@ -47,7 +51,9 @@ export default async function BlogPostPage({
|
|||||||
<ArrowLeft className="size-3.5" /> Tüm yazılar
|
<ArrowLeft className="size-3.5" /> Tüm yazılar
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<header className="mt-6 border-b border-[var(--border)] pb-8">
|
<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 && (
|
{post.tags && post.tags.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{post.tags.map((t) => (
|
{post.tags.map((t) => (
|
||||||
@@ -98,5 +104,9 @@ export default async function BlogPostPage({
|
|||||||
dangerouslySetInnerHTML={{ __html: html }}
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
/>
|
/>
|
||||||
</article>
|
</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;
|
||||||
|
}
|
||||||
+136
-27
@@ -1,53 +1,162 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Phone } from "lucide-react";
|
import { ChevronDown, Phone } from "lucide-react";
|
||||||
import { getSiteSettings } from "@/lib/data";
|
import { getSiteSettings, listServices } from "@/lib/data";
|
||||||
import { siteConfig } from "@/lib/site-config";
|
import { siteConfig } from "@/lib/site-config";
|
||||||
|
import { HeaderScrollEffect } from "@/components/header-scroll";
|
||||||
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" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function Header() {
|
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 phone = settings?.contact_phone ?? siteConfig.contact.phone;
|
||||||
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
|
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 (
|
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">
|
<HeaderScrollEffect />
|
||||||
<Link href="/" className="flex items-center gap-3">
|
<div className="sticky top-0 z-50 w-full" id="floating-header-wrap">
|
||||||
<Image src="/logo.png" alt={siteConfig.name} width={44} height={44} priority />
|
<div id="header-pill-wrap" className="transition-all duration-300 ease-out">
|
||||||
<span className="hidden text-base font-semibold tracking-tight text-[var(--navy)] sm:block">
|
<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"
|
||||||
|
>
|
||||||
|
{/* 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}
|
{siteConfig.name}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<nav className="hidden items-center gap-8 md:flex">
|
{/* Col 2 — Desktop nav */}
|
||||||
{nav.map((item) => (
|
<div className="hidden items-center gap-0.5 lg:flex">
|
||||||
<Link
|
<Link
|
||||||
key={item.href}
|
href="/"
|
||||||
href={item.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"
|
||||||
className="text-sm font-medium text-[var(--muted)] transition hover:text-[var(--navy)]"
|
|
||||||
>
|
>
|
||||||
{item.label}
|
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>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</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
|
<a
|
||||||
href={`tel:${phoneRaw}`}
|
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"
|
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-4" />
|
<Phone className="size-3.5" />
|
||||||
{phone}
|
<span className="hidden xl:inline">{phone}</span>
|
||||||
</a>
|
</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>
|
</div>
|
||||||
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,15 @@ import Link from "next/link";
|
|||||||
import { ArrowUpRight, ExternalLink } from "lucide-react";
|
import { ArrowUpRight, ExternalLink } from "lucide-react";
|
||||||
import type { ProjectRow } from "@/lib/types";
|
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[] }) {
|
export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
||||||
if (projects.length === 0) {
|
if (projects.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -15,29 +24,37 @@ export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{projects.map((p) => (
|
{projects.map((p) => {
|
||||||
|
const tagColor = p.category && CATEGORY_COLORS[p.category]
|
||||||
|
? CATEGORY_COLORS[p.category]
|
||||||
|
: "bg-[var(--navy)]";
|
||||||
|
return (
|
||||||
<article
|
<article
|
||||||
key={p.$id}
|
key={p.$id}
|
||||||
className="group overflow-hidden rounded-2xl border border-[var(--border)] bg-white transition hover:shadow-xl"
|
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">
|
<Link href={`/projeler/${p.slug}`} className="block">
|
||||||
<div className="relative aspect-video overflow-hidden bg-[var(--navy-50)]">
|
<div className="relative aspect-[5/3] overflow-hidden bg-[var(--navy-50)]">
|
||||||
{p.image_url ? (
|
{p.image_url ? (
|
||||||
<Image
|
<Image
|
||||||
src={p.image_url}
|
src={p.image_url}
|
||||||
alt={p.title}
|
alt={p.title}
|
||||||
fill
|
fill
|
||||||
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
||||||
className="object-cover transition group-hover:scale-105"
|
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full items-center justify-center text-[var(--navy)]/30">
|
<div className="flex h-full items-center justify-center text-[var(--navy)]/30">
|
||||||
<span className="text-5xl font-bold">{p.title.charAt(0)}</span>
|
<span className="text-5xl font-bold">{p.title.charAt(0)}</span>
|
||||||
</div>
|
</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 && (
|
{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">
|
<span
|
||||||
|
className={`absolute left-4 top-4 rounded-full ${tagColor} px-3 py-1 text-xs font-semibold text-white shadow-lg`}
|
||||||
|
>
|
||||||
{p.category}
|
{p.category}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -46,7 +63,7 @@ export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
|||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<Link href={`/projeler/${p.slug}`} className="block">
|
<Link href={`/projeler/${p.slug}`} className="block">
|
||||||
<h3 className="text-lg font-semibold text-[var(--navy)] transition group-hover:text-[var(--sky-600)]">
|
<h3 className="text-lg font-bold text-[var(--navy)] transition-colors group-hover:text-[var(--sky-600)]">
|
||||||
{p.title}
|
{p.title}
|
||||||
</h3>
|
</h3>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -56,12 +73,12 @@ export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
aria-label="Projeyi canlı aç"
|
aria-label="Projeyi canlı aç"
|
||||||
className="text-[var(--sky-600)] hover:text-[var(--navy)]"
|
className="text-[var(--sky-600)] transition-colors hover:text-[var(--navy)]"
|
||||||
>
|
>
|
||||||
<ExternalLink className="size-4" />
|
<ExternalLink className="size-4" />
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<ArrowUpRight className="size-5 text-[var(--muted)] transition group-hover:text-[var(--sky-600)]" />
|
<ArrowUpRight className="size-5 text-[var(--muted)] transition-colors group-hover:text-[var(--sky-600)]" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-3">
|
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-3">
|
||||||
@@ -72,7 +89,7 @@ export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
|||||||
{p.technologies.map((t) => (
|
{p.technologies.map((t) => (
|
||||||
<span
|
<span
|
||||||
key={t}
|
key={t}
|
||||||
className="rounded-md bg-[var(--navy-50)] px-2 py-0.5 text-xs text-[var(--navy-700)]"
|
className="rounded-md bg-[var(--navy-50)] px-2 py-0.5 text-xs font-medium text-[var(--navy-700)]"
|
||||||
>
|
>
|
||||||
{t}
|
{t}
|
||||||
</span>
|
</span>
|
||||||
@@ -81,7 +98,8 @@ export function ProjectsGrid({ projects }: { projects: ProjectRow[] }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,30 +18,27 @@ export function ServicesGrid({ services }: { services: ServiceRow[] }) {
|
|||||||
: (siteConfig.fallbackServices as readonly ServiceLike[]).slice();
|
: (siteConfig.fallbackServices as readonly ServiceLike[]).slice();
|
||||||
|
|
||||||
return (
|
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) => (
|
{items.map((s) => (
|
||||||
<Link
|
<Link
|
||||||
key={s.slug}
|
key={s.slug}
|
||||||
href={`/hizmetler/${s.slug}`}
|
href={`/hizmetler/${s.slug}`}
|
||||||
id={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
|
<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" />
|
||||||
className="absolute -right-12 -top-12 size-32 rounded-full bg-[var(--sky-50)] opacity-0 transition group-hover:opacity-100"
|
|
||||||
aria-hidden
|
{/* 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">
|
||||||
<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" />
|
<Icon name={s.icon} className="size-6" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="mt-5 text-lg font-semibold text-[var(--navy)] transition group-hover:text-[var(--sky-600)]">
|
|
||||||
|
<h3 className="mt-6 text-lg font-bold leading-tight text-[var(--navy)] transition-colors group-hover:text-[var(--sky-600)]">
|
||||||
{s.title}
|
{s.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)]">
|
<p className="mt-3 text-sm leading-relaxed text-[var(--muted)]">
|
||||||
{s.description}
|
{s.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user