first commit

This commit is contained in:
mstfyldz
2026-05-30 18:23:21 +03:00
commit e927cd6af3
57 changed files with 12945 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
node_modules
# Keep environment variables out of version control
.env
/app/generated/prisma
# Next.js
.next
.env.local
.env.development.local
.env.test.local
.env.production.local
# Vercel
.vercel
# macOS
.DS_Store
+63
View File
@@ -0,0 +1,63 @@
# 1. Base image
FROM node:20-alpine AS base
# 2. Dependencies
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci --legacy-peer-deps
# 3. Builder
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Environment variables must be present at build time for Next.js
# Coolify will provide these, but we can set defaults
ENV NEXT_TELEMETRY_DISABLED=1
# Generate Prisma Client
RUN npx prisma generate
RUN npm run build
# 4. Runner
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Copy Prisma schema and engine to avoid missing binaries in standalone mode
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@prisma ./node_modules/@prisma
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
USER nextjs
EXPOSE 3000
ENV PORT=3000
# set hostname to localhost
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
+42
View File
@@ -0,0 +1,42 @@
import { getDemoBySlug, demos } from "@/data/demos";
import KlinikTemplate from "@/components/templates/KlinikTemplate";
import RestoranTemplate from "@/components/templates/RestoranTemplate";
import KurumsalTemplate from "@/components/templates/KurumsalTemplate";
import DentalTemplate from "@/components/templates/DentalTemplate";
import RestoranTemplate2 from "@/components/templates/RestoranTemplate2";
import { notFound } from "next/navigation";
import { i18n, Locale } from "@/i18n-config";
export async function generateStaticParams() {
const paths: Array<{ lang: Locale; slug: string }> = [];
const demoList = Object.keys(demos);
for (const lang of i18n.locales) {
for (const slug of demoList) {
paths.push({ lang, slug });
}
}
return paths;
}
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale; slug: string }> }) {
const { slug } = await params;
const data = getDemoBySlug(slug);
if (!data) return { title: "Demo Bulunamadı | Ayris Tech" };
return {
title: `${data.firma.adi} — Premium Arayüz Demosu`,
description: data.firma.slogan,
};
}
export default async function DemoPage({ params }: { params: Promise<{ lang: Locale; slug: string }> }) {
const { slug } = await params;
const data = getDemoBySlug(slug);
if (!data) notFound();
if (data.template === "klinik") return <KlinikTemplate data={data} />;
if (data.template === "restoran") return <RestoranTemplate data={data} />;
if (data.template === "kurumsal") return <KurumsalTemplate data={data} />;
if (data.template === "dental") return <DentalTemplate data={data} />;
if (data.template === "restoran2") return <RestoranTemplate2 data={data} />;
notFound();
}
+173
View File
@@ -0,0 +1,173 @@
"use client";
import * as React from "react";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { motion, AnimatePresence } from "framer-motion";
import { adminLoginAction } from "@/app/actions";
import Link from "next/link";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
interface Props {
params: Promise<{ lang: string }>;
}
export default function AdminLoginPage({ params }: Props) {
const router = useRouter();
// Unpack params Promise natively using React 19 use() hook
const { lang } = React.use(params);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!username.trim() || !password.trim()) {
setError("Kullanıcı adı ve şifre zorunludur!");
return;
}
setLoading(true);
setError(null);
const res = await adminLoginAction({ username: username.trim(), password: password.trim() });
if (res.success) {
router.push(`/${lang}/admin`);
} else {
setError(res.error || "Giriş başarısız!");
setLoading(false);
}
};
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen flex items-center justify-center p-6 relative overflow-hidden">
{/* Dynamic Background Pattern */}
<div
className="absolute inset-0 opacity-[0.03] pointer-events-none select-none"
style={{
backgroundImage: "radial-gradient(#000 1px, transparent 1px)",
backgroundSize: "20px 20px"
}}
/>
{/* Decorative Floating Block */}
<div className="absolute top-1/4 left-1/4 w-32 h-32 bg-[#FFE600] opacity-10 blur-2xl rounded-full pointer-events-none" />
<div className="absolute bottom-1/4 right-1/4 w-48 h-48 bg-[#FF4500] opacity-5 blur-3xl rounded-full pointer-events-none" />
<main className="relative z-10 w-full max-w-md">
{/* Back to Home Link */}
<div className="mb-6 flex justify-start">
<Link
href={`/${lang}`}
className="inline-flex items-center gap-2 font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E] hover:text-[#0A0A0A] transition-colors"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</svg>
ANA SAYFAYA DÖN
</Link>
</div>
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: expo }}
className="bg-[#F4F0E8] border-2 border-[#0A0A0A] p-10 relative shadow-[8px_8px_0px_0px_#0A0A0A]"
>
{/* Accent Ribbon */}
<div className="absolute top-0 left-0 right-0 h-2 bg-[#FFE600]" />
{/* Logo Monogram */}
<div className="w-16 h-16 border-2 border-[#0A0A0A] bg-[#FFE600] text-[#0A0A0A] font-display font-black text-xl flex items-center justify-center select-none mx-auto mb-8 shadow-[4px_4px_0px_0px_#0A0A0A]">
AYR
</div>
<div className="text-center mb-8">
<h1 className="font-display font-black text-3xl uppercase tracking-tight text-[#0A0A0A] mb-2 leading-none">
YÖNETİCİ GİRİŞİ
</h1>
<p className="font-mono text-[9px] uppercase tracking-[0.2em] text-[#A0998E]">
AYRİS TECH YÖNETİM MERKEZİ
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Error Message */}
<AnimatePresence mode="wait">
{error && (
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
className="bg-[#FFF0F0] border border-[#FF4500] text-[#FF4500] p-4 font-mono text-[10px] tracking-wide uppercase flex items-center gap-3"
>
<div className="w-2.5 h-2.5 bg-[#FF4500] shrink-0 animate-ping" />
<span className="flex-grow">{error}</span>
</motion.div>
)}
</AnimatePresence>
{/* Username Input */}
<div className="space-y-2">
<label className="block font-mono text-[9px] uppercase tracking-[0.2em] text-[#6A6460]">
KULLANICI ADI
</label>
<input
type="text"
required
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={loading}
className="w-full font-mono text-xs bg-[#EDE8E0] border border-[#C8C2B8] hover:border-[#0A0A0A] focus:border-[#0A0A0A] focus:bg-[#EDE8E0] outline-none px-4 py-3.5 text-[#0A0A0A] transition-all"
placeholder="Örn: admin"
/>
</div>
{/* Password Input */}
<div className="space-y-2">
<label className="block font-mono text-[9px] uppercase tracking-[0.2em] text-[#6A6460]">
ŞİFRE
</label>
<input
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
className="w-full font-mono text-xs bg-[#EDE8E0] border border-[#C8C2B8] hover:border-[#0A0A0A] focus:border-[#0A0A0A] focus:bg-[#EDE8E0] outline-none px-4 py-3.5 text-[#0A0A0A] transition-all"
placeholder="••••••••"
/>
</div>
{/* Submit Button */}
<button
type="submit"
disabled={loading}
className="w-full border-2 border-[#0A0A0A] bg-[#FFE600] hover:bg-[#FFE600] text-[#0A0A0A] py-4 font-display font-black text-xs tracking-[0.2em] uppercase transition-all shadow-[4px_4px_0px_0px_#0A0A0A] hover:shadow-[2px_2px_0px_0px_#0A0A0A] hover:translate-x-[2px] hover:translate-y-[2px] active:translate-x-[4px] active:translate-y-[4px] active:shadow-none cursor-pointer flex items-center justify-center gap-2"
>
{loading ? (
<>
<div className="w-3.5 h-3.5 border-2 border-[#0A0A0A] border-t-transparent rounded-full animate-spin shrink-0" />
BAĞLANIYOR...
</>
) : (
"SİSTEME GİRİŞ YAP"
)}
</button>
</form>
{/* Decorative Security Footer */}
<div className="mt-8 pt-6 border-t border-[#C8C2B8] flex justify-between items-center text-[#A0998E] font-mono text-[8px] tracking-widest uppercase">
<span>SECURE SHELL v2.4</span>
<span className="text-[#00CC77] font-bold"> ONLINE</span>
</div>
</motion.div>
</main>
</div>
);
}
+99
View File
@@ -0,0 +1,99 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import AdminClient from "@/components/AdminClient";
import { getProjects, getBlogPosts, getAdminSession, getPartners } from "@/app/actions";
import { redirect } from "next/navigation";
export async function generateStaticParams() {
return [{ lang: "tr" }, { lang: "en" }];
}
export async function generateMetadata() {
return {
title: "Yönetim Paneli | Ayris Tech",
description: "Ayris Tech sistem, proje ve site ayarları yönetim merkezi.",
};
}
export default async function AdminPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
// Guard dashboard route against unauthenticated users
const session = await getAdminSession();
if (!session) {
redirect(`/${lang}/admin/login`);
}
const dict = await getDictionary(lang);
const initialDbProjects = await getProjects();
const initialDbBlogPosts = await getBlogPosts();
const initialDbPartners = await getPartners();
// Convert DB format to expected BlogPost format
const initialBlogPosts = initialDbBlogPosts.map((dbPost) => ({
id: dbPost.id,
slug: dbPost.slug,
date: dbPost.date,
author: dbPost.author,
authorRole: dbPost.authorRole,
image: dbPost.image,
tr: {
title: dbPost.trTitle,
excerpt: dbPost.trExcerpt,
readingTime: dbPost.trReadingTime,
category: dbPost.trCategory,
tags: dbPost.trTags,
content: dbPost.trContent,
},
en: {
title: dbPost.enTitle,
excerpt: dbPost.enExcerpt,
readingTime: dbPost.enReadingTime,
category: dbPost.enCategory,
tags: dbPost.enTags,
content: dbPost.enContent,
}
}));
// Convert DB Project format to standard ProjectData format (mapping fields properly)
const initialProjects = initialDbProjects.map((p) => ({
id: p.id,
num: p.num,
slug: p.slug,
title: p.title,
tag: p.tag,
desc: p.desc,
spec: p.spec,
year: p.year,
client: p.client,
duration: p.duration,
tech: p.tech,
challenge: p.challenge,
solution: p.solution,
results: p.results,
image: p.image,
gallery: p.gallery,
website: p.website,
}));
// Map database Partners
const initialPartners = initialDbPartners.map((p) => ({
id: p.id,
name: p.name,
tag: p.tag,
mono: p.mono,
year: p.year,
desc: p.desc,
}));
return (
<AdminClient
lang={lang}
dict={dict}
initialProjects={initialProjects}
initialBlogPosts={initialBlogPosts}
initialPartners={initialPartners}
/>
);
}
+87
View File
@@ -0,0 +1,87 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import { getBlogPosts } from "@/app/actions";
import BlogDetailClient from "@/components/BlogDetailClient";
import { notFound } from "next/navigation";
// Convert DB format to expected BlogPost format
function mapBlogPost(dbPost: any) {
return {
id: dbPost.id,
slug: dbPost.slug,
date: dbPost.date,
author: dbPost.author,
authorRole: dbPost.authorRole,
image: dbPost.image,
tr: {
title: dbPost.trTitle,
excerpt: dbPost.trExcerpt,
readingTime: dbPost.trReadingTime,
category: dbPost.trCategory,
tags: dbPost.trTags,
content: dbPost.trContent,
},
en: {
title: dbPost.enTitle,
excerpt: dbPost.enExcerpt,
readingTime: dbPost.enReadingTime,
category: dbPost.enCategory,
tags: dbPost.enTags,
content: dbPost.enContent,
}
};
}
export async function generateStaticParams() {
const langs: Locale[] = ["en", "tr"];
const dbPosts = await getBlogPosts();
const slugs = dbPosts.length > 0
? dbPosts.map((p) => p.slug)
: [
"real-time-anomaly-detection-ai",
"web3-consortium-networks-logistics",
"headless-ecommerce-conversion-rates",
];
return langs.flatMap((lang) =>
slugs.map((slug) => ({ lang, slug }))
);
}
export async function generateMetadata({
params,
}: {
params: Promise<{ lang: Locale; slug: string }>;
}) {
const { lang, slug } = await params;
const dbPosts = await getBlogPosts();
const dbPost = dbPosts.find((p) => p.slug === slug);
if (!dbPost) return { title: "Not Found" };
const post = mapBlogPost(dbPost);
const content = post[lang] || post.en;
return {
title: `${content.title} | Ayris Tech`,
description: content.excerpt,
};
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{ lang: Locale; slug: string }>;
}) {
const { lang, slug } = await params;
const dict = await getDictionary(lang);
const dbPosts = await getBlogPosts();
const dbPost = dbPosts.find((p) => p.slug === slug);
if (!dbPost) notFound();
const post = mapBlogPost(dbPost);
return <BlogDetailClient lang={lang} dict={dict} post={post} />;
}
+47
View File
@@ -0,0 +1,47 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import BlogClient from "@/components/BlogClient";
import { getBlogPosts } from "@/app/actions";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.blog.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
description: dict.blog.desc,
};
}
export default async function BlogPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
const dbPosts = await getBlogPosts();
const initialPosts = dbPosts.map((dbPost) => ({
id: dbPost.id,
slug: dbPost.slug,
date: dbPost.date,
author: dbPost.author,
authorRole: dbPost.authorRole,
image: dbPost.image,
tr: {
title: dbPost.trTitle,
excerpt: dbPost.trExcerpt,
readingTime: dbPost.trReadingTime,
category: dbPost.trCategory,
tags: dbPost.trTags,
content: dbPost.trContent,
},
en: {
title: dbPost.enTitle,
excerpt: dbPost.enExcerpt,
readingTime: dbPost.enReadingTime,
category: dbPost.enCategory,
tags: dbPost.enTags,
content: dbPost.enContent,
}
}));
return <BlogClient lang={lang} dict={dict} initialPosts={initialPosts} />;
}
+19
View File
@@ -0,0 +1,19 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import ContactClient from "@/components/ContactClient";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.contact.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
description: dict.contact.desc,
};
}
export default async function ContactPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return <ContactClient lang={lang} dict={dict} />;
}
+19
View File
@@ -0,0 +1,19 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import FAQClient from "@/components/FAQClient";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.faq.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
description: dict.faq.desc,
};
}
export default async function FAQPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return <FAQClient lang={lang} dict={dict} />;
}
+49
View File
@@ -0,0 +1,49 @@
import type { Metadata } from "next";
import { Archivo, Space_Grotesk, IBM_Plex_Mono } from "next/font/google";
import "../globals.css";
import { Locale, i18n } from "@/i18n-config";
const archivo = Archivo({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700", "900"],
variable: "--font-archivo",
});
const spaceGrotesk = Space_Grotesk({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
variable: "--font-space-grotesk",
});
const ibmPlexMono = IBM_Plex_Mono({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
variable: "--font-ibm-plex-mono",
});
export const metadata: Metadata = {
title: "Ayris Tech | Forging the Future with AI & Blockchain",
description: "Ayris Tech delivers enterprise-grade digital transformation. We build the impossible for the next generation of business using AI, Blockchain, and modern web technologies.",
keywords: ["AI Solutions", "Blockchain Development", "Mobile App", "Web Development", "Digital Transformation"],
};
export async function generateStaticParams() {
return i18n.locales.map((locale) => ({ lang: locale }));
}
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ lang: string }>;
}) {
const { lang } = await params;
return (
<html lang={lang} className="scroll-smooth">
<body className={`${archivo.variable} ${spaceGrotesk.variable} ${ibmPlexMono.variable} font-body bg-[#F4F0E8] text-[#0A0A0A] antialiased`}>
{children}
</body>
</html>
);
}
+19
View File
@@ -0,0 +1,19 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import HomeClient from "@/components/HomeClient";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.nav.brandName}${dict.nav.brandSub} | ${dict.hero.badge}`,
description: dict.hero.desc,
};
}
export default async function Page({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return <HomeClient lang={lang} dict={dict} />;
}
+25
View File
@@ -0,0 +1,25 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import PartnersClient from "@/components/PartnersClient";
import { getPartners } from "@/app/actions";
export async function generateStaticParams() {
return [{ lang: "tr" }, { lang: "en" }];
}
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.partners.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
description: dict.partners.desc,
};
}
export default async function PartnersPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
const partners = await getPartners();
return <PartnersClient lang={lang} dict={dict} initialPartners={partners} />;
}
+19
View File
@@ -0,0 +1,19 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import ProcessClient from "@/components/ProcessClient";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.process.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
description: dict.process.desc,
};
}
export default async function ProcessPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return <ProcessClient lang={lang} dict={dict} />;
}
+19
View File
@@ -0,0 +1,19 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import ServicesClient from "@/components/ServicesClient";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.services.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
description: dict.services.desc,
};
}
export default async function ServicesPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return <ServicesClient lang={lang} dict={dict} />;
}
+73
View File
@@ -0,0 +1,73 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import WorkDetailClient from "@/components/WorkDetailClient";
import { notFound } from "next/navigation";
import { getProjects } from "@/app/actions";
// ── Static params for all slug × lang combinations ──
export async function generateStaticParams() {
const langs: Locale[] = ["en", "tr"];
const dbProjects = await getProjects();
const slugs = dbProjects.length > 0
? dbProjects.map((p) => p.slug)
: [
"financeai-dashboard",
"chainsupply-network",
"meditrack-mobile",
"novamart-platform",
];
return langs.flatMap((lang) =>
slugs.map((slug) => ({ lang, slug }))
);
}
export async function generateMetadata({
params,
}: {
params: Promise<{ lang: Locale; slug: string }>;
}) {
const { lang, slug } = await params;
const dict = await getDictionary(lang);
const dbProjects = await getProjects();
const items = dbProjects.length > 0 ? dbProjects : (dict.work.items as any[]);
const project = items.find((p: any) => p.slug === slug);
if (!project) return { title: "Not Found" };
return {
title: `${project.title}${dict.nav.brandName}${dict.nav.brandSub}`,
description: project.desc,
};
}
export default async function WorkDetailPage({
params,
}: {
params: Promise<{ lang: Locale; slug: string }>;
}) {
const { lang, slug } = await params;
const dict = await getDictionary(lang);
const dbProjects = await getProjects();
const items = dbProjects.length > 0 ? dbProjects : (dict.work.items as any[]);
const project = items.find((p: any) => p.slug === slug);
if (!project) notFound();
// Determine prev/next
const currentIdx = items.findIndex((p: any) => p.slug === slug);
const prev = currentIdx > 0 ? items[currentIdx - 1] : null;
const next = currentIdx < items.length - 1 ? items[currentIdx + 1] : null;
return (
<WorkDetailClient
lang={lang}
dict={dict}
project={project}
prev={prev}
next={next}
/>
);
}
+21
View File
@@ -0,0 +1,21 @@
import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config";
import WorkClient from "@/components/WorkClient";
import { getProjects } from "@/app/actions";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
return {
title: `${dict.work.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
description: dict.work.desc,
};
}
export default async function WorkPage({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params;
const dict = await getDictionary(lang);
const projects = await getProjects();
return <WorkClient lang={lang} dict={dict} initialProjects={projects} />;
}
+352
View File
@@ -0,0 +1,352 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
import { verifyPassword, signToken, verifyToken } from "@/lib/auth";
import { cookies } from "next/headers";
// ── PROJECTS ACTIONS ──
export async function getProjects() {
try {
return await prisma.project.findMany({
orderBy: { num: "asc" },
});
} catch (error) {
console.error("Error fetching projects:", error);
return [];
}
}
export async function saveProject(data: any) {
try {
const { id, num, slug, title, tag, desc, spec, year, client, duration, tech, challenge, solution, results, image, gallery, website } = data;
let project;
if (id) {
// Update
project = await prisma.project.update({
where: { id: Number(id) },
data: {
num,
slug,
title,
tag,
desc,
spec,
year,
client,
duration,
tech,
challenge,
solution,
results,
image: image || "",
gallery: gallery || [],
website: website || "",
},
});
} else {
// Create
// Ensure slug is unique
const existing = await prisma.project.findFirst({ where: { slug } });
if (existing) {
throw new Error("A project with this slug already exists.");
}
project = await prisma.project.create({
data: {
num,
slug,
title,
tag,
desc,
spec,
year,
client,
duration,
tech,
challenge,
solution,
results,
image: image || "",
gallery: gallery || [],
website: website || "",
},
});
}
revalidatePath("/[lang]/work", "layout");
revalidatePath("/[lang]/admin", "page");
return { success: true, project };
} catch (error: any) {
console.error("Error saving project:", error);
return { success: false, error: error.message };
}
}
export async function deleteProject(id: number) {
try {
await prisma.project.delete({
where: { id },
});
revalidatePath("/[lang]/work", "layout");
revalidatePath("/[lang]/admin", "page");
return { success: true };
} catch (error: any) {
console.error("Error deleting project:", error);
return { success: false, error: error.message };
}
}
// ── BLOG POSTS ACTIONS ──
export async function getBlogPosts() {
try {
const posts = await prisma.blogPost.findMany({
orderBy: { date: "desc" },
});
return posts;
} catch (error) {
console.error("Error fetching blog posts:", error);
return [];
}
}
export async function saveBlogPost(data: any) {
try {
const {
id,
slug,
date,
author,
authorRole,
image,
trTitle,
trExcerpt,
trReadingTime,
trCategory,
trTags,
trContent,
enTitle,
enExcerpt,
enReadingTime,
enCategory,
enTags,
enContent,
} = data;
let post;
if (id) {
// Update
post = await prisma.blogPost.update({
where: { id: Number(id) },
data: {
slug,
date,
author,
authorRole,
image,
trTitle,
trExcerpt,
trReadingTime,
trCategory,
trTags,
trContent,
enTitle,
enExcerpt,
enReadingTime,
enCategory,
enTags,
enContent,
},
});
} else {
// Create
const existing = await prisma.blogPost.findFirst({ where: { slug } });
if (existing) {
throw new Error("A blog post with this slug already exists.");
}
post = await prisma.blogPost.create({
data: {
slug,
date,
author,
authorRole,
image,
trTitle,
trExcerpt,
trReadingTime,
trCategory,
trTags,
trContent,
enTitle,
enExcerpt,
enReadingTime,
enCategory,
enTags,
enContent,
},
});
}
revalidatePath("/[lang]/blog", "layout");
revalidatePath("/[lang]/admin", "page");
return { success: true, post };
} catch (error: any) {
console.error("Error saving blog post:", error);
return { success: false, error: error.message };
}
}
export async function deleteBlogPost(id: number) {
try {
await prisma.blogPost.delete({
where: { id },
});
revalidatePath("/[lang]/blog", "layout");
revalidatePath("/[lang]/admin", "page");
return { success: true };
} catch (error: any) {
console.error("Error deleting blog post:", error);
return { success: false, error: error.message };
}
}
// ── PARTNERS ACTIONS ──
export async function getPartners() {
try {
return await prisma.partner.findMany({
orderBy: { id: "asc" },
});
} catch (error) {
console.error("Error fetching partners:", error);
return [];
}
}
export async function savePartner(data: any) {
try {
const { id, name, tag, mono, year, desc } = data;
let partner;
if (id) {
// Update
partner = await prisma.partner.update({
where: { id: Number(id) },
data: {
name,
tag,
mono,
year,
desc,
},
});
} else {
// Create
// Ensure name is unique
const existing = await prisma.partner.findFirst({ where: { name } });
if (existing) {
throw new Error("Bu isimde bir partner zaten mevcut.");
}
partner = await prisma.partner.create({
data: {
name,
tag,
mono,
year,
desc,
},
});
}
revalidatePath("/[lang]/partners", "layout");
revalidatePath("/[lang]/admin", "page");
return { success: true, partner };
} catch (error: any) {
console.error("Error saving partner:", error);
return { success: false, error: error.message };
}
}
export async function deletePartner(id: number) {
try {
await prisma.partner.delete({
where: { id },
});
revalidatePath("/[lang]/partners", "layout");
revalidatePath("/[lang]/admin", "page");
return { success: true };
} catch (error: any) {
console.error("Error deleting partner:", error);
return { success: false, error: error.message };
}
}
// ── AUTHENTICATION ACTIONS ──
const COOKIE_NAME = "ayris_session";
export async function getAdminSession() {
try {
const cookieStore = await cookies();
const token = cookieStore.get(COOKIE_NAME)?.value;
if (!token) return null;
return verifyToken(token);
} catch (error) {
console.error("Error reading admin session:", error);
return null;
}
}
export async function adminLoginAction(data: any) {
try {
const { username, password } = data;
if (!username || !password) {
return { success: false, error: "Kullanıcı adı ve şifre zorunludur!" };
}
const user = await prisma.user.findUnique({
where: { username },
});
if (!user) {
return { success: false, error: "Geçersiz kullanıcı adı veya şifre!" };
}
const isValid = verifyPassword(password, user.password);
if (!isValid) {
return { success: false, error: "Geçersiz kullanıcı adı veya şifre!" };
}
const token = signToken({ userId: user.id, username: user.username });
const cookieStore = await cookies();
cookieStore.set(COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 24 * 60 * 60, // 24 hours
path: "/",
});
return { success: true };
} catch (error: any) {
console.error("Login action error:", error);
return { success: false, error: "Giriş yapılırken beklenmedik bir hata oluştu!" };
}
}
export async function adminLogoutAction() {
try {
const cookieStore = await cookies();
cookieStore.delete(COOKIE_NAME);
return { success: true };
} catch (error: any) {
console.error("Logout action error:", error);
return { success: false, error: "Çıkış yapılırken bir hata oluştu!" };
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+145
View File
@@ -0,0 +1,145 @@
@import "tailwindcss";
@theme {
--font-display: var(--font-archivo), sans-serif;
--font-body: var(--font-space-grotesk), sans-serif;
--font-mono: var(--font-ibm-plex-mono), monospace;
}
/* ── RESET & BASE ── */
*, *::before, *::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 80px;
background: #F4F0E8;
}
body {
background: #F4F0E8;
color: #0A0A0A;
overflow-x: hidden;
}
::selection {
background-color: #FFE600;
color: #0A0A0A;
}
/* ── SCROLLBAR ── */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: #F4F0E8; }
::-webkit-scrollbar-thumb { background: #0A0A0A; border-radius: 0; }
::-webkit-scrollbar-thumb:hover { background: #FF4500; }
/* ── NOISE OVERLAY ── */
.noise-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 150px 150px;
}
/* ── STROKE TEXT ── */
.text-stroke {
-webkit-text-stroke: 2px #0A0A0A;
color: transparent;
}
.text-stroke-yellow {
-webkit-text-stroke: 2px #FFE600;
color: transparent;
}
/* ── MARQUEE ── */
.marquee-wrapper {
overflow: hidden;
white-space: nowrap;
display: flex;
}
.marquee-track {
display: flex;
animation: marqueeScroll 18s linear infinite;
}
.marquee-track-reverse {
display: flex;
animation: marqueeScrollReverse 22s linear infinite;
}
@keyframes marqueeScroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@keyframes marqueeScrollReverse {
from { transform: translateX(-50%); }
to { transform: translateX(0); }
}
/* ── DOT GRID ── */
.dot-grid {
background-image: radial-gradient(#C8C2B8 1px, transparent 1px);
background-size: 24px 24px;
}
/* ── YELLOW STRIKE ── */
.yellow-strike {
position: relative;
display: inline-block;
}
.yellow-strike::after {
content: '';
position: absolute;
bottom: 4px;
left: 0;
right: 0;
height: 6px;
background: #FFE600;
z-index: -1;
}
/* ── BRUTAL BUTTON ── */
.btn-brutal {
position: relative;
border: 2px solid #0A0A0A;
transition: transform 0.1s ease, box-shadow 0.1s ease;
background: transparent;
color: #0A0A0A;
}
.btn-brutal::after {
content: '';
position: absolute;
bottom: -5px;
right: -5px;
width: 100%;
height: 100%;
border: 2px solid #0A0A0A;
z-index: -1;
transition: all 0.15s ease;
}
.btn-brutal:hover {
transform: translate(-3px, -3px);
box-shadow: 6px 6px 0 #FFE600;
}
.btn-brutal:hover::after {
transform: translate(3px, 3px);
}
.btn-brutal:active {
transform: translate(0, 0);
box-shadow: none;
}
.btn-brutal-yellow {
background: #FFE600;
color: #0A0A0A;
border: 2px solid #0A0A0A;
}
.btn-brutal-yellow::after {
border-color: #0A0A0A;
}
.btn-brutal-yellow:hover {
box-shadow: 6px 6px 0 #FF4500;
}
File diff suppressed because it is too large Load Diff
+196
View File
@@ -0,0 +1,196 @@
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
import { blogPosts, BlogPost } from "@/data/blog";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
export default function BlogClient({
lang,
dict,
initialPosts,
}: {
lang: Locale;
dict: any;
initialPosts?: BlogPost[];
}) {
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const postsList = initialPosts && initialPosts.length > 0 ? initialPosts : blogPosts;
const categories = Array.from(
new Set(
postsList.map((post) => {
const content = post[lang] || post.en;
return (content.category as string).split(" · ")[0]; // Main category e.g. YZ, Web3, Web
})
)
);
const filteredPosts = postsList.filter((post) => {
const content = post[lang] || post.en;
const matchesCategory =
!selectedCategory || (content.category as string).startsWith(selectedCategory);
const matchesSearch =
(content.title as string).toLowerCase().includes(searchQuery.toLowerCase()) ||
(content.excerpt as string).toLowerCase().includes(searchQuery.toLowerCase()) ||
(content.tags as string[]).some((t: string) => t.toLowerCase().includes(searchQuery.toLowerCase()));
return matchesCategory && matchesSearch;
});
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-7xl mx-auto">
{/* Header Block */}
<div className="border-b border-[#C8C2B8] pb-12 mb-12">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{dict.blog.badge}
</span>
</div>
<motion.h1
className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: expo }}
>
{dict.blog.title}
</motion.h1>
<p className="text-[#6A6460] mt-6 text-lg max-w-2xl font-light">
{dict.blog.desc}
</p>
</div>
{/* Filter Controls & Search */}
<div className="flex flex-col md:flex-row gap-6 justify-between items-stretch md:items-center mb-12">
{/* Categories */}
<div className="flex flex-wrap gap-2">
<button
onClick={() => setSelectedCategory(null)}
className={`font-mono text-[10px] tracking-[0.15em] uppercase px-4 py-2 border transition-all cursor-pointer ${
selectedCategory === null
? "bg-[#0A0A0A] text-[#F4F0E8] border-[#0A0A0A]"
: "border-[#C8C2B8] text-[#6A6460] hover:border-[#0A0A0A] hover:text-[#0A0A0A]"
}`}
>
{lang === "tr" ? "HEPSİ" : "ALL"}
</button>
{categories.map((cat) => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
className={`font-mono text-[10px] tracking-[0.15em] uppercase px-4 py-2 border transition-all cursor-pointer ${
selectedCategory === cat
? "bg-[#0A0A0A] text-[#F4F0E8] border-[#0A0A0A]"
: "border-[#C8C2B8] text-[#6A6460] hover:border-[#0A0A0A] hover:text-[#0A0A0A]"
}`}
>
{cat}
</button>
))}
</div>
{/* Search Box */}
<div className="relative flex-grow md:max-w-xs">
<input
type="text"
placeholder={lang === "tr" ? "Arama yap..." : "Search..."}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full font-mono text-[11px] bg-[#EDE8E0] border border-[#C8C2B8] hover:border-[#0A0A0A] focus:border-[#0A0A0A] outline-none px-4 py-3 placeholder-[#A0998E] text-[#0A0A0A] transition-colors"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery("")}
className="absolute right-3 top-1/2 -translate-y-1/2 text-[10px] font-mono text-[#A0998E] hover:text-[#0A0A0A] cursor-pointer"
>
[X]
</button>
)}
</div>
</div>
{/* Grid List */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<AnimatePresence mode="popLayout">
{filteredPosts.map((post, idx) => {
const content = post[lang] || post.en;
const accent = ["#FFE600", "#FF4500", "#00CC77"][idx % 3] || "#FFE600";
return (
<motion.div
key={post.slug}
layout
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.4, ease: expo }}
className="group flex flex-col bg-[#F4F0E8] border border-[#C8C2B8] hover:border-[#0A0A0A] p-8 relative overflow-hidden transition-all duration-300 h-full"
whileHover={{ y: -6, backgroundColor: "#EDE8E0", boxShadow: "4px 4px 0px 0px #0A0A0A" }}
>
<div className="flex items-center gap-3 mb-6">
<span
className="font-mono text-[9px] tracking-[0.2em] uppercase px-2.5 py-1 border border-[#C8C2B8] text-[#6A6460]"
style={{ borderLeftColor: accent, borderLeftWidth: 3 }}
>
{content.category}
</span>
<span className="font-mono text-[9px] text-[#A0998E]">
{post.date}
</span>
</div>
<h3 className="font-display font-black text-xl uppercase text-[#0A0A0A] mb-4 leading-tight">
{content.title}
</h3>
<p className="text-[#6A6460] text-[13px] leading-relaxed mb-8 flex-grow">
{content.excerpt}
</p>
<div className="pt-6 border-t border-[#C8C2B8] flex items-center justify-between mt-auto">
<div className="flex flex-col">
<span className="font-display font-black text-[11px] uppercase text-[#0A0A0A]">{post.author}</span>
<span className="font-mono text-[9px] text-[#A0998E] uppercase tracking-wider">{post.authorRole}</span>
</div>
<Link
href={`/${lang}/blog/${post.slug}`}
className="w-10 h-10 border border-[#C8C2B8] group-hover:border-[#0A0A0A] group-hover:bg-[#0A0A0A] group-hover:text-[#FFE600] text-[#0A0A0A] flex items-center justify-center transition-all"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
</Link>
</div>
{/* Category Accent Stripe */}
<div className="absolute top-0 left-0 right-0 h-1 transition-colors" style={{ backgroundColor: accent }} />
</motion.div>
);
})}
</AnimatePresence>
{filteredPosts.length === 0 && (
<div className="col-span-full py-16 text-center border border-dashed border-[#C8C2B8]">
<p className="font-mono text-xs uppercase text-[#A0998E]">
{lang === "tr" ? "Sonuç bulunamadı." : "No entries match your parameters."}
</p>
</div>
)}
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+192
View File
@@ -0,0 +1,192 @@
"use client";
import { motion, useScroll, useSpring } from "framer-motion";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
import type { BlogPost } from "@/data/blog";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
export default function BlogDetailClient({
lang,
dict,
post,
}: {
lang: Locale;
dict: any;
post: BlogPost;
}) {
const { scrollYProgress } = useScroll();
const scaleX = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001,
});
const content = post[lang] || post.en;
const accent = "#FFE600"; // Signature yellow
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
{/* Scroll indicator */}
<motion.div
className="fixed top-0 left-0 right-0 h-1.5 bg-[#FFE600] z-50 origin-[0%]"
style={{ scaleX }}
/>
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-4xl mx-auto">
{/* Back Link */}
<motion.div
className="mb-8"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<Link
href={`/${lang}/blog`}
className="inline-flex items-center gap-2 font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E] hover:text-[#0A0A0A] transition-colors"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</svg>
{dict.blog.back}
</Link>
</motion.div>
{/* Article Metadata */}
<div className="flex items-center gap-3 mb-6">
<span
className="font-mono text-[9px] tracking-[0.2em] uppercase px-2.5 py-1 border border-[#C8C2B8] text-[#6A6460]"
style={{ borderLeftColor: accent, borderLeftWidth: 3 }}
>
{content.category}
</span>
<span className="font-mono text-[9px] text-[#A0998E]">
{post.date}
</span>
<span className="text-[#C8C2B8]"></span>
<span className="font-mono text-[9px] text-[#A0998E] uppercase tracking-wider">
{content.readingTime}
</span>
</div>
{/* Title */}
<motion.h1
className="font-display font-black text-4xl sm:text-5xl lg:text-6xl uppercase leading-[0.95] tracking-tight mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: expo }}
>
{content.title}
</motion.h1>
{/* Author Bio Row */}
<motion.div
className="flex items-center gap-4 py-6 border-y border-[#C8C2B8] mb-12"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
<div className="w-10 h-10 border border-[#C8C2B8] bg-[#FFE600] flex items-center justify-center font-display font-black text-xs text-[#0A0A0A]">
{post.author.charAt(0)}{post.author.split(" ")[1]?.charAt(0)}
</div>
<div>
<div className="font-display font-black text-[12px] uppercase text-[#0A0A0A]">
{post.author}
</div>
<div className="font-mono text-[9px] text-[#A0998E] uppercase tracking-wider">
{post.authorRole}
</div>
</div>
</motion.div>
{/* Featured Image */}
{post.image && (
<motion.div
className="border border-[#C8C2B8] p-2 bg-[#EDE8E0] mb-12 relative overflow-hidden group"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<img
src={post.image}
alt={content.title}
className="w-full h-auto object-cover grayscale contrast-125 group-hover:grayscale-0 transition-all duration-700"
/>
</motion.div>
)}
{/* Article Body Content */}
<motion.article
className="prose prose-stone max-w-none text-[#4A4440] leading-relaxed text-[15px] space-y-6"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
>
{/* Render paragraph chunks dynamically */}
{content.content.split("\n\n").map((paragraph, index) => {
if (paragraph.startsWith("## ")) {
return (
<h2 key={index} className="font-display font-black text-2xl uppercase text-[#0A0A0A] pt-6 mb-2">
{paragraph.replace("## ", "")}
</h2>
);
}
if (paragraph.startsWith("### ")) {
return (
<h3 key={index} className="font-display font-black text-lg uppercase text-[#0A0A0A] pt-4 mb-2">
{paragraph.replace("### ", "")}
</h3>
);
}
if (paragraph.startsWith("> ")) {
return (
<blockquote key={index} className="border-l-4 border-[#FFE600] pl-6 font-display font-bold uppercase text-[13px] text-[#0A0A0A] italic my-6">
{paragraph.replace("> ", "")}
</blockquote>
);
}
if (paragraph.startsWith("- ")) {
return (
<ul key={index} className="list-disc pl-6 space-y-2 font-mono text-xs text-[#6A6460]">
{paragraph.split("\n").map((li, idx) => (
<li key={idx}>{li.replace("- ", "")}</li>
))}
</ul>
);
}
if (paragraph.includes("```")) {
const codeLines = paragraph.split("\n").filter(l => !l.includes("```"));
const codeHeader = codeLines[0]?.startsWith("//") ? codeLines.shift() : "";
return (
<div key={index} className="font-mono text-xs bg-[#EDE8E0] p-5 border border-[#C8C2B8] my-6 overflow-x-auto text-[#0A0A0A]">
{codeHeader && <div className="text-[#A0998E] pb-3 border-b border-[#C8C2B8] mb-3">{codeHeader}</div>}
<pre className="whitespace-pre-wrap leading-relaxed">{codeLines.join("\n")}</pre>
</div>
);
}
return <p key={index}>{paragraph}</p>;
})}
</motion.article>
{/* Tags Block */}
<div className="flex flex-wrap gap-2 pt-12 mt-12 border-t border-[#C8C2B8]">
{content.tags.map((tag) => (
<span
key={tag}
className="font-mono text-[9px] tracking-wider uppercase px-2.5 py-1.5 border border-[#C8C2B8] text-[#6A6460] hover:border-[#0A0A0A] hover:text-[#0A0A0A] transition-colors cursor-default"
>
#{tag}
</span>
))}
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+141
View File
@@ -0,0 +1,141 @@
"use client";
import { motion } from "framer-motion";
import { useState } from "react";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const fadeUp = {
hidden: { opacity: 0, y: 30 },
show: { opacity: 1, y: 0, transition: { duration: 0.8, ease: easeOutExpo } },
};
const stagger = {
hidden: {},
show: { transition: { staggerChildren: 0.1 } },
};
export default function ContactClient({ lang, dict }: { lang: Locale; dict: any }) {
const [formState, setFormState] = useState({
name: "",
email: "",
company: "",
service: "AI Solutions",
message: "",
});
const [submitted, setSubmitted] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setSubmitted(true);
};
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 min-h-screen max-w-4xl mx-auto">
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">{dict.contact.badge}</span>
</div>
<h1 className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight">
{dict.contact.title}
</h1>
</div>
{/* Contact Grid */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
{/* Info Side */}
<div className="lg:col-span-5 space-y-0">
<div className="border-b border-[#C8C2B8] pb-8 mb-8">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">{dict.contact.infoBase}</div>
<div className="font-display font-black text-base text-[#0A0A0A]">{dict.contact.infoBaseText}</div>
<div className="font-mono text-[11px] text-[#A0998E] mt-1">{dict.contact.infoBaseSub}</div>
</div>
<div className="border-b border-[#C8C2B8] pb-8 mb-8">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">{dict.contact.infoDirect}</div>
<a href="mailto:hello@ayristech.com" className="font-display font-black text-base text-[#0A0A0A] hover:text-[#FF4500] transition-colors block">hello@ayristech.com</a>
<a href="tel:+905320000000" className="font-mono text-[12px] text-[#A0998E] hover:text-[#0A0A0A] transition-colors mt-1 block">+90 (532) 000 00 00</a>
</div>
<div className="pb-8">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">{dict.contact.infoBlueprint}</div>
<p className="text-[#7A7470] text-[13px] leading-relaxed">{dict.contact.infoBlueprintText}</p>
</div>
<div className="bg-[#FFE600] border-2 border-[#0A0A0A] p-6">
<p className="font-display font-black text-sm uppercase text-[#0A0A0A]">
{lang === "tr" ? "12 saat içinde yanıt alırsınız." : "Response within 12 operational hours."}
</p>
</div>
</div>
{/* Form Side */}
<div className="lg:col-span-7">
{submitted ? (
<motion.div
className="border-2 border-[#0A0A0A] bg-[#FFE600] p-8 sm:p-12 text-center h-full flex flex-col justify-center items-center"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6 }}
>
<div className="w-12 h-12 border-2 border-[#0A0A0A] bg-[#0A0A0A] flex items-center justify-center mb-6">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFE600" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12" /></svg>
</div>
<h3 className="font-display font-black text-2xl uppercase tracking-tight mb-2">{dict.contact.submittedTitle}</h3>
<p className="text-[#3A3A3A] text-[13px] max-w-sm">{dict.contact.submittedText}</p>
</motion.div>
) : (
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block font-mono text-[9px] font-bold uppercase tracking-[0.3em] text-[#A0998E] mb-2">{dict.contact.formName}</label>
<input type="text" required value={formState.name} onChange={e => setFormState({ ...formState, name: e.target.value })} placeholder={dict.contact.formNamePlaceholder}
className="w-full bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] outline-none py-3 text-sm text-[#0A0A0A] placeholder-[#C8C2B8] transition-colors" />
</div>
<div>
<label className="block font-mono text-[9px] font-bold uppercase tracking-[0.3em] text-[#A0998E] mb-2">{dict.contact.formEmail}</label>
<input type="email" required value={formState.email} onChange={e => setFormState({ ...formState, email: e.target.value })} placeholder={dict.contact.formEmailPlaceholder}
className="w-full bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] outline-none py-3 text-sm text-[#0A0A0A] placeholder-[#C8C2B8] transition-colors" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div>
<label className="block font-mono text-[9px] font-bold uppercase tracking-[0.3em] text-[#A0998E] mb-2">{dict.contact.formCompany}</label>
<input type="text" value={formState.company} onChange={e => setFormState({ ...formState, company: e.target.value })} placeholder={dict.contact.formCompanyPlaceholder}
className="w-full bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] outline-none py-3 text-sm text-[#0A0A0A] placeholder-[#C8C2B8] transition-colors" />
</div>
<div>
<label className="block font-mono text-[9px] font-bold uppercase tracking-[0.3em] text-[#A0998E] mb-2">{dict.contact.formTrack}</label>
<select value={formState.service} onChange={e => setFormState({ ...formState, service: e.target.value })}
className="w-full bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] outline-none py-3 text-sm text-[#0A0A0A] transition-colors cursor-pointer">
<option value="AI Solutions" className="bg-[#F4F0E8]">{dict.services.items.ai.title}</option>
<option value="Blockchain Dev" className="bg-[#F4F0E8]">{dict.services.items.blockchain.title}</option>
<option value="Mobile Apps" className="bg-[#F4F0E8]">{dict.services.items.mobile.title}</option>
<option value="Web Platforms" className="bg-[#F4F0E8]">{dict.services.items.web.title}</option>
</select>
</div>
</div>
<div>
<label className="block font-mono text-[9px] font-bold uppercase tracking-[0.3em] text-[#A0998E] mb-2">{dict.contact.formScope}</label>
<textarea rows={4} required value={formState.message} onChange={e => setFormState({ ...formState, message: e.target.value })} placeholder={dict.contact.formScopePlaceholder}
className="w-full bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] outline-none py-3 text-sm text-[#0A0A0A] placeholder-[#C8C2B8] transition-colors resize-none" />
</div>
<motion.button type="submit" className="btn-brutal btn-brutal-yellow w-full py-5 font-display font-black text-[13px] tracking-widest uppercase cursor-pointer" whileTap={{ scale: 0.98 }}>
{dict.contact.formSubmit}
</motion.button>
</form>
)}
</div>
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+101
View File
@@ -0,0 +1,101 @@
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const fadeUp = {
hidden: { opacity: 0, y: 30 },
show: { opacity: 1, y: 0, transition: { duration: 0.8, ease: easeOutExpo } },
};
const stagger = {
hidden: {},
show: { transition: { staggerChildren: 0.1 } },
};
const IconPlus = () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="w-4 h-4">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
);
export default function FAQClient({ lang, dict }: { lang: Locale; dict: any }) {
const [activeFaq, setActiveFaq] = useState<number | null>(null);
const faqs = dict.faq.items;
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 min-h-screen max-w-3xl mx-auto">
{/* Header */}
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">{dict.faq.badge}</span>
</div>
<motion.h1
className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
{dict.faq.title}
</motion.h1>
</div>
{/* Accordions */}
<div>
{faqs.map((faq: any, idx: number) => (
<motion.div
key={idx}
className="border-b border-[#C8C2B8]"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.06 }}
>
<button
className="w-full flex items-center justify-between py-6 text-left cursor-pointer group"
onClick={() => setActiveFaq(activeFaq === idx ? null : idx)}
>
<span className="font-display font-black text-base sm:text-lg uppercase text-[#0A0A0A] pr-8 leading-snug">{faq.q}</span>
<motion.div
className="flex-shrink-0 w-8 h-8 border border-[#C8C2B8] group-hover:border-[#0A0A0A] group-hover:bg-[#FFE600] text-[#8A8480] group-hover:text-[#0A0A0A] flex items-center justify-center transition-colors"
animate={{ rotate: activeFaq === idx ? 45 : 0 }}
>
<IconPlus />
</motion.div>
</button>
<AnimatePresence initial={false}>
{activeFaq === idx && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: easeOutExpo }}
>
<p className="text-[#6A6460] text-[14px] leading-relaxed pb-6 border-l-2 border-[#FFE600] pl-6">
{faq.a}
</p>
</motion.div>
)}
</AnimatePresence>
</motion.div>
))}
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+117
View File
@@ -0,0 +1,117 @@
"use client";
import Link from "next/link";
import type { Locale } from "@/i18n-config";
export default function Footer({ lang, dict }: { lang: Locale; dict: any }) {
const spectrum = [
dict.services.items.ai.title,
dict.services.items.blockchain.title,
dict.services.items.mobile.title,
dict.services.items.web.title,
];
const registry = [
{ name: dict.nav.services, href: `/${lang}/services` },
{ name: dict.nav.work, href: `/${lang}/work` },
{ name: dict.nav.process, href: `/${lang}/process` },
{ name: dict.nav.blog, href: `/${lang}/blog` },
{ name: dict.nav.partners, href: `/${lang}/partners` },
{ name: dict.nav.faq, href: `/${lang}/faq` },
];
return (
<footer className="bg-[#EDE8E0] border-t border-[#C8C2B8]">
{/* Big CTA */}
<div className="border-b border-[#C8C2B8] px-6 py-20">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-8">
<h2 className="font-display font-black text-4xl sm:text-5xl lg:text-6xl uppercase text-[#0A0A0A] leading-[0.9]">
Ready to build<br />
<span className="text-stroke">the impossible?</span>
</h2>
<Link
href={`/${lang}/contact`}
className="btn-brutal btn-brutal-yellow flex items-center gap-3 px-10 py-5 font-display font-black text-[13px] tracking-widest uppercase cursor-pointer flex-shrink-0"
>
{dict.nav.quote}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
</Link>
</div>
</div>
{/* Links */}
<div className="px-6 py-16 border-b border-[#C8C2B8]">
<div className="max-w-7xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-12">
{/* Brand */}
<div className="col-span-2">
<div className="flex items-center gap-3 mb-6">
<div className="w-8 h-8 bg-[#FFE600] border-2 border-[#0A0A0A] flex items-center justify-center font-display font-black text-[13px] text-[#0A0A0A]">
A
</div>
<span className="font-display font-black tracking-tight text-[15px] text-[#0A0A0A] uppercase">
{dict.nav.brandName}<span className="text-[#A0998E] font-medium">{dict.nav.brandSub}</span>
</span>
</div>
<p className="text-[#8A8480] text-[13px] leading-relaxed max-w-xs">
{dict.footer.desc}
</p>
</div>
{/* Spectrum */}
<div>
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-5">
{dict.footer.spectrum}
</div>
{spectrum.map(s => (
<Link
key={s}
href={`/${lang}/services`}
className="block text-[#6A6460] text-[13px] mb-3 hover:text-[#0A0A0A] transition-colors uppercase font-display font-bold tracking-tight"
>
{s}
</Link>
))}
</div>
{/* Registry */}
<div>
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-5">
{dict.footer.registry}
</div>
{registry.map(s => (
<Link
key={s.name}
href={s.href}
className="block text-[#6A6460] text-[13px] mb-3 hover:text-[#0A0A0A] transition-colors uppercase font-display font-bold tracking-tight"
>
{s.name}
</Link>
))}
</div>
</div>
</div>
{/* Bottom bar */}
<div className="px-6 py-6">
<div className="max-w-7xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-4">
<p className="font-mono text-[10px] tracking-[0.2em] uppercase text-[#C8C2B8]">
© 2026 Ayris Tech {dict.footer.rights}
</p>
<div className="flex items-center gap-6">
<Link href="#" className="font-mono text-[10px] tracking-[0.15em] uppercase text-[#C8C2B8] hover:text-[#0A0A0A] transition-colors">
{dict.footer.privacy}
</Link>
<Link href="#" className="font-mono text-[10px] tracking-[0.15em] uppercase text-[#C8C2B8] hover:text-[#0A0A0A] transition-colors">
{dict.footer.terms}
</Link>
</div>
</div>
</div>
</footer>
);
}
+163
View File
@@ -0,0 +1,163 @@
"use client";
import { motion, AnimatePresence, useScroll } from "framer-motion";
import { useState, useEffect } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { Locale } from "@/i18n-config";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
export default function Header({ lang, dict }: { lang: Locale; dict: any }) {
const [menuOpen, setMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const pathname = usePathname();
const { scrollY } = useScroll();
useEffect(() => {
const unsub = scrollY.on("change", (v) => setScrolled(v > 40));
return () => unsub();
}, [scrollY]);
const links = [
{ name: dict.services, href: `/${lang}/services` },
{ name: dict.work, href: `/${lang}/work` },
{ name: dict.process, href: `/${lang}/process` },
{ name: dict.blog, href: `/${lang}/blog` },
{ name: dict.partners, href: `/${lang}/partners` },
{ name: dict.faq, href: `/${lang}/faq` },
];
const otherLang = lang === "tr" ? "en" : "tr";
return (
<>
<motion.header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
scrolled
? "bg-[#F4F0E8]/90 backdrop-blur-md border-b border-[#C8C2B8]"
: "bg-transparent"
}`}
initial={{ y: -80, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.8, ease: expo }}
>
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
{/* Logo */}
<Link href={`/${lang}`} className="flex items-center gap-3 cursor-pointer group">
<div className="w-8 h-8 bg-[#FFE600] border-2 border-[#0A0A0A] flex items-center justify-center font-display font-black text-[13px] text-[#0A0A0A] group-hover:bg-[#FF4500] transition-colors duration-200">
A
</div>
<span className="font-display font-black tracking-tight text-[15px] text-[#0A0A0A] uppercase">
{dict.brandName}<span className="text-[#A0998E] font-medium">{dict.brandSub}</span>
</span>
</Link>
{/* Desktop nav */}
<nav className="hidden md:flex items-center gap-8">
{links.map(item => {
const isActive = pathname === item.href;
return (
<Link
key={item.name}
href={item.href}
className={`font-mono text-[10px] tracking-[0.2em] uppercase transition-colors duration-200 ${
isActive ? "text-[#0A0A0A] font-bold" : "text-[#A0998E] hover:text-[#0A0A0A]"
}`}
>
{item.name}
</Link>
);
})}
</nav>
{/* Right: lang + CTA */}
<div className="hidden md:flex items-center gap-4">
<Link
href={pathname.replace(`/${lang}`, `/${otherLang}`)}
className="font-mono text-[10px] tracking-[0.2em] uppercase text-[#A0998E] hover:text-[#0A0A0A] transition-colors duration-200 border border-[#C8C2B8] hover:border-[#0A0A0A] px-3 py-2"
>
{otherLang.toUpperCase()}
</Link>
<Link
href={`/${lang}/contact`}
className="btn-brutal btn-brutal-yellow flex items-center gap-2 px-5 py-2.5 font-display font-black text-[11px] tracking-widest uppercase cursor-pointer"
>
{dict.quote}
</Link>
</div>
{/* Mobile hamburger */}
<button
className="md:hidden flex flex-col justify-center items-center gap-1.5 w-10 h-10 cursor-pointer border border-[#C8C2B8] hover:border-[#0A0A0A] transition-colors"
onClick={() => setMenuOpen(!menuOpen)}
aria-label="Toggle Menu"
>
<motion.span
className="h-[1.5px] w-5 bg-[#0A0A0A] block"
animate={{ rotate: menuOpen ? 45 : 0, y: menuOpen ? 4.5 : 0 }}
transition={{ duration: 0.2 }}
/>
<motion.span
className="h-[1.5px] w-5 bg-[#0A0A0A] block"
animate={{ opacity: menuOpen ? 0 : 1, scaleX: menuOpen ? 0 : 1 }}
transition={{ duration: 0.2 }}
/>
<motion.span
className="h-[1.5px] w-5 bg-[#0A0A0A] block"
animate={{ rotate: menuOpen ? -45 : 0, y: menuOpen ? -4.5 : 0 }}
transition={{ duration: 0.2 }}
/>
</button>
</div>
</motion.header>
{/* Mobile menu overlay */}
<AnimatePresence>
{menuOpen && (
<motion.div
className="fixed inset-0 z-40 bg-[#F4F0E8] flex flex-col"
initial={{ clipPath: "inset(0 0 100% 0)" }}
animate={{ clipPath: "inset(0 0 0% 0)" }}
exit={{ clipPath: "inset(0 0 100% 0)" }}
transition={{ duration: 0.5, ease: expo }}
>
<div className="max-w-7xl mx-auto px-6 pt-20 pb-12 flex flex-col h-full">
<nav className="flex flex-col gap-1 flex-1 justify-center">
{links.map((item, idx) => (
<motion.div
key={item.name}
initial={{ x: -30, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -30, opacity: 0 }}
transition={{ delay: idx * 0.07, duration: 0.4 }}
>
<Link
href={item.href}
className="block font-display font-black text-5xl uppercase text-[#C8C2B8] hover:text-[#0A0A0A] transition-colors duration-200 py-2"
onClick={() => setMenuOpen(false)}
>
{item.name}
</Link>
</motion.div>
))}
</nav>
<div className="border-t border-[#C8C2B8] pt-8">
<Link
href={`/${lang}/contact`}
className="btn-brutal btn-brutal-yellow inline-flex items-center gap-3 px-8 py-4 font-display font-black text-[13px] tracking-widest uppercase cursor-pointer"
onClick={() => setMenuOpen(false)}
>
{dict.quote}
</Link>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</>
);
}
+863
View File
@@ -0,0 +1,863 @@
"use client";
import {
motion,
AnimatePresence,
useScroll,
useTransform,
useMotionValue,
useSpring,
} from "framer-motion";
import { useRef, useState, useEffect, useCallback } from "react";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
import Link from "next/link";
import { blogPosts } from "@/data/blog";
// ── EASING ──
const expo: [number, number, number, number] = [0.16, 1, 0.3, 1];
// ── SVG ICONS ──
const IconAI = () => (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="18" height="18" rx="1" />
<path d="M9 3v18M15 3v18M3 9h18M3 15h18" />
<circle cx="9" cy="9" r="1.5" fill="currentColor" />
<circle cx="15" cy="15" r="1.5" fill="currentColor" />
</svg>
);
const IconChain = () => (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<path d="M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12" />
</svg>
);
const IconMobile = () => (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="5" y="2" width="14" height="20" rx="2" />
<path d="M12 18h.01" />
</svg>
);
const IconWeb = () => (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="4" width="18" height="16" rx="1" />
<path d="M3 9h18M8 6h.01M11 6h.01M14 6h.01" />
</svg>
);
const IconArrow = () => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
);
const IconArrowUpRight = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="7" y1="17" x2="17" y2="7" />
<polyline points="7 7 17 7 17 17" />
</svg>
);
const IconPlus = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
);
const IconCheck = () => (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#0A0A0A" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
);
// ── ANIMATED COUNTER ──
function Counter({ target, suffix = "" }: { target: number; suffix?: string }) {
const [count, setCount] = useState(0);
const ref = useRef<HTMLSpanElement>(null);
const [started, setStarted] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) setStarted(true); },
{ threshold: 0.5 }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
useEffect(() => {
if (!started) return;
let cur = 0;
const steps = 50;
const inc = target / steps;
const iv = setInterval(() => {
cur += inc;
if (cur >= target) { setCount(target); clearInterval(iv); }
else setCount(Math.floor(cur));
}, 1600 / steps);
return () => clearInterval(iv);
}, [started, target]);
return <span ref={ref}>{count.toLocaleString()}{suffix}</span>;
}
// ── MAGNETIC BUTTON ──
function MagneticBtn({ children, className, href }: { children: React.ReactNode; className?: string; href?: string }) {
const ref = useRef<HTMLDivElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const sx = useSpring(x, { stiffness: 200, damping: 20 });
const sy = useSpring(y, { stiffness: 200, damping: 20 });
const handleMouse = useCallback((e: React.MouseEvent) => {
if (!ref.current) return;
const { left, top, width, height } = ref.current.getBoundingClientRect();
x.set((e.clientX - left - width / 2) * 0.3);
y.set((e.clientY - top - height / 2) * 0.3);
}, [x, y]);
const handleLeave = useCallback(() => { x.set(0); y.set(0); }, [x, y]);
const Tag = href ? "a" : "div";
return (
<motion.div ref={ref} style={{ x: sx, y: sy }} onMouseMove={handleMouse} onMouseLeave={handleLeave} className="inline-block">
<Tag href={href} className={className}>{children}</Tag>
</motion.div>
);
}
// ── MARQUEE TEXT ──
function Marquee({ text, reverse = false }: { text: string; reverse?: boolean }) {
const repeated = Array(8).fill(text).join(" · ");
return (
<div className="marquee-wrapper border-y border-[#C8C2B8] bg-[#EDE8E0] py-3 overflow-hidden">
<div className={reverse ? "marquee-track-reverse" : "marquee-track"}>
{[0, 1].map(i => (
<span key={i} className="font-mono text-[11px] tracking-[0.35em] uppercase text-[#A0998E] px-8 whitespace-nowrap flex-shrink-0">
{repeated}
</span>
))}
</div>
</div>
);
}
// ── SPLIT TEXT ANIMATION ──
function SplitReveal({ text, className, delay = 0 }: { text: string; className?: string; delay?: number }) {
const words = text.split(" ");
return (
<span className={`inline-flex flex-wrap gap-x-[0.25em] ${className}`}>
{words.map((word, i) => (
<span key={i} className="overflow-hidden inline-block">
<motion.span
className="inline-block"
initial={{ y: "110%", rotate: 2 }}
whileInView={{ y: 0, rotate: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.75, ease: expo, delay: delay + i * 0.06 }}
>
{word}
</motion.span>
</span>
))}
</span>
);
}
// ── SECTION LABEL ──
function Label({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3 mb-6">
<div className="w-4 h-4 bg-[#FFE600] flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">{children}</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
);
}
// ── MAIN COMPONENT ──
export default function HomeClient({ lang, dict }: { lang: Locale; dict: any }) {
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "25%"]);
const heroOpacity = useTransform(scrollYProgress, [0, 0.7], [1, 0]);
const [activeFaq, setActiveFaq] = useState<number | null>(null);
const [formSent, setFormSent] = useState(false);
const [hoverCase, setHoverCase] = useState<number | null>(null);
const serviceIcons = [<IconAI />, <IconChain />, <IconMobile />, <IconWeb />];
const serviceCodes = ["AI / ML", "BC / WEB3", "MOB / DART", "WEB / NEXT"];
const services = dict.services.items
? Object.entries(dict.services.items as Record<string, { title: string; desc: string }>).map(([, val], idx) => ({
icon: serviceIcons[idx],
code: serviceCodes[idx],
title: val.title,
desc: val.desc,
items: [
["Predictive Models", "NLP Pipelines", "Computer Vision", "Agentic Workflows"],
["Smart Contract Audit", "DeFi Architecture", "Tokenomics Design", "Cross-chain Bridges"],
["Universal Codebase", "Offline Syncing", "Biometric Protocols", "Core Bundle Optimization"],
["Next.js App Router", "Server Components", "Headless Commerce", "Modular API Mesh"],
][idx],
}))
: [];
const caseStudies = (dict.work.items as any[]).map((item: any, idx: number) => ({
...item,
tech: [
["Python", "TensorFlow", "Next.js", "PostgreSQL"],
["Solidity", "React", "IPFS", "Node.js"],
["Flutter", "Firebase", "Django", "AWS"],
["Next.js", "Stripe", "Redis", "Vercel"],
][idx] || ["React", "TypeScript", "Node.js"],
accent: ["#FFE600", "#FF4500", "#0A0A0A", "#00CC77"][idx] || "#FFE600",
}));
const processes = dict.process.items as any[];
const featuredPosts = blogPosts;
const faqs = dict.faq.items as any[];
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body selection:bg-[#FFE600] selection:text-[#0A0A0A]">
{/* Noise overlay */}
<div className="noise-overlay" />
<Header lang={lang} dict={dict.nav} />
{/* ──────────────── HERO ──────────────── */}
<section ref={heroRef} className="relative min-h-screen flex flex-col justify-end overflow-hidden border-b border-[#C8C2B8]">
{/* Dot grid bg */}
<motion.div
className="absolute inset-0 dot-grid opacity-60"
style={{ y: heroY, opacity: heroOpacity }}
/>
{/* Big watermark letters */}
<motion.div
className="absolute right-0 bottom-0 font-display font-black text-[22vw] leading-none text-[#E8E3DC] select-none pointer-events-none overflow-hidden"
style={{ y: useTransform(scrollYProgress, [0, 1], ["0%", "20%"]) }}
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 1.2, delay: 0.5 }}
>
AT
</motion.div>
{/* Yellow right accent */}
<div className="absolute top-0 right-0 w-1 h-full bg-[#FFE600]" />
<div className="relative z-10 max-w-7xl mx-auto px-6 w-full pb-20 pt-40">
{/* Meta bar */}
<motion.div
className="flex items-center justify-between border-b border-[#C8C2B8] pb-6 mb-16"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.1, duration: 0.8 }}
>
<div className="flex items-center gap-3">
<span className="w-2 h-2 bg-[#00CC77] rounded-full animate-pulse" />
<span className="font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E]">
{dict.hero.badge}
</span>
</div>
<span className="font-mono text-[10px] tracking-[0.2em] text-[#A0998E]">
EST. 2025 MUĞLA
</span>
</motion.div>
{/* Headline */}
<div className="mb-12">
<h1 className="font-display font-black uppercase leading-[0.85] tracking-tight">
<motion.div className="overflow-hidden" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: 0.15 }}>
<motion.span
className="block text-[15vw] sm:text-[12vw] lg:text-[10vw] text-[#0A0A0A]"
initial={{ y: "105%" }}
animate={{ y: 0 }}
transition={{ delay: 0.2, duration: 0.9, ease: expo }}
>
{dict.hero.titleLine1}
</motion.span>
</motion.div>
<motion.div className="overflow-hidden" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: 0.3 }}>
<motion.span
className="block text-[15vw] sm:text-[12vw] lg:text-[10vw] text-stroke"
initial={{ y: "105%" }}
animate={{ y: 0 }}
transition={{ delay: 0.35, duration: 0.9, ease: expo }}
>
{dict.hero.titleLine2}
</motion.span>
</motion.div>
</h1>
</div>
{/* Bottom bar */}
<motion.div
className="grid grid-cols-1 lg:grid-cols-12 gap-8 border-t border-[#C8C2B8] pt-10"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.8 }}
>
<div className="lg:col-span-5">
<p className="text-[#6A6460] text-base leading-relaxed max-w-md">
{dict.hero.desc}
</p>
</div>
<div className="lg:col-span-7 flex items-end justify-end gap-4 flex-wrap">
<MagneticBtn href={`/${lang}/contact`} className="btn-brutal btn-brutal-yellow inline-flex items-center gap-3 px-8 py-4 font-display font-black text-[13px] tracking-widest uppercase cursor-pointer">
{dict.hero.ctaQuote} <IconArrow />
</MagneticBtn>
<MagneticBtn href={`/${lang}/work`} className="btn-brutal inline-flex items-center gap-3 px-8 py-4 font-display font-black text-[13px] tracking-widest uppercase cursor-pointer">
{dict.hero.ctaWork}
</MagneticBtn>
</div>
</motion.div>
</div>
</section>
{/* ──────────────── MARQUEE 1 ──────────────── */}
<Marquee text="AI SOLUTIONS · BLOCKCHAIN DEV · MOBILE APPS · WEB PLATFORMS · DIGITAL TRANSFORMATION" />
{/* ──────────────── STATS ──────────────── */}
<section className="border-b border-[#C8C2B8] bg-[#F4F0E8]">
<div className="max-w-7xl mx-auto px-6 grid grid-cols-2 md:grid-cols-4">
{[
{ val: 48, suffix: "+", label: dict.stats.deliveries },
{ val: 98, suffix: "%", label: dict.stats.retention },
{ val: 12, suffix: "+", label: dict.stats.regions },
{ val: 1, suffix: "wk", label: lang === "tr" ? "MVP Süresi" : "Avg Cycle To MVP" },
].map((s, idx) => (
<motion.div
key={idx}
className={`py-12 px-6 flex flex-col justify-center ${idx < 3 ? "border-r border-[#C8C2B8]" : ""} ${idx < 2 ? "border-b md:border-b-0 border-[#C8C2B8]" : ""}`}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1, duration: 0.6 }}
>
<div className="font-display font-black text-5xl sm:text-6xl text-[#0A0A0A] leading-none mb-2">
<Counter target={s.val} suffix={s.suffix} />
</div>
<div className="font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E]">
{s.label}
</div>
</motion.div>
))}
</div>
</section>
{/* ──────────────── SERVICES ──────────────── */}
<section id="services" className="py-24 border-b border-[#C8C2B8]">
<div className="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-16">
<div className="lg:col-span-7">
<Label>{dict.services.badge}</Label>
<h2 className="font-display font-black uppercase text-5xl sm:text-6xl lg:text-7xl leading-[0.9] tracking-tight">
<SplitReveal text={dict.services.title} />
</h2>
</div>
<div className="lg:col-span-5 flex items-end">
<p className="text-[#6A6460] leading-relaxed border-l-2 border-[#FFE600] pl-6">
{dict.services.desc}
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-px bg-[#C8C2B8]">
{services.map((s, idx) => (
<motion.div
key={idx}
className="group bg-[#F4F0E8] p-10 cursor-pointer relative overflow-hidden"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1 }}
whileHover={{ backgroundColor: "#EDE8E0" }}
>
{/* Hover accent left bar */}
<motion.div
className="absolute left-0 top-0 bottom-0 w-1 bg-[#FFE600]"
initial={{ scaleY: 0 }}
whileHover={{ scaleY: 1 }}
transition={{ duration: 0.2 }}
style={{ transformOrigin: "top" }}
/>
<div className="flex items-start justify-between mb-8">
<div className="w-12 h-12 border border-[#C8C2B8] group-hover:border-[#0A0A0A] group-hover:bg-[#0A0A0A] group-hover:text-[#FFE600] flex items-center justify-center text-[#6A6460] transition-colors duration-200">
{s.icon}
</div>
<span className="font-mono text-[10px] tracking-[0.2em] uppercase text-[#C8C2B8] group-hover:text-[#A0998E] transition-colors duration-200">
{s.code}
</span>
</div>
<h3 className="font-display font-black text-2xl uppercase text-[#0A0A0A] mb-3 group-hover:text-[#0A0A0A] transition-colors duration-200">
{s.title}
</h3>
<p className="text-[#8A8480] text-sm leading-relaxed mb-8">
{s.desc}
</p>
<div className="flex flex-wrap gap-2 pt-6 border-t border-[#D8D3CA]">
{s.items?.map((item: string) => (
<span key={item} className="font-mono text-[10px] tracking-wider uppercase px-3 py-1.5 border border-[#C8C2B8] text-[#8A8480] group-hover:border-[#0A0A0A] group-hover:text-[#0A0A0A] transition-colors">
{item}
</span>
))}
</div>
{/* Number watermark */}
<div className="absolute bottom-4 right-6 font-display font-black text-[6rem] leading-none text-[#E8E3DC] select-none pointer-events-none">
{String(idx + 1).padStart(2, "0")}
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ──────────────── MARQUEE 2 ──────────────── */}
<Marquee text="CASE STUDIES · FINTECH · WEB3 · HEALTHCARE · E-COMMERCE · ENTERPRISE SCALE" reverse />
{/* ──────────────── CASE STUDIES ──────────────── */}
<section id="work" className="py-24 border-b border-[#C8C2B8]">
<div className="max-w-7xl mx-auto px-6">
<div className="mb-16">
<Label>{dict.work.badge}</Label>
<h2 className="font-display font-black uppercase text-5xl sm:text-6xl lg:text-7xl leading-[0.9] tracking-tight">
<SplitReveal text={dict.work.title} />
</h2>
</div>
<div className="border-t border-[#C8C2B8]">
{caseStudies.map((study: any, idx: number) => (
<motion.div
key={idx}
className="group border-b border-[#C8C2B8] cursor-pointer"
onHoverStart={() => setHoverCase(idx)}
onHoverEnd={() => setHoverCase(null)}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.08 }}
>
<div className="grid grid-cols-12 items-center py-8 gap-6">
{/* Index */}
<div className="col-span-1 hidden md:block">
<span className="font-mono text-[10px] text-[#C8C2B8]">{study.num}</span>
</div>
{/* Title */}
<div className="col-span-12 md:col-span-4">
<div className="flex items-center gap-4">
<motion.div
className="w-10 h-10 flex-shrink-0 flex items-center justify-center font-display font-black text-[11px] border-2 border-[#0A0A0A]"
style={{ backgroundColor: hoverCase === idx ? study.accent : "transparent", color: "#0A0A0A" }}
animate={{ rotate: hoverCase === idx ? 45 : 0 }}
transition={{ duration: 0.2 }}
>
</motion.div>
<h3 className="font-display font-black text-xl uppercase text-[#0A0A0A] group-hover:text-[#0A0A0A]">
{study.title}
</h3>
</div>
</div>
{/* Tag */}
<div className="col-span-6 md:col-span-2">
<span className="font-mono text-[10px] tracking-[0.15em] uppercase text-[#8A8480] border border-[#C8C2B8] px-3 py-1.5 group-hover:border-[#0A0A0A] group-hover:text-[#0A0A0A] transition-colors duration-200">
{study.tag}
</span>
</div>
{/* Description */}
<div className="col-span-12 md:col-span-4">
<p className="text-[#8A8480] text-[13px] leading-relaxed group-hover:text-[#6A6460] transition-colors">
{study.desc}
</p>
</div>
{/* Arrow */}
<div className="col-span-6 md:col-span-1 flex justify-end">
<motion.div
className="text-[#C8C2B8] group-hover:text-[#0A0A0A] transition-colors"
animate={{ x: hoverCase === idx ? 4 : 0 }}
>
<IconArrowUpRight />
</motion.div>
</div>
</div>
{/* Expanded tech stack */}
<AnimatePresence>
{hoverCase === idx && (
<motion.div
className="pb-6 flex flex-wrap gap-2 pl-0 md:pl-[calc(8.33%+1.5rem)]"
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: expo }}
>
<span className="font-mono text-[9px] tracking-[0.2em] uppercase text-[#C8C2B8] pr-3">STACK:</span>
{study.tech.map((t: string) => (
<span key={t} className="font-mono text-[9px] tracking-wider uppercase px-2.5 py-1 bg-[#EDE8E0] text-[#6A6460] border border-[#C8C2B8]">
{t}
</span>
))}
</motion.div>
)}
</AnimatePresence>
</motion.div>
))}
</div>
<motion.div
className="mt-12 flex justify-end"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
>
<a href={`/${lang}/work`} className="btn-brutal inline-flex items-center gap-3 px-8 py-4 font-display font-black text-[12px] tracking-widest uppercase cursor-pointer">
{dict.work.analyzeBtn} <IconArrow />
</a>
</motion.div>
</div>
</section>
{/* ──────────────── PROCESS ──────────────── */}
<section id="process" className="py-24 border-b border-[#C8C2B8] bg-[#EDE8E0]">
<div className="max-w-7xl mx-auto px-6">
<div className="mb-20">
<Label>{dict.process.badge}</Label>
<h2 className="font-display font-black uppercase text-5xl sm:text-6xl lg:text-7xl leading-[0.9] tracking-tight">
<SplitReveal text={dict.process.title} />
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-px bg-[#C8C2B8]">
{processes.map((p: any, idx: number) => (
<motion.div
key={idx}
className="bg-[#EDE8E0] p-8 relative overflow-hidden group"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.12, duration: 0.6 }}
whileHover={{ backgroundColor: "#E8E3DA" }}
>
<div className="absolute top-0 right-0 font-display font-black text-[7rem] leading-none text-[#D8D3CA] select-none pointer-events-none">
{p.num}
</div>
<div className="relative z-10">
<motion.div
className="w-8 h-1 bg-[#FFE600] mb-8"
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
viewport={{ once: true }}
style={{ transformOrigin: "left" }}
transition={{ delay: idx * 0.12 + 0.3, duration: 0.5 }}
/>
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-4">
{p.timeframe}
</div>
<h3 className="font-display font-black text-lg uppercase text-[#0A0A0A] mb-4 group-hover:text-[#0A0A0A] transition-colors">
{p.title}
</h3>
<p className="text-[#7A7470] text-[13px] leading-relaxed">
{p.desc}
</p>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ──────────────── BLOG FEATURED ──────────────── */}
<section id="blog" className="py-24 border-b border-[#C8C2B8]">
<div className="max-w-7xl mx-auto px-6">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-8 mb-16">
<div>
<Label>{dict.blog.badge}</Label>
<h2 className="font-display font-black uppercase text-5xl sm:text-6xl leading-[0.9] tracking-tight">
<SplitReveal text={dict.blog.title} />
</h2>
</div>
<Link
href={`/${lang}/blog`}
className="font-mono text-[10px] tracking-[0.2em] uppercase text-[#0A0A0A] border-b-2 border-[#0A0A0A] pb-1 hover:text-[#FF4500] hover:border-[#FF4500] transition-colors self-start md:self-auto"
>
{dict.blog.viewAll}
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{featuredPosts.map((post, idx) => {
const content = post[lang] || post.en;
const accent = ["#FFE600", "#FF4500", "#00CC77"][idx] || "#FFE600";
return (
<motion.div
key={post.slug}
className="group flex flex-col bg-[#F4F0E8] border border-[#C8C2B8] hover:border-[#0A0A0A] p-8 relative overflow-hidden transition-all duration-300 h-full"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1, duration: 0.6 }}
whileHover={{ y: -6, backgroundColor: "#EDE8E0", boxShadow: "4px 4px 0px 0px #0A0A0A" }}
>
<div className="flex items-center gap-3 mb-6">
<span
className="font-mono text-[9px] tracking-[0.2em] uppercase px-2.5 py-1 border border-[#C8C2B8] text-[#6A6460]"
style={{ borderLeftColor: accent, borderLeftWidth: 3 }}
>
{content.category}
</span>
<span className="font-mono text-[9px] text-[#A0998E]">
{post.date}
</span>
</div>
<h3 className="font-display font-black text-xl uppercase text-[#0A0A0A] mb-4 leading-tight group-hover:text-[#0A0A0A]">
{content.title}
</h3>
<p className="text-[#6A6460] text-[13px] leading-relaxed mb-8 flex-grow">
{content.excerpt}
</p>
<div className="pt-6 border-t border-[#C8C2B8] flex items-center justify-between mt-auto">
<div className="flex flex-col">
<span className="font-display font-black text-[11px] uppercase text-[#0A0A0A]">{post.author}</span>
<span className="font-mono text-[9px] text-[#A0998E] uppercase tracking-wider">{post.authorRole}</span>
</div>
<Link
href={`/${lang}/blog/${post.slug}`}
className="w-10 h-10 border border-[#C8C2B8] group-hover:border-[#0A0A0A] group-hover:bg-[#0A0A0A] group-hover:text-[#FFE600] text-[#0A0A0A] flex items-center justify-center transition-all"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
</Link>
</div>
{/* Top line category accent */}
<div className="absolute top-0 left-0 right-0 h-1 transition-colors" style={{ backgroundColor: accent }} />
</motion.div>
);
})}
</div>
</div>
</section>
{/* ──────────────── FAQ ──────────────── */}
<section id="faq" className="py-24 border-b border-[#C8C2B8] bg-[#EDE8E0]">
<div className="max-w-4xl mx-auto px-6">
<div className="mb-16">
<Label>{dict.faq.badge}</Label>
<h2 className="font-display font-black uppercase text-5xl sm:text-6xl leading-[0.9] tracking-tight">
<SplitReveal text={dict.faq.title} />
</h2>
</div>
<div>
{faqs.map((faq: any, idx: number) => (
<motion.div
key={idx}
className="border-b border-[#C8C2B8]"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.06 }}
>
<button
className="w-full flex items-center justify-between py-6 text-left cursor-pointer group"
onClick={() => setActiveFaq(activeFaq === idx ? null : idx)}
>
<span className="font-display font-black text-base sm:text-lg uppercase text-[#0A0A0A] pr-8 leading-snug">
{faq.q}
</span>
<motion.div
className="flex-shrink-0 w-8 h-8 border border-[#C8C2B8] group-hover:border-[#0A0A0A] group-hover:bg-[#FFE600] text-[#8A8480] group-hover:text-[#0A0A0A] flex items-center justify-center transition-colors"
animate={{ rotate: activeFaq === idx ? 45 : 0 }}
>
<IconPlus />
</motion.div>
</button>
<AnimatePresence initial={false}>
{activeFaq === idx && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: expo }}
>
<p className="text-[#6A6460] text-[14px] leading-relaxed pb-6 border-l-2 border-[#FFE600] pl-6">
{faq.a}
</p>
</motion.div>
)}
</AnimatePresence>
</motion.div>
))}
</div>
</div>
</section>
{/* ──────────────── CONTACT ──────────────── */}
<section id="contact" className="py-24 border-b border-[#C8C2B8]">
<div className="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-16">
<div className="lg:col-span-7">
<Label>{dict.contact.badge}</Label>
<h2 className="font-display font-black uppercase text-5xl sm:text-6xl lg:text-7xl leading-[0.9] tracking-tight">
<SplitReveal text={dict.contact.title} />
</h2>
</div>
<div className="lg:col-span-5 flex items-end">
<p className="text-[#6A6460] leading-relaxed border-l-2 border-[#FFE600] pl-6">
{dict.contact.desc}
</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-px bg-[#C8C2B8]">
{/* Form */}
<div className="lg:col-span-8 bg-[#F4F0E8] p-10">
<AnimatePresence mode="wait">
{!formSent ? (
<motion.form
key="form"
onSubmit={(e) => { e.preventDefault(); setFormSent(true); }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
{[
{ label: dict.contact.formName, placeholder: dict.contact.formNamePlaceholder, type: "text" },
{ label: dict.contact.formEmail, placeholder: dict.contact.formEmailPlaceholder, type: "email" },
{ label: dict.contact.formCompany, placeholder: dict.contact.formCompanyPlaceholder, type: "text" },
{ label: dict.contact.formTrack, type: "select", options: ["$5k $20k", "$20k $50k", "$50k $100k", "$100k+"] },
].map((field, fi) => (
<div key={fi} className="flex flex-col">
<label className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">{field.label}</label>
{field.type === "select" ? (
<select className="bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] text-[#0A0A0A] text-sm py-3 focus:outline-none transition-colors cursor-pointer appearance-none">
{field.options?.map(opt => <option key={opt} value={opt} className="bg-[#F4F0E8]">{opt}</option>)}
</select>
) : (
<input
required
type={field.type}
placeholder={field.placeholder}
className="bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] text-[#0A0A0A] placeholder-[#C8C2B8] text-sm py-3 focus:outline-none transition-colors rounded-none"
/>
)}
</div>
))}
</div>
<div className="flex flex-col mb-8">
<label className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">{dict.contact.formScope}</label>
<textarea
required
rows={4}
placeholder={dict.contact.formScopePlaceholder}
className="bg-transparent border-b border-[#C8C2B8] focus:border-[#0A0A0A] text-[#0A0A0A] placeholder-[#C8C2B8] text-sm py-3 focus:outline-none transition-colors resize-none rounded-none"
/>
</div>
<motion.button
type="submit"
className="btn-brutal btn-brutal-yellow w-full py-5 font-display font-black text-[13px] tracking-widest uppercase cursor-pointer"
whileTap={{ scale: 0.98 }}
>
{dict.contact.formSubmit}
</motion.button>
</motion.form>
) : (
<motion.div
key="success"
className="py-16 flex flex-col items-center text-center"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<div className="w-16 h-16 bg-[#FFE600] border-2 border-[#0A0A0A] flex items-center justify-center mb-8">
<IconCheck />
</div>
<h3 className="font-display font-black text-3xl uppercase text-[#0A0A0A] mb-3">
{dict.contact.submittedTitle}
</h3>
<p className="text-[#6A6460] text-sm max-w-sm mb-8">
{dict.contact.submittedText}
</p>
<button
onClick={() => setFormSent(false)}
className="btn-brutal px-6 py-3 font-display font-black text-[11px] tracking-widest uppercase cursor-pointer"
>
{lang === "tr" ? "Yeni Talep" : "New Request"}
</button>
</motion.div>
)}
</AnimatePresence>
</div>
{/* Sidebar info */}
<div className="lg:col-span-4 bg-[#F4F0E8]">
<div className="border-b border-[#C8C2B8] p-8">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">
{dict.contact.infoDirect}
</div>
<div className="font-display font-black text-base text-[#0A0A0A]">
hello@ayristech.com
</div>
</div>
<div className="border-b border-[#C8C2B8] p-8">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">
{dict.contact.infoBlueprint}
</div>
<div className="font-display font-black text-base text-[#0A0A0A]">
+90 532 000 00 00
</div>
</div>
<div className="p-8">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-3">
{lang === "tr" ? "Genel Merkez" : "HQ Coordinates"}
</div>
<div className="font-display font-black text-base text-[#0A0A0A]">
{dict.contact.infoBaseText}
</div>
</div>
{/* Yellow CTA block */}
<div className="bg-[#FFE600] p-8 border-t-2 border-[#0A0A0A]">
<p className="font-display font-black text-sm uppercase text-[#0A0A0A] leading-snug">
{lang === "tr"
? "Teknik ekibimiz 12 saat içinde geri döner."
: "Our team responds within 12 operational hours."}
</p>
</div>
</div>
</div>
</div>
</section>
<Footer lang={lang} dict={dict} />
</div>
);
}
+93
View File
@@ -0,0 +1,93 @@
"use client";
import { motion } from "framer-motion";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
export default function PartnersClient({
lang,
dict,
initialPartners,
}: {
lang: Locale;
dict: any;
initialPartners?: any[];
}) {
const partners = initialPartners && initialPartners.length > 0 ? initialPartners : (dict.partners.items as any[]);
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-7xl mx-auto">
{/* Title Header Block */}
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{dict.partners.badge}
</span>
</div>
<motion.h1
className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: expo }}
>
{dict.partners.title}
</motion.h1>
<p className="text-[#6A6460] mt-6 text-lg max-w-3xl font-light">
{dict.partners.desc}
</p>
</div>
{/* Dynamic partners brutalist card grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-px bg-[#C8C2B8] border border-[#C8C2B8]">
{partners.map((partner, idx) => (
<motion.div
key={partner.name}
className="group bg-[#F4F0E8] p-8 relative overflow-hidden transition-all duration-300 flex flex-col h-full cursor-default"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.08 }}
whileHover={{ backgroundColor: "#EDE8E0" }}
>
{/* Monogram Badge */}
<div className="w-14 h-14 mb-8 border border-[#C8C2B8] group-hover:border-[#0A0A0A] group-hover:bg-[#0A0A0A] group-hover:text-[#FFE600] transition-all flex items-center justify-center font-display font-black text-sm text-[#8A8480] bg-[#F4F0E8] select-none">
{partner.mono}
</div>
{/* Title & Tag */}
<h3 className="font-display font-black text-xl uppercase text-[#0A0A0A] mb-1">
{partner.name}
</h3>
<p className="font-mono text-[9px] tracking-[0.2em] uppercase text-[#FF4500] mb-5 font-bold">
{partner.tag}
</p>
{/* Description */}
<p className="text-[#7A7470] text-[13px] leading-relaxed mb-8 flex-grow">
{partner.desc}
</p>
{/* Establishment Year details bar */}
<div className="pt-4 border-t border-[#C8C2B8] flex justify-between items-center mt-auto">
<span className="font-mono text-[9px] text-[#A0998E]">PARTNERSHIP</span>
<span className="font-mono text-[9px] text-[#0A0A0A] font-bold">[ EST. {partner.year} ]</span>
</div>
{/* Interactive bottom corner ribbon */}
<div className="absolute bottom-0 right-0 w-0 h-0 group-hover:w-8 group-hover:h-8 bg-[#FFE600] transition-all duration-300" />
</motion.div>
))}
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+76
View File
@@ -0,0 +1,76 @@
"use client";
import { motion } from "framer-motion";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
export default function ProcessClient({ lang, dict }: { lang: Locale; dict: any }) {
const processes = dict.process.items as any[];
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-7xl mx-auto">
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">{dict.process.badge}</span>
</div>
<motion.h1
className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: expo }}
>
{dict.process.title}
</motion.h1>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-px bg-[#C8C2B8]">
{processes.map((p: any, idx: number) => (
<motion.div
key={idx}
className="group bg-[#F4F0E8] p-10 relative overflow-hidden"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.12, duration: 0.6 }}
whileHover={{ backgroundColor: "#EDE8E0" }}
>
<div className="absolute top-0 right-0 font-display font-black text-[7rem] leading-none text-[#E8E3DC] select-none pointer-events-none">
{p.num}
</div>
<div className="relative z-10">
<motion.div
className="w-8 h-1 bg-[#FFE600] mb-8"
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
viewport={{ once: true }}
style={{ transformOrigin: "left" }}
transition={{ delay: idx * 0.12 + 0.3, duration: 0.5 }}
/>
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-4">
{p.timeframe}
</div>
<h3 className="font-display font-black text-lg uppercase text-[#0A0A0A] mb-4 group-hover:text-[#0A0A0A]">
{p.title}
</h3>
<p className="text-[#7A7470] text-[13px] leading-relaxed">
{p.desc}
</p>
</div>
</motion.div>
))}
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+144
View File
@@ -0,0 +1,144 @@
"use client";
import { motion } from "framer-motion";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const IconAI = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="18" height="18" rx="1" />
<path d="M9 3v18M15 3v18M3 9h18M3 15h18" />
<circle cx="9" cy="9" r="1.5" fill="currentColor" />
<circle cx="15" cy="15" r="1.5" fill="currentColor" />
</svg>
);
const IconChain = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<path d="M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12" />
</svg>
);
const IconMobile = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round">
<rect x="5" y="2" width="14" height="20" rx="2" />
<path d="M12 18h.01" />
</svg>
);
const IconWeb = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="4" width="18" height="16" rx="1" />
<path d="M3 9h18M8 6h.01M11 6h.01M14 6h.01" />
</svg>
);
export default function ServicesClient({ lang, dict }: { lang: Locale; dict: any }) {
const services = [
{
icon: <IconAI />, title: dict.services.items.ai.title, code: "AI / ML", desc: dict.services.items.ai.desc, accent: "#FFE600",
items: lang === "tr"
? ["Tahmin Modelleri", "Doğal Dil İşleme", "Bilgisayarlı Görü", "Etmen İş Akışları", "Özel Gömme Uzayları", "RAG Sistemleri"]
: ["Predictive Models", "Natural Language Processing", "Computer Vision", "Agentic Workflows", "Custom Embeddings", "RAG Deployments"],
},
{
icon: <IconChain />, title: dict.services.items.blockchain.title, code: "BC / WEB3", desc: dict.services.items.blockchain.desc, accent: "#FF4500",
items: lang === "tr"
? ["Akıllı Sözleşme Denetimi", "DeFi Mimarileri", "Tokenomik Tasarımı", "Çapraz Zincir Köprüleri", "EVM Optimizasyonu", "IPFS Entegrasyonu"]
: ["Smart Contract Audit", "DeFi Architectures", "Tokenomics Design", "Cross-chain Bridges", "EVM Optimizations", "IPFS Integration"],
},
{
icon: <IconMobile />, title: dict.services.items.mobile.title, code: "MOB / DART", desc: dict.services.items.mobile.desc, accent: "#0A0A0A",
items: lang === "tr"
? ["Evrensel Kod Tabanı", "Çevrimdışı Senkronizasyon", "Biyometrik Protokoller", "Optimize Çekirdek Paket", "Push Bildirim Mimarisi", "Mağaza Operasyonları"]
: ["Universal Codebase", "Offline Syncing", "Biometric Protocols", "Optimized Core Bundle", "Push Notification Architecture", "App Store Operations"],
},
{
icon: <IconWeb />, title: dict.services.items.web.title, code: "WEB / NEXT", desc: dict.services.items.web.desc, accent: "#00CC77",
items: lang === "tr"
? ["Next.js App Router", "Sunucu Bileşenleri", "Headless E-Ticaret", "Modüler API Ağı", "Tailwind CSS Entegrasyonu", "Vercel Kenar Telemetrisi"]
: ["Next.js App Router", "Server Components", "Headless Commerce", "Modular API Mesh", "Tailwind CSS Integration", "Vercel Edge Telemetry"],
},
];
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-7xl mx-auto">
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">{dict.services.badge}</span>
</div>
<motion.h1
className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: expo }}
>
{dict.services.title}
</motion.h1>
</div>
<div className="grid grid-cols-1 gap-px bg-[#C8C2B8]">
{services.map((s, idx) => (
<motion.div
key={idx}
className="group bg-[#F4F0E8] grid grid-cols-1 lg:grid-cols-12 relative overflow-hidden"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1 }}
whileHover={{ backgroundColor: "#EDE8E0" }}
>
{/* Left */}
<div className="lg:col-span-5 p-10 border-b lg:border-b-0 lg:border-r border-[#C8C2B8] flex flex-col justify-between">
<div>
<div className="flex items-start justify-between mb-8">
<div className="w-14 h-14 border border-[#C8C2B8] group-hover:border-[#0A0A0A] group-hover:bg-[#0A0A0A] group-hover:text-[#FFE600] flex items-center justify-center text-[#8A8480] transition-colors duration-200">
{s.icon}
</div>
<span className="font-mono text-[9px] tracking-[0.25em] uppercase text-[#C8C2B8] group-hover:text-[#A0998E] transition-colors">
{s.code}
</span>
</div>
<h2 className="font-display font-black text-3xl uppercase mb-4 group-hover:text-[#0A0A0A]">
{s.title}
</h2>
<p className="text-[#7A7470] text-[14px] leading-relaxed">
{s.desc}
</p>
</div>
<div className="font-display font-black text-[6rem] leading-none text-[#E8E3DC] select-none mt-4">
{String(idx + 1).padStart(2, "0")}
</div>
</div>
{/* Right */}
<div className="lg:col-span-7 p-10 flex flex-col justify-center">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-8">
{dict.services.deliverablesLabel}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{s.items.map((item: string) => (
<div key={item} className="flex items-center gap-4 group/item cursor-default">
<div className="w-1 h-4 flex-shrink-0 group-hover/item:h-6 transition-all duration-200" style={{ backgroundColor: s.accent }} />
<span className="font-display font-bold text-[14px] uppercase text-[#8A8480] group-hover/item:text-[#0A0A0A] transition-colors">
{item}
</span>
</div>
))}
</div>
</div>
</motion.div>
))}
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+148
View File
@@ -0,0 +1,148 @@
"use client";
import { motion } from "framer-motion";
import { useState } from "react";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
const expo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const IconArrowUpRight = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="7" y1="17" x2="17" y2="7" />
<polyline points="7 7 17 7 17 17" />
</svg>
);
export default function WorkClient({
lang,
dict,
initialProjects,
}: {
lang: Locale;
dict: any;
initialProjects?: any[];
}) {
const [hoverCase, setHoverCase] = useState<number | null>(null);
const accentColors = ["#FFE600", "#FF4500", "#0A0A0A", "#00CC77"];
const projectsList = initialProjects && initialProjects.length > 0 ? initialProjects : (dict.work.items as any[]);
const caseStudies = projectsList.map((item: any, idx: number) => ({
...item,
accent: accentColors[idx % accentColors.length] ?? "#FFE600",
// Fallback tech in case older dict snapshot doesn't have it yet
tech: item.tech ?? ["React", "TypeScript", "Node.js"],
}));
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
<main className="pt-32 pb-24 px-6 max-w-7xl mx-auto">
{/* Page header */}
<div className="border-b border-[#C8C2B8] pb-12 mb-16">
<div className="flex items-center gap-3 mb-6">
<div className="w-3 h-3 bg-[#FFE600]" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{dict.work.badge}
</span>
</div>
<motion.h1
className="font-display font-black text-5xl sm:text-6xl lg:text-8xl uppercase leading-[0.85] tracking-tight"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: expo }}
>
{dict.work.title}
</motion.h1>
</div>
{/* Case study list */}
<div className="space-y-px bg-[#C8C2B8]">
{caseStudies.map((study: any, idx: number) => (
<motion.div
key={idx}
className="group bg-[#F4F0E8] grid grid-cols-1 lg:grid-cols-12 relative overflow-hidden cursor-pointer"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1 }}
whileHover={{ backgroundColor: "#EDE8E0" }}
onHoverStart={() => setHoverCase(idx)}
onHoverEnd={() => setHoverCase(null)}
>
{/* Full-card link */}
<Link
href={`/${lang}/work/${study.slug}`}
className="absolute inset-0 z-10"
aria-label={study.title}
/>
{/* Accent top bar */}
<motion.div
className="absolute top-0 left-0 right-0 h-1"
style={{ backgroundColor: study.accent }}
initial={{ scaleX: 0 }}
animate={{ scaleX: hoverCase === idx ? 1 : 0 }}
transition={{ duration: 0.3, ease: expo }}
/>
{/* Left column */}
<div className="lg:col-span-4 p-10 border-b lg:border-b-0 lg:border-r border-[#C8C2B8] flex flex-col justify-between">
<div>
<span className="font-mono text-[9px] tracking-[0.25em] uppercase text-[#A0998E] border border-[#C8C2B8] px-2.5 py-1">
{study.tag}
</span>
<h2 className="font-display font-black text-2xl uppercase mt-6 mb-2">
{study.title}
</h2>
</div>
<div>
<div className="font-mono text-[9px] tracking-[0.2em] uppercase text-[#A0998E] mb-1">
{dict.work.specsLabel}:
</div>
<div className="font-display font-bold text-[13px] text-[#0A0A0A]">
{study.spec}
</div>
<div className="font-display font-black text-[5rem] leading-none text-[#E8E3DC] select-none mt-4">
{study.num}
</div>
</div>
</div>
{/* Right column */}
<div className="lg:col-span-8 p-10 flex flex-col justify-between">
<p className="text-[#7A7470] text-base leading-relaxed mb-8">
{study.desc}
</p>
<div className="flex flex-wrap items-center justify-between gap-4 pt-6 border-t border-[#D8D3CA]">
<div className="flex flex-wrap gap-2">
{(study.tech as string[]).map((t) => (
<span
key={t}
className="font-mono text-[10px] tracking-wider uppercase px-2.5 py-1 border border-[#C8C2B8] text-[#8A8480] group-hover:border-[#0A0A0A] group-hover:text-[#0A0A0A] transition-colors"
>
{t}
</span>
))}
</div>
{/* Button sits above the overlay link via z-20 */}
<span className="relative z-20 font-display font-black text-[12px] uppercase tracking-wider text-[#0A0A0A] flex items-center gap-2 group-hover:opacity-60 transition-opacity">
{dict.work.analyzeBtn} <IconArrowUpRight />
</span>
</div>
</div>
</motion.div>
))}
</div>
</main>
<Footer lang={lang} dict={dict} />
</div>
);
}
+555
View File
@@ -0,0 +1,555 @@
"use client";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import { useRef, useState, useEffect } from "react";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import type { Locale } from "@/i18n-config";
const expo: [number, number, number, number] = [0.16, 1, 0.3, 1];
const IconArrowLeft = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</svg>
);
const IconArrowRight = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
);
const IconCheck = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#0A0A0A" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
);
// Tag colors per category
const accentFor = (tag: string): string => {
if (/ai|yz|ml/i.test(tag)) return "#FFE600";
if (/web3|blockchain|chain/i.test(tag)) return "#FF4500";
if (/mobile|mobil/i.test(tag)) return "#0A0A0A";
return "#00CC77";
};
interface Props {
lang: Locale;
dict: any;
project: any;
prev: any | null;
next: any | null;
}
const screenshotsFor = (slug: string): string[] => {
const mapping: Record<string, string[]> = {
"financeai-dashboard": [
"https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1551836022-d5d88e9218df?auto=format&fit=crop&w=1200&q=80",
],
"chainsupply-network": [
"https://images.unsplash.com/photo-1508873535684-277a3cbcc4e8?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1639762681485-074b7f938ba0?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&w=1200&q=80",
],
"meditrack-mobile": [
"https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1584982751601-97dcc096659c?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1530026405186-ed1ea0ac7a63?auto=format&fit=crop&w=1200&q=80",
],
"novamart-platform": [
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?auto=format&fit=crop&w=1200&q=80",
"https://images.unsplash.com/photo-1523474253046-8cd2748b5fd2?auto=format&fit=crop&w=1200&q=80",
],
};
return mapping[slug] || [];
};
export default function WorkDetailClient({ lang, dict, project, prev, next }: Props) {
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "30%"]);
const [activeImage, setActiveImage] = useState<string | null>(null);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setActiveImage(null);
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
const accent = accentFor(project.tag);
let screenshots = screenshotsFor(project.slug);
if (project.gallery && project.gallery.length > 0) {
screenshots = project.gallery;
}
return (
<div className="bg-[#F4F0E8] text-[#0A0A0A] font-body min-h-screen">
<Header lang={lang} dict={dict.nav} />
{/* ── HERO ── */}
<section
ref={heroRef}
className="relative min-h-[70vh] flex flex-col justify-end overflow-hidden border-b border-[#C8C2B8]"
>
{/* Dot grid */}
<motion.div
className="absolute inset-0"
style={{ y: heroY }}
>
{project.image ? (
<>
<div
className="absolute inset-0 bg-cover bg-center"
style={{ backgroundImage: `url(${project.image})` }}
/>
<div className="absolute inset-0 bg-[#F4F0E8]/80" />
</>
) : (
<div className="absolute inset-0 dot-grid opacity-50" />
)}
</motion.div>
{/* Accent left vertical bar */}
<div className="absolute top-0 left-0 w-1 h-full" style={{ backgroundColor: accent }} />
{/* Big number watermark */}
<motion.div
className="absolute right-0 bottom-0 font-display font-black select-none pointer-events-none text-[#E8E3DC] leading-none"
style={{ fontSize: "clamp(8rem, 25vw, 20rem)" }}
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 1, delay: 0.3 }}
>
{project.num}
</motion.div>
<div className="relative z-10 max-w-7xl mx-auto px-6 w-full pb-16 pt-36">
{/* Breadcrumb */}
<motion.div
className="flex items-center gap-3 mb-10"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.1 }}
>
<Link
href={`/${lang}/work`}
className="flex items-center gap-2 font-mono text-[10px] tracking-[0.25em] uppercase text-[#A0998E] hover:text-[#0A0A0A] transition-colors"
>
<IconArrowLeft /> {dict.nav.work}
</Link>
<span className="text-[#C8C2B8]">/</span>
<span className="font-mono text-[10px] tracking-[0.25em] uppercase text-[#0A0A0A]">
{project.num}
</span>
</motion.div>
{/* Tag badge */}
<motion.div
className="mb-5"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.5 }}
>
<span
className="inline-block font-mono text-[10px] tracking-[0.25em] uppercase px-3 py-1.5 border border-[#C8C2B8]"
style={{ borderLeftColor: accent, borderLeftWidth: 3 }}
>
{project.tag}
</span>
</motion.div>
{/* Title */}
<div className="overflow-hidden mb-8">
<motion.h1
className="font-display font-black uppercase leading-[0.85] tracking-tight text-[#0A0A0A]"
style={{ fontSize: "clamp(3rem, 8vw, 7rem)" }}
initial={{ y: "110%" }}
animate={{ y: 0 }}
transition={{ duration: 0.85, ease: expo, delay: 0.25 }}
>
{project.title}
</motion.h1>
</div>
{/* Meta row */}
<motion.div
className="grid grid-cols-2 md:grid-cols-4 gap-px bg-[#C8C2B8] border-t border-[#C8C2B8]"
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5, duration: 0.6 }}
>
{[
{ label: lang === "tr" ? "Yıl" : "Year", value: project.year },
{ label: lang === "tr" ? "Süre" : "Duration", value: project.duration },
{ label: lang === "tr" ? "Sektör" : "Sector", value: project.tag },
{ label: lang === "tr" ? "Müşteri" : "Client", value: project.client },
].map((m, i) => (
<div key={i} className="bg-[#F4F0E8] px-5 py-4">
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] mb-1">{m.label}</div>
<div className="font-display font-black text-[13px] uppercase text-[#0A0A0A] truncate">{m.value}</div>
</div>
))}
</motion.div>
</div>
</section>
{/* ── CONTENT ── */}
<main className="max-w-7xl mx-auto px-6 py-20">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16">
{/* Left column: main content */}
<div className="lg:col-span-8 space-y-20">
{/* Overview */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-6">
<div className="w-4 h-4 flex-shrink-0" style={{ backgroundColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Genel Bakış" : "Overview"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<p className="text-[#4A4440] text-lg leading-relaxed border-l-4 pl-6" style={{ borderColor: accent }}>
{project.desc}
</p>
</motion.section>
{/* Challenge */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-6">
<div className="w-4 h-4 border-2 border-[#0A0A0A] flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Problem" : "The Challenge"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<h2 className="font-display font-black text-2xl uppercase text-[#0A0A0A] mb-4">
{lang === "tr" ? "Ne Sorununu Çözdük?" : "What Problem Did We Solve?"}
</h2>
<p className="text-[#6A6460] leading-relaxed">
{project.challenge}
</p>
</motion.section>
{/* Solution */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-6">
<div className="w-4 h-4 border-2 flex-shrink-0" style={{ borderColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Çözüm" : "Our Solution"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<h2 className="font-display font-black text-2xl uppercase text-[#0A0A0A] mb-4">
{lang === "tr" ? "Nasıl İnşa Ettik?" : "How Did We Build It?"}
</h2>
<p className="text-[#6A6460] leading-relaxed">
{project.solution}
</p>
</motion.section>
{/* Results */}
<motion.section
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
>
<div className="flex items-center gap-3 mb-8">
<div className="w-4 h-4 flex-shrink-0" style={{ backgroundColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Sonuçlar" : "Results"}
</span>
<div className="flex-1 h-px bg-[#C8C2B8]" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-px bg-[#C8C2B8]">
{project.results.map((r: string, idx: number) => (
<motion.div
key={idx}
className="bg-[#F4F0E8] p-6 flex items-start gap-4 group"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1 }}
whileHover={{ backgroundColor: "#EDE8E0" }}
>
<div
className="w-6 h-6 flex-shrink-0 flex items-center justify-center mt-0.5"
style={{ backgroundColor: accent }}
>
<IconCheck />
</div>
<p className="font-display font-bold text-[14px] uppercase text-[#0A0A0A] leading-snug">
{r}
</p>
</motion.div>
))}
</div>
</motion.section>
</div>
{/* Right column: sidebar */}
<aside className="lg:col-span-4 space-y-0">
<div className="sticky top-24">
{/* Perf spec */}
<motion.div
className="border border-[#C8C2B8] mb-px"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.4 }}
>
<div className="border-b border-[#C8C2B8] px-6 py-3 flex items-center gap-2">
<div className="w-2 h-2" style={{ backgroundColor: accent }} />
<span className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E]">
{dict.work.specsLabel}
</span>
</div>
<div className="px-6 py-5">
<p className="font-display font-black text-sm uppercase text-[#0A0A0A] leading-snug">
{project.spec}
</p>
</div>
</motion.div>
{/* Tech stack */}
<motion.div
className="border border-[#C8C2B8] mb-px"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.5 }}
>
<div className="border-b border-[#C8C2B8] px-6 py-3">
<span className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Teknoloji Yığını" : "Tech Stack"}
</span>
</div>
<div className="px-6 py-5 flex flex-wrap gap-2">
{project.tech.map((t: string) => (
<span
key={t}
className="font-mono text-[10px] tracking-wider uppercase px-2.5 py-1.5 border border-[#C8C2B8] text-[#6A6460] hover:border-[#0A0A0A] hover:text-[#0A0A0A] transition-colors cursor-default"
>
{t}
</span>
))}
</div>
</motion.div>
{/* Website */}
{project.website && (
<motion.div
className="border border-[#C8C2B8] mb-px"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.55 }}
>
<div className="border-b border-[#C8C2B8] px-6 py-3 flex items-center gap-2">
<div className="w-2 h-2" style={{ backgroundColor: accent }} />
<span className="font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E]">
{lang === "tr" ? "Canlı Proje" : "Live Project"}
</span>
</div>
<div className="px-6 py-5">
<a
href={project.website.startsWith('http') ? project.website : `https://${project.website}`}
target="_blank"
rel="noopener noreferrer"
className="inline-block font-mono text-[10px] tracking-wider uppercase px-4 py-2 border border-[#0A0A0A] hover:bg-[#0A0A0A] hover:text-[#FFE600] transition-colors cursor-pointer w-full text-center"
>
{lang === "tr" ? "Siteyi Ziyaret Et" : "Visit Website"}
</a>
</div>
</motion.div>
)}
{/* CTA */}
<motion.div
className="border-2 border-[#0A0A0A] p-6"
style={{ backgroundColor: accent }}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.6 }}
>
<p className="font-display font-black text-sm uppercase text-[#0A0A0A] mb-4 leading-snug">
{lang === "tr" ? "Benzer bir proje mi istiyorsunuz?" : "Want a similar project?"}
</p>
<Link
href={`/${lang}/contact`}
className="btn-brutal inline-flex items-center gap-2 px-5 py-3 font-display font-black text-[11px] tracking-widest uppercase cursor-pointer bg-[#0A0A0A] text-[#F4F0E8] border-[#0A0A0A]"
>
{dict.nav.quote} <IconArrowRight />
</Link>
</motion.div>
</div>
</aside>
</div>
</main>
{/* ── PROJECT SCREENSHOTS GALLERY ── */}
{screenshots.length > 0 && (
<section className="py-24 border-t border-[#C8C2B8] bg-[#EDE8E0]">
<div className="max-w-7xl mx-auto px-6">
<div className="flex items-center gap-3 mb-12">
<div className="w-3 h-3" style={{ backgroundColor: accent }} />
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-[#A0998E]">
{dict.work.galleryTitle || "EKRAN GÖRÜNTÜLERİ"}
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{screenshots.map((src, idx) => (
<motion.div
key={idx}
className="group bg-[#F4F0E8] border border-[#C8C2B8] hover:border-[#0A0A0A] p-3 relative overflow-hidden transition-all duration-300 cursor-zoom-in"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: idx * 0.1, duration: 0.6 }}
onClick={() => setActiveImage(src)}
whileHover={{ y: -6, boxShadow: `4px 4px 0px 0px ${accent}` }}
>
<div className="aspect-[16/10] w-full overflow-hidden border border-[#C8C2B8] group-hover:border-[#0A0A0A] bg-[#EDE8E0] transition-colors relative">
<img
src={src}
alt={`${project.title} screenshot ${idx + 1}`}
className="w-full h-full object-cover grayscale group-hover:grayscale-0 contrast-[1.1] brightness-[0.98] group-hover:scale-105 transition-all duration-700"
/>
<div className="absolute inset-0 bg-[#0A0A0A]/5 opacity-100 group-hover:opacity-0 transition-opacity duration-300" />
</div>
<div className="mt-3 flex justify-between items-center font-mono text-[9px] text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors">
<span>SCREEN // 0{idx + 1}</span>
<span className="uppercase tracking-widest">[ ZOOM ]</span>
</div>
</motion.div>
))}
</div>
</div>
</section>
)}
{/* Lightbox Modal */}
<AnimatePresence>
{activeImage && (
<motion.div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/85 backdrop-blur-md px-6 cursor-zoom-out"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setActiveImage(null)}
>
<button
onClick={() => setActiveImage(null)}
className="absolute top-6 right-6 w-12 h-12 border border-white/20 hover:border-white/80 hover:bg-white/10 text-white flex items-center justify-center font-mono text-[10px] uppercase tracking-widest cursor-pointer transition-colors duration-200"
>
[ CLOSE ]
</button>
<motion.div
className="max-w-5xl max-h-[85vh] border-2 border-white/10 bg-[#0A0A0A] p-2 relative"
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
transition={{ duration: 0.4, ease: expo }}
onClick={(e) => e.stopPropagation()}
>
<img
src={activeImage}
alt="Enlarged project screenshot"
className="max-w-full max-h-[80vh] object-contain block"
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* ── PREV / NEXT NAVIGATION ── */}
<div className="border-t border-[#C8C2B8]">
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-px bg-[#C8C2B8]">
{/* Prev */}
<div>
{prev ? (
<Link
href={`/${lang}/work/${prev.slug}`}
className="group flex flex-col p-10 bg-[#F4F0E8] hover:bg-[#EDE8E0] transition-colors h-full"
>
<div className="flex items-center gap-2 font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors mb-4">
<IconArrowLeft />
{lang === "tr" ? "Önceki Proje" : "Previous Project"}
</div>
<div className="font-display font-black text-xl uppercase text-[#0A0A0A] mt-auto">
{prev.title}
</div>
<div className="font-mono text-[10px] tracking-wider uppercase text-[#A0998E] mt-1">
{prev.tag}
</div>
</Link>
) : (
<div className="p-10 bg-[#EDE8E0]" />
)}
</div>
{/* Next */}
<div>
{next ? (
<Link
href={`/${lang}/work/${next.slug}`}
className="group flex flex-col items-end p-10 bg-[#F4F0E8] hover:bg-[#EDE8E0] transition-colors h-full text-right"
>
<div className="flex items-center gap-2 font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors mb-4">
{lang === "tr" ? "Sonraki Proje" : "Next Project"}
<IconArrowRight />
</div>
<div className="font-display font-black text-xl uppercase text-[#0A0A0A] mt-auto">
{next.title}
</div>
<div className="font-mono text-[10px] tracking-wider uppercase text-[#A0998E] mt-1">
{next.tag}
</div>
</Link>
) : (
<Link
href={`/${lang}/work`}
className="group flex flex-col items-end p-10 bg-[#F4F0E8] hover:bg-[#EDE8E0] transition-colors h-full text-right"
>
<div className="flex items-center gap-2 font-mono text-[9px] tracking-[0.3em] uppercase text-[#A0998E] group-hover:text-[#0A0A0A] transition-colors mb-4">
{lang === "tr" ? "Tüm Projeler" : "All Projects"}
<IconArrowRight />
</div>
<div className="font-display font-black text-xl uppercase text-[#C8C2B8] mt-auto">
{lang === "tr" ? "Portfolyo" : "Portfolio"}
</div>
</Link>
)}
</div>
</div>
</div>
<Footer lang={lang} dict={dict} />
</div>
);
}
+474
View File
@@ -0,0 +1,474 @@
"use client";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import { useRef, useState } from "react";
import type { DemoData } from "@/data/demos";
import AnimatedCounter from "@/components/ui/AnimatedCounter";
const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const fadeUp = {
hidden: { opacity: 0, y: 40 },
show: { opacity: 1, y: 0, transition: { duration: 0.7, ease: easeOutExpo } },
};
const stagger = {
hidden: {},
show: { transition: { staggerChildren: 0.1 } },
};
export default function DentalTemplate({ data }: { data: DemoData }) {
const { firma, istatistikler, hizmetler, projeler = [], yorumlar = [] } = data;
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "20%"]);
const heroOpacity = useTransform(scrollYProgress, [0, 0.8], [1, 0]);
const [activeYorum, setActiveYorum] = useState(0);
const [menuOpen, setMenuOpen] = useState(false);
const [showBookingModal, setShowBookingModal] = useState(false);
return (
<div className="bg-[#FAF7F2] text-[#1E2E38] font-body">
{/* ── HEADER ── */}
<motion.header
className="fixed top-0 left-0 right-0 z-50 px-6 py-4"
initial={{ y: -80 }}
animate={{ y: 0 }}
transition={{ duration: 0.6, ease: easeOutExpo }}
>
<div className="mx-auto max-w-6xl flex items-center justify-between h-16 px-6 bg-white/40 backdrop-blur-md rounded-3xl border border-white/20 shadow-sm">
<div className="flex items-center gap-2.5">
<span className="text-xl font-bold tracking-tight text-[#1E2E38] font-display flex items-center gap-1.5 uppercase">
<span className="text-2xl">🦷</span> {firma.adi}
</span>
</div>
<div className="flex items-center gap-8">
<button
className="flex flex-col gap-1.5 p-2 bg-[#1E2E38]/5 hover:bg-[#1E2E38]/10 transition-colors rounded-xl"
onClick={() => setMenuOpen(!menuOpen)}
>
<div className="w-5 h-0.5 bg-[#1E2E38]" />
<div className="w-3 h-0.5 bg-[#1E2E38]" />
</button>
</div>
<div className="hidden md:flex items-center gap-6">
<a href={`tel:${firma.telefon}`} className="text-xs font-bold text-[#1E2E38]/80 hover:text-[#1E2E38] transition-colors">
📞 {firma.telefon}
</a>
<motion.button
onClick={() => setShowBookingModal(true)}
className="text-xs font-black text-[#FAF7F2] bg-[#1E2E38] px-5 py-2.5 rounded-full hover:bg-[#121C22] transition-colors"
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.97 }}
>
Contact Us
</motion.button>
</div>
</div>
{/* Dropdown Menu */}
<AnimatePresence>
{menuOpen && (
<motion.div
className="absolute top-24 left-6 right-6 bg-white/95 backdrop-blur-xl border border-gray-100 rounded-3xl p-6 shadow-2xl flex flex-col gap-4 z-50 md:max-w-md md:left-auto md:right-6"
initial={{ opacity: 0, y: -20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -20, scale: 0.95 }}
transition={{ duration: 0.25 }}
>
<h4 className="text-xs font-black uppercase tracking-widest text-gray-400 border-b pb-2 mb-2">Navigasyon</h4>
{[
{ label: "001 - Uyelik Programı", href: "#uyelik" },
{ label: "002 - Hizmetler", href: "#hizmetler" },
{ label: "003 - Portfolyo (Works)", href: "#works" },
{ label: "004 - Yorumlar", href: "#yorumlar" },
{ label: "005 - Randevu Al", href: "#randevu" }
].map(item => (
<a
key={item.href}
href={item.href}
onClick={() => setMenuOpen(false)}
className="font-display font-bold text-[#1E2E38] hover:text-[#D4AF37] transition-colors text-base"
>
{item.label}
</a>
))}
</motion.div>
)}
</AnimatePresence>
</motion.header>
{/* ── HERO SECTION ── */}
<section ref={heroRef} className="relative min-h-screen flex items-center overflow-hidden bg-[#FAF7F2] pt-24">
<div className="absolute inset-0 pointer-events-none z-10">
<div className="absolute top-[25%] left-[8%] text-[#1E2E38]/20 text-3xl font-light select-none">+</div>
<div className="absolute bottom-[20%] left-[28%] text-[#1E2E38]/20 text-3xl font-light select-none">+</div>
<div className="absolute bottom-[35%] right-[5%] text-[#1E2E38]/30 text-3xl font-light select-none">+</div>
</div>
<div className="relative z-10 max-w-6xl mx-auto px-6 w-full py-12">
<div className="grid lg:grid-cols-12 gap-12 items-center">
<motion.div
className="lg:col-span-12 z-20 text-center"
variants={stagger}
initial="hidden"
animate="show"
>
<motion.div
variants={fadeUp}
className="inline-flex items-center gap-2 text-[11px] font-black uppercase tracking-widest text-[#1E2E38]/60 mb-6"
>
<span className="w-1.5 h-1.5 rounded-full bg-[#1E2E38]" />
{firma.slogan}
</motion.div>
<motion.h1
variants={fadeUp}
className="text-6xl md:text-7xl font-display font-black text-[#1E2E38] leading-[1.05] tracking-tight mb-8"
>
Seamless 🦷
<br />
<span className="italic font-light opacity-90 text-[#3C5A48]">Dental Care</span>
</motion.h1>
<motion.p
variants={fadeUp}
className="text-[#1E2E38]/70 text-lg leading-relaxed max-w-md mx-auto mb-10 font-medium"
>
A smooth and hassle-free dental care experience for a healthy and confident smile.
</motion.p>
<motion.div variants={fadeUp}>
<motion.button
onClick={() => setShowBookingModal(true)}
className="inline-flex items-center gap-3 px-8 py-4 rounded-full bg-[#1E2E38] hover:bg-[#121C22] text-[#FAF7F2] font-black text-sm transition-all"
whileHover={{ scale: 1.04, boxShadow: "0 10px 30px rgba(30, 46, 56, 0.15)" }}
whileTap={{ scale: 0.97 }}
>
Book Now <span className="text-xs"></span>
</motion.button>
</motion.div>
</motion.div>
</div>
</div>
</section>
{/* ── SECTION 001: SAVINGS BENTO ── */}
<section id="uyelik" className="py-24 bg-[#FAF7F2] px-6 border-t border-[#1E2E38]/5">
<div className="max-w-6xl mx-auto">
<div className="grid lg:grid-cols-12 gap-16 items-start">
<motion.div
className="lg:col-span-5"
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.2 }}
variants={stagger}
>
<span className="inline-block text-[11px] font-black uppercase tracking-widest text-[#1E2E38]/60 mb-4">
001 - Join Membership
</span>
<h2 className="text-4xl lg:text-5xl font-display font-black text-[#1E2E38] leading-tight mb-6">
Affordable Dental Care for Your Family
</h2>
<p className="text-[#1E2E38]/70 text-base leading-relaxed mb-10">
Unlock optimal dental wellness for your loved ones. Get massive savings on check-ups, cleaning, and specialized cosmetic treatments.
</p>
<motion.button
onClick={() => setShowBookingModal(true)}
className="px-7 py-3.5 rounded-full bg-[#1E2E38] hover:bg-[#121C22] text-[#FAF7F2] font-black text-xs transition-colors"
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.97 }}
>
Join Membership <span className="text-[10px]"></span>
</motion.button>
</motion.div>
<div className="lg:col-span-7 grid md:grid-cols-2 gap-6 w-full">
<motion.div
className="bg-[#EBF5F0] rounded-[36px] p-8 flex flex-col justify-between h-[320px] relative overflow-hidden group shadow-sm"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
whileHover={{ y: -6 }}
>
<div className="flex justify-between items-start">
<span className="text-5xl font-black text-[#1E2E38] font-display">
{istatistikler[0]?.deger}
</span>
<span className="text-3xl font-light text-[#1E2E38]/50 shrink-0 select-none">+</span>
</div>
<p className="text-[#1E2E38]/80 text-[13px] leading-relaxed font-medium">
{istatistikler[0]?.etiket}
</p>
</motion.div>
<motion.div
className="bg-[#1E2E38] rounded-[36px] p-8 flex flex-col justify-between h-[320px] relative overflow-hidden group shadow-xl"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.15 }}
whileHover={{ y: -6 }}
>
<div className="flex justify-between items-start">
<span className="text-5xl font-black text-[#FAF7F2] font-display">
{istatistikler[1]?.deger}
</span>
<span className="text-3xl font-light text-[#FAF7F2]/50 shrink-0 select-none">+</span>
</div>
<p className="text-[#FAF7F2]/80 text-[13px] leading-relaxed font-medium">
{istatistikler[1]?.etiket}
</p>
</motion.div>
</div>
</div>
</div>
</section>
{/* ── CORE SERVICES CARD ROW ── */}
<section id="hizmetler" className="py-16 bg-white px-6">
<div className="max-w-6xl mx-auto">
<div className="grid md:grid-cols-3 gap-6">
{hizmetler.map((h, i) => (
<motion.div
key={i}
className="bg-[#FAF7F2]/60 hover:bg-[#FAF7F2] rounded-[28px] p-8 border border-gray-100 hover:border-[#1E2E38]/10 transition-all duration-300 flex flex-col items-center text-center group cursor-default"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.1 }}
whileHover={{ y: -4 }}
>
<div className="w-14 h-14 rounded-full bg-white flex items-center justify-center text-2xl shadow-sm mb-5 transition-transform group-hover:scale-110">
{h.ikon}
</div>
<h3 className="font-display font-bold text-[#1E2E38] text-base mb-3 leading-snug">
{h.baslik}
</h3>
<p className="text-gray-400 text-xs leading-relaxed">
{h.aciklama}
</p>
</motion.div>
))}
</div>
</div>
</section>
{/* ── SECTION 002: OUR WORKS ── */}
<section id="works" className="py-24 bg-[#EBF5F0] px-6">
<div className="max-w-6xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between mb-16 gap-6">
<motion.div
initial="hidden"
whileInView="show"
viewport={{ once: true }}
variants={stagger}
>
<span className="inline-block text-[11px] font-black uppercase tracking-widest text-[#1E2E38]/60 mb-4">
002 - Our Works
</span>
<h2 className="text-4xl lg:text-5xl font-display font-black text-[#1E2E38] leading-tight">
A Healthy Smile<br />Starts Here
</h2>
</motion.div>
</div>
<div className="grid md:grid-cols-3 gap-8">
{projeler.map((p, i) => (
<motion.div
key={i}
className="bg-[#FAF7F2] border border-[#E7E5E4] rounded-3xl p-6 hover:border-[#1E2E38] transition-colors"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: i * 0.15 }}
whileHover={{ y: -6 }}
>
<div className="w-10 h-10 rounded-xl bg-[#1E2E38]/5 flex items-center justify-center mb-6 text-xl">
{p.ikon}
</div>
<h3 className="font-display font-bold text-[#1E2E38] text-lg mb-1">{p.baslik}</h3>
<p className="text-gray-500 text-xs font-semibold">{p.aciklama}</p>
</motion.div>
))}
</div>
</div>
</section>
{/* ── YORUMLAR ── */}
<section id="yorumlar" className="py-24 bg-white px-6">
<div className="max-w-6xl mx-auto">
<div className="mb-16 text-center">
<span className="inline-block text-[11px] font-black uppercase tracking-widest text-[#1E2E38]/60 mb-4">
003 - Patient Stories
</span>
<h2 className="text-4xl font-display font-black text-[#1E2E38]">
Patient Experiences
</h2>
</div>
<div className="relative">
<AnimatePresence mode="wait">
<motion.div
key={activeYorum}
className="bg-[#FAF7F2] border border-[#1E2E38]/5 rounded-[36px] p-10 max-w-2xl mx-auto shadow-sm"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.4 }}>
<div className="flex gap-1 mb-6 justify-center">
{[...Array(yorumlar[activeYorum]?.puan)].map((_, i) => (
<span key={i} className="text-[#1E2E38] text-xl"></span>
))}
</div>
<p className="text-[#1E2E38]/80 text-lg leading-relaxed italic text-center mb-8 font-medium">
&ldquo;{yorumlar[activeYorum]?.yorum}&rdquo;
</p>
<div className="flex items-center gap-4 justify-center">
<div className="w-12 h-12 rounded-full flex items-center justify-center text-2xl bg-white shadow-sm border border-[#1E2E38]/5">
{yorumlar[activeYorum]?.emoji}
</div>
<div>
<p className="text-[#1E2E38] font-bold text-sm">{yorumlar[activeYorum]?.yazar}</p>
<p className="text-gray-400 text-xs">{yorumlar[activeYorum]?.tarih}</p>
</div>
</div>
</motion.div>
</AnimatePresence>
<div className="flex justify-center gap-2.5 mt-8">
{yorumlar.map((_, i) => (
<button key={i} onClick={() => setActiveYorum(i)}
className="w-2.5 h-2.5 rounded-full transition-all"
style={{ background: i === activeYorum ? "#1E2E38" : "rgba(30,46,56,0.15)",
transform: i === activeYorum ? "scale(1.3)" : "scale(1)" }} />
))}
</div>
</div>
</div>
</section>
{/* ── RANDEVU AL ── */}
<section id="randevu" className="py-24 bg-[#FAF7F2] px-6 border-t border-[#1E2E38]/5">
<div className="max-w-3xl mx-auto">
<div className="bg-white rounded-[40px] p-8 md:p-12 shadow-2xl border border-gray-100">
<div className="text-center mb-10">
<span className="inline-block text-[11px] font-black uppercase tracking-widest text-[#1E2E38]/60 mb-4">
004 - Online Appointment
</span>
<h2 className="text-3xl md:text-4xl font-display font-black text-[#1E2E38]">
Appointment Request
</h2>
<p className="text-gray-400 text-sm mt-2">
Hayalinizdeki sağlıklı gülüşe kavuşmak için formu doldurun, sizi arayalım.
</p>
</div>
<div className="grid grid-cols-2 gap-4">
{[
{ label: "Ad Soyad", placeholder: "Adınız Soyadınız", type: "text", full: false },
{ label: "Telefon", placeholder: "05xx xxx xx xx", type: "tel", full: false },
{ label: "E-posta", placeholder: "ornek@mail.com", type: "email", full: false },
{ label: "Hizmet Türü", placeholder: "", type: "select", full: false },
{ label: "Tercih Edilen Tarih", placeholder: "", type: "date", full: false },
{ label: "Şikayetiniz / Notunuz", placeholder: "Belirtmek istediğiniz detaylar...", type: "text", full: true },
].map((field, i) => (
<div key={i} className={field.full ? "col-span-2" : "col-span-1"}>
<label className="block text-[11px] font-bold text-[#1E2E38]/80 mb-1.5 uppercase tracking-wider">{field.label}</label>
{field.type === "select" ? (
<select className="w-full px-4 py-3 rounded-xl border border-gray-200 text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-[#1E2E38] bg-gray-50/50 transition-all">
<option>Prevent Cavities & Disease</option>
<option>Teeth Sparkling Cleaning</option>
<option>Teeth Straightening</option>
<option>Dental Implant</option>
</select>
) : (
<input type={field.type} placeholder={field.placeholder}
className="w-full px-4 py-3 rounded-xl border border-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-[#1E2E38] bg-gray-50/50 transition-all" />
)}
</div>
))}
</div>
<motion.button
className="mt-8 w-full py-4 rounded-full text-[#FAF7F2] font-black text-sm bg-[#1E2E38] hover:bg-[#121C22]"
whileHover={{ scale: 1.02, boxShadow: "0 10px 30px rgba(30, 46, 56, 0.2)" }}
whileTap={{ scale: 0.98 }}
>
📅 Randevu Talep Et
</motion.button>
</div>
</div>
</section>
{/* ── FOOTER ── */}
<footer className="bg-[#121C22] border-t border-white/10 px-6 pt-16 pb-8">
<div className="max-w-6xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-center pt-8 gap-4 text-white/40">
<p className="text-xs">© 2026 {firma.adi}. Tüm hakları saklıdır.</p>
<p className="text-[10px]">Gizlilik Politikası · Çerez Ayarları</p>
</div>
</div>
</footer>
{/* ── BOOKING MODAL ── */}
<AnimatePresence>
{showBookingModal && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
className="bg-white rounded-[36px] p-8 max-w-lg w-full relative shadow-2xl"
initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }}
>
<button
className="absolute top-6 right-6 w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center font-bold text-gray-500 hover:bg-gray-200 transition-colors"
onClick={() => setShowBookingModal(false)}
>
</button>
<h3 className="font-display font-black text-[#1E2E38] text-2xl mb-2">Hızlı İletişim</h3>
<p className="text-gray-400 text-xs mb-6">İletişim bilgilerinizi bırakın, en kısa sürede geri dönelim.</p>
<div className="space-y-4">
<div>
<label className="block text-[10px] font-bold text-gray-500 mb-1 uppercase">Ad Soyad</label>
<input type="text" className="w-full px-4 py-3 rounded-xl border bg-gray-50/50 text-sm focus:outline-none focus:ring-2 focus:ring-[#1E2E38]" placeholder="Adınız Soyadınız" />
</div>
<div>
<label className="block text-[10px] font-bold text-gray-500 mb-1 uppercase">Telefon Numarası</label>
<input type="tel" className="w-full px-4 py-3 rounded-xl border bg-gray-50/50 text-sm focus:outline-none focus:ring-2 focus:ring-[#1E2E38]" placeholder="05xx xxx xx xx" />
</div>
<motion.button
className="w-full py-4 rounded-full bg-[#1E2E38] text-[#FAF7F2] font-black text-sm hover:bg-[#121C22] transition-colors mt-2"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowBookingModal(false)}
>
Gönder
</motion.button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
+423
View File
@@ -0,0 +1,423 @@
"use client";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import { useRef, useState } from "react";
import type { DemoData } from "@/data/demos";
import AnimatedCounter from "@/components/ui/AnimatedCounter";
const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const fadeUp = {
hidden: { opacity: 0, y: 40 },
show: { opacity: 1, y: 0, transition: { duration: 0.7, ease: easeOutExpo } },
};
const stagger = {
hidden: {},
show: { transition: { staggerChildren: 0.1 } },
};
export default function KlinikTemplate({ data }: { data: DemoData }) {
const { firma, istatistikler, hizmetler, doktorlar, yorumlar } = data;
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "30%"]);
const heroOpacity = useTransform(scrollYProgress, [0, 0.8], [1, 0]);
const [activeYorum, setActiveYorum] = useState(0);
const [menuOpen, setMenuOpen] = useState(false);
return (
<div className="bg-[#FAF9F6] text-[#1C1917] font-body">
{/* ── HEADER ── */}
<motion.header
className="fixed top-0 left-0 right-0 z-50 px-6 pt-5"
initial={{ y: -80 }}
animate={{ y: 0 }}
transition={{ duration: 0.6, ease: easeOutExpo }}
>
<div className="mx-auto max-w-6xl px-6 h-16 flex items-center justify-between bg-[#FAF9F6]/80 backdrop-blur-md border border-[#E7E5E4] rounded-full">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-[#1C1917] flex items-center justify-center text-sm font-bold text-[#FAF9F6]">
{firma.logoEmoji}
</div>
<span className="font-display font-bold text-[15px] tracking-tight">{firma.adi}</span>
</div>
<nav className="hidden md:flex items-center gap-8">
{["Hizmetler", "Doktorlar", "Yorumlar", "İletişim"].map((item) => (
<a key={item} href={`#${item.toLowerCase()}`}
className="text-[12px] font-medium tracking-tight text-[#78716C] hover:text-[#1C1917] transition-colors duration-200">
{item}
</a>
))}
</nav>
<motion.a
href="#randevu"
className="hidden md:flex items-center gap-2 text-[12px] font-bold text-[#FAF9F6] bg-[#1C1917] px-5 py-2.5 rounded-full hover:bg-[#33302E] transition-colors"
whileTap={{ scale: 0.97 }}
>
Randevu Al
</motion.a>
<button className="md:hidden flex flex-col justify-center items-end gap-1 w-8 h-8 cursor-pointer" onClick={() => setMenuOpen(!menuOpen)}>
<span className={`h-0.5 bg-[#1C1917] transition-all duration-300 ${menuOpen ? "w-5 rotate-45 translate-y-1.5" : "w-5"}`} />
<span className={`h-0.5 bg-[#1C1917] transition-all duration-300 ${menuOpen ? "w-0 opacity-0" : "w-3"}`} />
<span className={`h-0.5 bg-[#1C1917] transition-all duration-300 ${menuOpen ? "w-5 -rotate-45 -translate-y-1.5" : "w-4"}`} />
</button>
</div>
{/* Mobile Dropdown */}
<AnimatePresence>
{menuOpen && (
<motion.div
className="md:hidden max-w-6xl mx-auto mt-2 bg-[#FAF9F6] border border-[#E7E5E4] rounded-2xl p-4 space-y-2"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3, ease: easeOutExpo }}
>
{["Hizmetler", "Doktorlar", "Yorumlar", "İletişim", "Randevu"].map(item => (
<a
key={item}
href={item === "Randevu" ? "#randevu" : `#${item.toLowerCase()}`}
className="block text-[14px] font-medium py-2 px-3 rounded-lg hover:bg-[#E7E5E4]/30 text-[#1C1917]"
onClick={() => setMenuOpen(false)}
>
{item}
</a>
))}
</motion.div>
)}
</AnimatePresence>
</motion.header>
{/* ── HERO ── */}
<section ref={heroRef} className="relative min-h-screen flex items-center overflow-hidden pt-24 pb-16 bg-[#FAF9F6]">
{/* Subtle grid pattern */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03]"
style={{ backgroundImage: "radial-gradient(#1C1917 1px, transparent 1px)", backgroundSize: "32px 32px" }} />
<div className="relative z-10 max-w-6xl mx-auto px-6 w-full grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
{/* Hero Left */}
<motion.div className="lg:col-span-8" variants={stagger} initial="hidden" animate="show">
<motion.div variants={fadeUp}
className="inline-flex items-center gap-2 text-[11px] font-bold uppercase tracking-wider text-[#78716C] mb-8"
>
<span className="w-1.5 h-1.5 rounded-full bg-[#10B981]" />
{firma.sehir}&apos;in Modern Sağlık Üssü
</motion.div>
<motion.h1 variants={fadeUp}
className="font-display font-black text-[12vw] sm:text-[8vw] lg:text-[5vw] leading-[0.9] tracking-tight uppercase text-[#1C1917] mb-8">
Sağlığınız İçin<br />
<span className="text-transparent stroke-text" style={{ WebkitTextStroke: "1px #1C1917" }}>En Doğru</span> Adım.
</motion.h1>
<motion.p variants={fadeUp} className="text-[#57534E] text-base sm:text-lg leading-relaxed max-w-xl mb-10 font-light">
{firma.slogan}. Son teknoloji teşhis altyapımız ve alanında uzman hekim kadromuz ile her hastamıza kişiselleştirilmiş bir bakım süreci vadediyoruz.
</motion.p>
<motion.div variants={fadeUp} className="flex flex-wrap gap-4 mb-12">
<motion.a href="#randevu"
className="flex items-center justify-center px-8 py-4 rounded-full font-bold text-[13px] tracking-wide uppercase bg-[#1C1917] text-[#FAF9F6] cursor-pointer hover:bg-[#33302E] transition-all duration-200"
whileTap={{ scale: 0.97 }}>
📅 Randevu Oluştur
</motion.a>
<motion.a href="#hizmetler"
className="flex items-center justify-center px-8 py-4 rounded-full font-bold text-[13px] tracking-wide uppercase border border-[#E7E5E4] text-[#1C1917] hover:bg-[#E7E5E4]/20 transition-all cursor-pointer duration-200"
whileTap={{ scale: 0.97 }}>
Hizmetler
</motion.a>
</motion.div>
{/* Stats */}
<motion.div variants={fadeUp} className="grid grid-cols-2 sm:grid-cols-4 gap-6 pt-10 border-t border-[#E7E5E4]">
{istatistikler.map(s => {
const val = parseInt(s.deger.replace(/\D/g, "")) || 0;
const suf = s.deger.replace(/[0-9]/g, "");
return (
<div key={s.etiket}>
<div className="text-3xl font-display font-black text-[#1C1917]">
<AnimatedCounter target={val} suffix={suf} />
</div>
<div className="text-[#78716C] text-[10px] uppercase tracking-wider font-semibold mt-1">{s.etiket}</div>
</div>
);
})}
</motion.div>
</motion.div>
{/* Hero Right */}
<motion.div
className="lg:col-span-4 hidden lg:block"
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, delay: 0.3, ease: easeOutExpo }}
style={{ y: heroY }}
>
<div className="bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl p-6 shadow-sm">
<div className="flex items-center justify-between border-b border-[#E7E5E4] pb-4 mb-4">
<span className="font-display font-bold text-[12px] uppercase tracking-wider text-[#78716C]">Günlük Randevu Akışı</span>
<span className="w-1.5 h-1.5 rounded-full bg-[#10B981] animate-pulse" />
</div>
<div className="space-y-3">
{[
{ saat: "09:30", tip: "Genel Muayene", dolu: true },
{ saat: "11:00", tip: "Check-Up Kontrol", dolu: false },
{ saat: "13:30", tip: "Kardiyoloji Muayenesi", dolu: true },
{ saat: "15:00", tip: "Göz Polikliniği", dolu: false },
].map((r, i) => (
<div key={i} className="flex items-center justify-between p-3 rounded-2xl bg-[#FAF9F6] border border-[#E7E5E4]/60">
<div className="flex items-center gap-3">
<span className={`w-2 h-2 rounded-full ${r.dolu ? "bg-red-400" : "bg-green-400"}`} />
<div>
<p className="text-[#1C1917] text-[12px] font-bold">{r.saat}</p>
<p className="text-[#78716C] text-[10px]">{r.tip}</p>
</div>
</div>
<span className={`text-[10px] font-bold px-2 py-0.5 rounded-full ${r.dolu ? "bg-red-50 text-red-500" : "bg-green-50 text-green-500"}`}>
{r.dolu ? "Dolu" : "Müsait"}
</span>
</div>
))}
</div>
</div>
</motion.div>
</div>
</section>
{/* ── HİZMETLER ── */}
<section id="hizmetler" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 border-b border-[#E7E5E4] pb-12 mb-16">
<div className="lg:col-span-5">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Tıbbi Birimlerimiz
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase leading-tight tracking-tight text-[#1C1917]">
Kapsamlı Tedavi Çözümleri
</h2>
</div>
<div className="lg:col-span-7 flex items-end">
<p className="text-[#57534E] text-base leading-relaxed font-light lg:pl-8 border-l border-transparent lg:border-[#E7E5E4]">
Her branşta yüksek teknolojik imkanlarla entegre edilmiş tanı ünitelerimiz ile kusursuz bir klinik deneyim sunuyoruz.
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{hizmetler.map((h, i) => (
<motion.div key={i}
className="group p-8 rounded-3xl border border-[#E7E5E4] bg-[#FAF9F6] hover:border-[#1C1917] transition-all duration-300 flex flex-col justify-between min-h-[220px]"
whileHover={{ y: -4 }}
initial="hidden"
whileInView="show"
viewport={{ once: true }}
variants={fadeUp}>
<div>
<div className="w-10 h-10 rounded-xl bg-[#1C1917]/5 flex items-center justify-center mb-6 group-hover:bg-[#1C1917] group-hover:text-[#FAF9F6] transition-colors duration-300">
<span className="text-xl">🩺</span>
</div>
<h3 className="font-display font-black text-xl uppercase text-[#1C1917] mb-2">{h.baslik}</h3>
<p className="text-[#57534E] text-[13px] leading-relaxed font-light">{h.aciklama}</p>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ── DOKTORLAR ── */}
{doktorlar && (
<section id="doktorlar" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-6xl mx-auto">
<div className="mb-16">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Hekim Kadromuz
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase tracking-tight text-[#1C1917]">
Alanında Öncü Akademik Ekip
</h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{doktorlar.map((d, i) => (
<motion.div key={i}
className="bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl overflow-hidden hover:border-[#1C1917] transition-all duration-300 flex flex-col justify-between"
whileHover={{ y: -6 }}
initial="hidden"
whileInView="show"
viewport={{ once: true }}
variants={fadeUp}>
<div className="h-44 bg-[#FAF9F6] border-b border-[#E7E5E4] flex items-center justify-center text-5xl relative">
<span className="z-10">{d.emoji}</span>
</div>
<div className="p-6">
<h3 className="font-display font-black text-[15px] uppercase text-[#1C1917]">{d.ad}</h3>
<p className="text-[12px] font-bold uppercase tracking-wider text-[#78716C] mt-0.5 mb-3">{d.uzmanlik}</p>
<div className="flex items-center justify-between text-[11px] text-[#78716C] pt-3 border-t border-[#E7E5E4]/60">
<span> {d.puan}</span>
<span>🏥 {d.yil} Yıl</span>
<span>👥 {d.hasta}+ Hasta</span>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
)}
{/* ── RANDEVU ── */}
<section id="randevu" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 items-start">
{/* Left */}
<div className="lg:col-span-5">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Hızlı Destek Hattı
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase tracking-tight text-[#1C1917] leading-tight mb-8">
İletişime Geçin
</h2>
<p className="text-[#57534E] text-base font-light mb-10">
Form üzerinden randevu talebi oluşturun. Tıbbi sekreterliğimiz detayları onaylamak üzere sizi en kısa sürede arayacaktır.
</p>
<div className="space-y-4">
{[
{ ikon: "📞", baslik: "Çağrı Merkezi", val: firma.telefon },
{ ikon: "📍", baslik: "Poliklinik Adresi", val: firma.adres },
{ ikon: "✉️", baslik: "Resmi Yazışmalar", val: firma.email },
].map((item) => (
<div key={item.baslik} className="flex gap-4 bg-[#FAF9F6] border border-[#E7E5E4] rounded-2xl p-4">
<span className="text-xl">{item.ikon}</span>
<div>
<p className="text-[#78716C] text-[10px] font-bold uppercase tracking-wider">{item.baslik}</p>
<p className="text-[#1C1917] text-[13px] font-semibold mt-0.5">{item.val}</p>
</div>
</div>
))}
</div>
</div>
{/* Right Form */}
<div className="lg:col-span-7 bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl p-8 shadow-sm">
<h3 className="font-display font-black text-xl uppercase text-[#1C1917] mb-6">Online Randevu Formu</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{[
{ label: "Hasta Adı Soyadı", placeholder: "Ad Soyad", type: "text" },
{ label: "Telefon Numarası", placeholder: "05xx xxx xx xx", type: "tel" },
{ label: "E-Posta Adresi", placeholder: "e-posta@adresiniz.com", type: "email" },
{ label: "Birim / Poliklinik", type: "select" },
{ label: "Tercih Edilen Tarih", type: "date" },
{ label: "Şikayet / Not", placeholder: "Tıbbi geçmişiniz veya eklemek istediğiniz notlar...", type: "textarea", full: true }
].map((field, i) => (
<div key={i} className={field.full ? "sm:col-span-2 flex flex-col" : "flex flex-col"}>
<label className="text-[11px] font-bold uppercase tracking-wider text-[#78716C] mb-2">{field.label}</label>
{field.type === "select" ? (
<select className="w-full px-0 py-3 bg-transparent border-b border-[#E7E5E4] text-[#1C1917] text-sm focus:outline-none focus:border-[#1C1917] transition-colors rounded-none appearance-none cursor-pointer">
<option>Dahiliye</option>
<option>Kardiyoloji</option>
<option>Göz Sağlığı</option>
<option>Diş Hekimliği</option>
</select>
) : field.type === "textarea" ? (
<textarea rows={3} placeholder={field.placeholder} className="w-full px-0 py-3 bg-transparent border-b border-[#E7E5E4] text-[#1C1917] placeholder-[#78716C]/40 text-sm focus:outline-none focus:border-[#1C1917] transition-colors resize-none rounded-none" />
) : (
<input type={field.type} placeholder={field.placeholder} className="w-full px-0 py-3 bg-transparent border-b border-[#E7E5E4] text-[#1C1917] placeholder-[#78716C]/40 text-sm focus:outline-none focus:border-[#1C1917] transition-colors rounded-none" />
)}
</div>
))}
</div>
<motion.button
className="mt-8 w-full py-4 rounded-full font-bold text-[13px] tracking-wide uppercase bg-[#1C1917] text-[#FAF9F6] hover:bg-[#33302E] transition-colors cursor-pointer"
whileTap={{ scale: 0.98 }}>
Randevu Talebi Gönder
</motion.button>
</div>
</div>
</div>
</section>
{/* ── YORUMLAR ── */}
<section id="yorumlar" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-3xl mx-auto">
<div className="mb-16 text-center">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Hasta Görüşleri
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase tracking-tight text-[#1C1917]">
Hasta Deneyimleri
</h2>
</div>
<div className="relative border border-[#E7E5E4] rounded-3xl p-8 bg-[#FAF9F6]">
<AnimatePresence mode="wait">
<motion.div
key={activeYorum}
className="min-h-[160px] flex flex-col justify-between"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.4 }}>
<div>
<div className="flex gap-1 mb-4">
{[...Array(yorumlar[activeYorum].puan)].map((_, i) => (
<span key={i} className="text-yellow-500 text-lg"></span>
))}
</div>
<p className="text-[#57534E] text-base leading-relaxed italic font-light mb-6">
&ldquo;{yorumlar[activeYorum].yorum}&rdquo;
</p>
</div>
<div className="flex items-center gap-3 pt-4 border-t border-[#E7E5E4]/60">
<span className="text-2xl">{yorumlar[activeYorum].emoji}</span>
<div>
<p className="text-[#1C1917] font-bold text-sm uppercase tracking-tight">{yorumlar[activeYorum].yazar}</p>
<p className="text-[#78716C] text-[11px]">{yorumlar[activeYorum].tarih}</p>
</div>
</div>
</motion.div>
</AnimatePresence>
<div className="flex justify-center gap-2 mt-8">
{yorumlar.map((_, i) => (
<button key={i} onClick={() => setActiveYorum(i)}
className="w-2 h-2 rounded-full cursor-pointer transition-all"
style={{ background: i === activeYorum ? "#1C1917" : "#E7E5E4",
transform: i === activeYorum ? "scale(1.2)" : "scale(1)" }} />
))}
</div>
</div>
</div>
</section>
{/* ── FOOTER ── */}
<footer className="border-t border-[#E7E5E4] bg-[#FAF9F6] px-6 pt-16 pb-8">
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-4 text-[12px] text-[#78716C]">
<p>© 2026 {firma.adi}. Tüm hakları saklıdır.</p>
<div className="flex gap-6">
<a href="#" className="hover:text-[#1C1917] font-light">Gizlilik Bildirgesi</a>
<a href="#" className="hover:text-[#1C1917] font-light">Çerez Ayarları</a>
</div>
</div>
</footer>
<style jsx global>{`
.stroke-text {
font-weight: 900;
color: transparent;
}
`}</style>
</div>
);
}
+335
View File
@@ -0,0 +1,335 @@
"use client";
import { motion, useScroll, useTransform } from "framer-motion";
import { useRef } from "react";
import type { DemoData } from "@/data/demos";
import AnimatedCounter from "@/components/ui/AnimatedCounter";
const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const fadeUp = {
hidden: { opacity: 0, y: 40 },
show: { opacity: 1, y: 0, transition: { duration: 0.7, ease: easeOutExpo } },
};
const stagger = { hidden: {}, show: { transition: { staggerChildren: 0.1 } } };
export default function KurumsalTemplate({ data }: { data: DemoData }) {
const { firma, istatistikler, hizmetler, yorumlar, projeler = [], ekip = [] } = data;
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "20%"]);
return (
<div className="bg-[#FAF9F6] text-[#1C1917] font-body">
{/* ── HEADER ── */}
<motion.header
className="fixed top-0 left-0 right-0 z-50 px-6 pt-5"
initial={{ y: -80 }}
animate={{ y: 0 }}
transition={{ duration: 0.6, ease: easeOutExpo }}>
<div className="mx-auto max-w-6xl px-6 h-16 flex items-center justify-between bg-[#FAF9F6]/80 backdrop-blur-md border border-[#E7E5E4] rounded-full">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-[#1C1917] flex items-center justify-center text-sm font-bold text-[#FAF9F6]">
{firma.logoEmoji}
</div>
<span className="font-display font-bold text-[15px] tracking-tight">{firma.adi}</span>
</div>
<nav className="hidden md:flex items-center gap-8">
{["Hizmetler", "Projeler", "Ekip", "İletişim"].map(item => (
<a key={item} href={`#${item.toLowerCase()}`}
className="text-[12px] font-medium tracking-tight text-[#78716C] hover:text-[#1C1917] transition-colors">
{item}
</a>
))}
</nav>
<motion.a href="#iletisim"
className="hidden md:flex items-center gap-2 text-[12px] font-bold text-[#FAF9F6] bg-[#1C1917] px-5 py-2.5 rounded-full hover:bg-[#33302E] transition-colors"
whileTap={{ scale: 0.97 }}>
Teklif Al
</motion.a>
</div>
</motion.header>
{/* ── HERO ── */}
<section ref={heroRef} className="relative min-h-screen flex items-center overflow-hidden bg-[#FAF9F6] pt-24">
{/* Subtle grid pattern */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03]"
style={{ backgroundImage: "radial-gradient(#1C1917 1px, transparent 1px)", backgroundSize: "32px 32px" }} />
<div className="relative z-10 max-w-6xl mx-auto px-6 w-full grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
<motion.div className="lg:col-span-8" variants={stagger} initial="hidden" animate="show">
<motion.div variants={fadeUp}
className="inline-flex items-center gap-2 text-[11px] font-bold uppercase tracking-wider text-[#78716C] mb-8"
>
<span className="w-1.5 h-1.5 rounded-full bg-[#10B981]" />
{firma.sehir} · {istatistikler[0]?.deger} Lojistik Hizmeti
</motion.div>
<motion.h1 variants={fadeUp}
className="font-display font-black text-[12vw] sm:text-[8vw] lg:text-[5vw] leading-[0.9] tracking-tight uppercase text-[#1C1917] mb-8">
Lojistikte<br />
<span className="text-transparent stroke-text" style={{ WebkitTextStroke: "1px #1C1917" }}>Yeni Nesil</span> Ağı.
</motion.h1>
<motion.p variants={fadeUp} className="text-[#57534E] text-base sm:text-lg leading-relaxed max-w-xl mb-10 font-light">
{firma.slogan}. Tedarik zincirinizin uçtan uca akışını optimize etmek için geliştirilmiş son derece güvenli ve anlık takip edilebilen operasyon sistemleri.
</motion.p>
<motion.div variants={fadeUp} className="flex flex-wrap gap-4 mb-12">
<motion.a href="#iletisim"
className="flex items-center justify-center px-8 py-4 rounded-full font-bold text-[13px] tracking-wide uppercase bg-[#1C1917] text-[#FAF9F6] cursor-pointer hover:bg-[#33302E] transition-all duration-200"
whileTap={{ scale: 0.97 }}>
Teklif Al
</motion.a>
<motion.a href="#projeler"
className="flex items-center justify-center px-8 py-4 rounded-full font-bold text-[13px] tracking-wide uppercase border border-[#E7E5E4] text-[#1C1917] hover:bg-[#E7E5E4]/20 transition-all cursor-pointer duration-200"
whileTap={{ scale: 0.97 }}>
Referanslar
</motion.a>
</motion.div>
{/* Stats row */}
<motion.div variants={fadeUp}
className="grid grid-cols-2 sm:grid-cols-4 gap-6 pt-10 border-t border-[#E7E5E4]">
{istatistikler.map((s, i) => (
<div key={i}>
<div className="text-3xl font-display font-black text-[#1C1917]">
<AnimatedCounter target={parseInt(s.deger.replace(/\D/g, "")) || 0}
suffix={s.deger.replace(/[0-9]/g, "")} />
</div>
<div className="text-[#78716C] text-[10px] uppercase tracking-wider font-semibold mt-1">{s.etiket}</div>
</div>
))}
</motion.div>
</motion.div>
{/* Bento grid right side */}
<motion.div
className="lg:col-span-4 hidden lg:grid grid-cols-2 gap-4"
initial={{ opacity: 0, x: 60 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.9, delay: 0.3 }}
style={{ y: heroY }}
>
{hizmetler.slice(0, 4).map((h, i) => (
<div key={i} className="bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl p-6 shadow-sm">
<div className="w-10 h-10 rounded-xl bg-[#1C1917]/5 flex items-center justify-center mb-4 text-xl">
{h.ikon}
</div>
<h3 className="font-display font-black text-[12px] uppercase text-[#1C1917] mb-1">{h.baslik}</h3>
<p className="text-[#78716C] text-[10px] leading-relaxed font-light">{h.aciklama}</p>
</div>
))}
</motion.div>
</div>
</section>
{/* ── HİZMETLER TAM ── */}
<section id="hizmetler" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 border-b border-[#E7E5E4] pb-12 mb-16">
<div className="lg:col-span-5">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Uçtan Uca Çözümler
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase leading-tight tracking-tight text-[#1C1917]">
Kapsamlı Lojistik Paketi
</h2>
</div>
<div className="lg:col-span-7 flex items-end">
<p className="text-[#57534E] text-base leading-relaxed font-light lg:pl-8 border-l border-transparent lg:border-[#E7E5E4]">
Operasyonel mükemmeliyetçiliği temel alan depolama, yurt içi dağıtım ve anlık takip sistemlerimizle tedarik zincirinize tam entegrasyon sunuyoruz.
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{hizmetler.map((h, i) => (
<motion.div key={i}
className="bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl p-8 hover:border-[#1C1917] transition-all duration-300 flex flex-col justify-between min-h-[220px]"
whileHover={{ y: -4 }}
initial="hidden"
whileInView="show"
viewport={{ once: true }}
variants={fadeUp}>
<div>
<div className="w-10 h-10 rounded-xl bg-[#1C1917]/5 flex items-center justify-center mb-6 text-xl">
{h.ikon}
</div>
<h3 className="font-display font-black text-xl uppercase text-[#1C1917] mb-2">{h.baslik}</h3>
<p className="text-[#57534E] text-[13px] leading-relaxed font-light">{h.aciklama}</p>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ── REFERANSLAR ── */}
<section id="projeler" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-6xl mx-auto">
<div className="mb-16">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Güçlü Referanslar
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase tracking-tight text-[#1C1917]">
Birlikte Yol Aldıklarımız
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{projeler.map((p, i) => (
<motion.div key={i}
className="bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl p-7 flex flex-col justify-between hover:border-[#1C1917] transition-all duration-300 relative overflow-hidden"
whileHover={{ y: -4 }}
initial="hidden"
whileInView="show"
viewport={{ once: true }}
variants={fadeUp}>
<div className="flex items-start gap-5">
<div className="w-14 h-14 rounded-2xl flex items-center justify-center text-2xl shrink-0"
style={{ background: `${p.renk}10` }}>
{p.ikon}
</div>
<div>
<div className="flex items-center gap-3 mb-2">
<h3 className="font-display font-black text-[16px] uppercase text-[#1C1917]">{p.baslik}</h3>
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full"
style={{ background: `${p.renk}10`, color: p.renk }}>
{p.sektor}
</span>
</div>
<p className="text-[#57534E] text-[13px] leading-relaxed font-light">{p.aciklama}</p>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ── EKİP ── */}
{ekip.length > 0 && (
<section id="ekip" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-6xl mx-auto">
<div className="mb-16">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Operasyon Liderleri
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase tracking-tight text-[#1C1917]">
Yönetim Ekibimiz
</h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{ekip.map((k, i) => (
<motion.div key={i}
className="bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl overflow-hidden hover:border-[#1C1917] transition-all duration-300 flex flex-col justify-between"
whileHover={{ y: -6 }}
initial="hidden"
whileInView="show"
viewport={{ once: true }}
variants={fadeUp}>
<div className="h-40 bg-[#FAF9F6] border-b border-[#E7E5E4] flex items-center justify-center text-5xl">
<span>{k.emoji}</span>
</div>
<div className="p-6">
<h3 className="font-display font-black text-[15px] uppercase text-[#1C1917]">{k.ad}</h3>
<p className="text-[12px] font-bold uppercase tracking-wider text-[#78716C] mt-0.5">{k.rol}</p>
</div>
</motion.div>
))}
</div>
</div>
</section>
)}
{/* ── YORUMLAR ── */}
<section id="yorumlar" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-6xl mx-auto">
<div className="mb-16">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Müşteri Deneyimleri
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase tracking-tight text-[#1C1917]">
Ortaklarımız Ne Söylüyor?
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{yorumlar.map((y, i) => (
<motion.div key={i}
className="bg-[#FAF9F6] border border-[#E7E5E4] rounded-3xl p-7 hover:border-[#1C1917] transition-all duration-300 flex flex-col justify-between"
whileHover={{ y: -4 }}
initial="hidden"
whileInView="show"
viewport={{ once: true }}
variants={fadeUp}>
<div>
<div className="flex gap-1 mb-4">
{[...Array(y.puan)].map((_, j) => (
<span key={j} className="text-[#10B981]"></span>
))}
</div>
<p className="text-[#57534E] text-[13px] leading-relaxed italic mb-6 font-light">&ldquo;{y.yorum}&rdquo;</p>
</div>
<div className="flex items-center gap-3 pt-4 border-t border-[#E7E5E4]/60">
<span className="text-xl">{y.emoji}</span>
<div>
<p className="text-[#1C1917] font-bold text-xs uppercase tracking-tight">{y.yazar}</p>
<p className="text-gray-400 text-[10px] mt-0.5">{y.tarih}</p>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ── İLETİŞİM ── */}
<section id="iletisim" className="py-24 px-6 border-t border-[#E7E5E4] bg-[#FAF9F6]">
<div className="max-w-4xl mx-auto text-center">
<span className="inline-block text-[11px] font-bold uppercase tracking-widest px-3 py-1 bg-[#FAF9F6] border border-[#E7E5E4] rounded-full text-[#78716C] mb-4">
Operational Blueprint
</span>
<h2 className="font-display font-black text-4xl sm:text-5xl uppercase tracking-tight text-[#1C1917] mb-6">
Birlikte Değer Katalım
</h2>
<p className="text-[#57534E] text-lg font-light mb-10 max-w-xl mx-auto">
Hacminiz ve operasyon sıklığınız ne olursa olsun, lojistik süreçlerinizi en güvenli hatlarla optimize etmek üzere teklif isteyin.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<motion.a href={`tel:${firma.telefon}`}
className="flex items-center justify-center gap-2 px-8 py-4 rounded-full bg-[#1C1917] text-[#FAF9F6] font-bold text-[13px] tracking-wide uppercase hover:bg-[#33302E] transition-colors"
whileHover={{ scale: 1.04 }} whileTap={{ scale: 0.97 }}>
📞 {firma.telefon}
</motion.a>
<motion.a href={`mailto:${firma.email}`}
className="flex items-center justify-center gap-2 px-8 py-4 rounded-full border border-[#E7E5E4] text-[#1C1917] font-bold text-[13px] tracking-wide uppercase hover:bg-[#E7E5E4]/20 transition-all"
whileHover={{ scale: 1.04 }} whileTap={{ scale: 0.97 }}>
{firma.email}
</motion.a>
</div>
</div>
</section>
{/* ── FOOTER ── */}
<footer className="border-t border-[#E7E5E4] bg-[#FAF9F6] px-6 pt-16 pb-8">
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-4 text-[12px] text-[#78716C]">
<p>© 2026 {firma.adi}. Tüm hakları saklıdır.</p>
<p className="hover:text-[#1C1917] transition-colors font-light">Gizlilik Politikası</p>
</div>
</footer>
<style jsx global>{`
.stroke-text {
font-weight: 900;
color: transparent;
}
`}</style>
</div>
);
}
+508
View File
@@ -0,0 +1,508 @@
"use client";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import { useRef, useState } from "react";
import type { DemoData } from "@/data/demos";
import AnimatedCounter from "@/components/ui/AnimatedCounter";
const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const fadeUp = {
hidden: { opacity: 0, y: 40 },
show: { opacity: 1, y: 0, transition: { duration: 0.7, ease: easeOutExpo } },
};
const stagger = {
hidden: {},
show: { transition: { staggerChildren: 0.1 } },
};
export default function RestoranTemplate({ data }: { data: DemoData }) {
const { firma, istatistikler, hizmetler, menu = [], yorumlar = [] } = data;
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "20%"]);
const heroOpacity = useTransform(scrollYProgress, [0, 0.8], [1, 0]);
const [aktifKategori, setAktifKategori] = useState(0);
const [showBookingModal, setShowBookingModal] = useState(false);
// SVG Render Helper for clean iconography (Replaces all emojis)
const getIconSvg = (name: string) => {
switch (name) {
case "alacarte":
return (
<svg className="w-6 h-6 stroke-[#C5A880]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" />
</svg>
);
case "special":
return (
<svg className="w-6 h-6 stroke-[#C5A880]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
);
case "catering":
return (
<svg className="w-6 h-6 stroke-[#C5A880]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>
);
case "delivery":
return (
<svg className="w-6 h-6 stroke-[#C5A880]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 18.75a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM19.5 18.75a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84" />
</svg>
);
default:
return (
<svg className="w-6 h-6 stroke-[#C5A880]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21" />
</svg>
);
}
};
return (
<div className="bg-[#090A0C] text-white font-body">
{/* ── FLOATING NAVIGATION HEADER ── */}
<motion.header
className="fixed top-4 left-4 right-4 z-50 px-4"
initial={{ y: -80, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.7, ease: easeOutExpo }}
>
<div className="mx-auto max-w-6xl h-20 flex items-center justify-between px-8 bg-[#090A0C]/80 backdrop-blur-xl border border-[#C5A880]/20 rounded-2xl shadow-2xl">
<a href="#" className="flex items-center gap-3 group">
<span className="font-display text-2xl font-bold tracking-widest text-white group-hover:text-[#C5A880] transition-colors uppercase">
{firma.adi}
</span>
</a>
<nav className="hidden lg:flex items-center gap-10">
{[
{ label: "Our Heritage", href: "#heritage" },
{ label: "Special Meal", href: "#special-meal" },
{ label: "Chalkboard Bakery", href: "#bakery" },
{ label: "Hizmetlerimiz", href: "#services" },
{ label: "Rezervasyon", href: "#randevu" }
].map(item => (
<a
key={item.label}
href={item.href}
className="text-[11px] font-sans-clean font-bold tracking-[2px] uppercase text-white/60 hover:text-white transition-colors cursor-pointer"
>
{item.label}
</a>
))}
</nav>
<div className="flex items-center gap-6">
<motion.button
onClick={() => setShowBookingModal(true)}
className="text-[11px] font-sans-clean font-black tracking-[2px] uppercase text-[#C5A880] border border-[#C5A880] hover:bg-[#C5A880] hover:text-[#090A0C] px-6 py-3 rounded-xl transition-all cursor-pointer"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
>
Enquiry
</motion.button>
</div>
</div>
</motion.header>
{/* ── HERO SECTION ── */}
<section ref={heroRef} className="relative min-h-screen flex items-center justify-center overflow-hidden bg-[#090A0C]">
<motion.div
className="absolute inset-0 pointer-events-none"
style={{ y: heroY, opacity: heroOpacity }}
>
<div className="absolute inset-0 bg-gradient-to-b from-black/80 via-black/60 to-[#090A0C] z-10" />
</motion.div>
<div className="relative z-20 max-w-5xl mx-auto px-6 text-center pt-24">
<motion.div
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col items-center"
>
<motion.span
variants={fadeUp}
className="text-[#C5A880] italic text-lg lg:text-xl tracking-wider mb-6 block font-display"
>
welcome to our delicious corner
</motion.span>
<motion.h1
variants={fadeUp}
className="font-display text-5xl md:text-7xl lg:text-8xl font-black text-white leading-[1.05] tracking-widest uppercase mb-8"
>
Best Famous
<br />
Dishes
</motion.h1>
<motion.p
variants={fadeUp}
className="text-white/60 font-sans-clean font-medium text-sm lg:text-base leading-relaxed max-w-lg mb-12"
>
A journey of raw ingredients transformed by fire, smoke, and heritage. Exquisite tastes curated for refined palates.
</motion.p>
<motion.div variants={fadeUp}>
<motion.a
href="#special-meal"
className="inline-flex items-center gap-3 text-[11px] font-sans-clean font-bold tracking-[3px] uppercase border-b-2 border-[#C5A880] text-white hover:text-[#C5A880] pb-2 transition-colors cursor-pointer"
whileHover={{ y: -3 }}
>
Learn More
</motion.a>
</motion.div>
</motion.div>
</div>
</section>
{/* ── SECTION 2: HERITAGE ── */}
<section id="heritage" className="py-32 bg-[#090A0C] border-t border-[#C5A880]/15 relative z-10 px-6">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-24">
<span className="text-[#C5A880] font-display italic text-base tracking-wider block mb-3">Special moments</span>
<h2 className="font-display text-4xl lg:text-5xl font-black tracking-widest text-white uppercase">
About Us
</h2>
</div>
<div className="grid lg:grid-cols-3 gap-8 items-stretch">
<div className="bg-[#13151A] rounded-2xl p-10 border border-[#C5A880]/10 flex flex-col items-center justify-center text-center shadow-2xl relative min-h-[300px]">
<span className="text-[#C5A880] text-xs tracking-wider mb-4 block">Taste perception</span>
<h3 className="font-display text-2xl lg:text-3xl font-black text-white tracking-widest uppercase mb-6 leading-tight">
Traditional
<br />
& Modern
</h3>
<p className="text-white/50 font-sans-clean text-xs leading-relaxed max-w-xs mb-8">
Palermonun en eski geleneksel tekniklerini modern gastronomi vizyonuyla harmanlıyoruz. Her ayrıntıda yüksek zanaatkarlık yatıyor.
</p>
<motion.button
onClick={() => setShowBookingModal(true)}
className="text-[10px] font-sans-clean font-bold tracking-[2px] uppercase text-[#C5A880] border border-[#C5A880]/40 hover:border-[#C5A880] px-5 py-3 rounded-lg transition-colors cursor-pointer"
whileHover={{ scale: 1.04 }}
>
Read More
</motion.button>
</div>
<div className="bg-[#13151A] rounded-2xl p-10 border border-[#C5A880]/10 flex flex-col items-center justify-center text-center shadow-2xl relative">
<span className="text-[#C5A880] text-xs tracking-wider mb-4 block">Crafted Flavours</span>
<h3 className="font-display text-2xl lg:text-3xl font-black text-white tracking-widest uppercase mb-6 leading-tight">
Wood &amp; Fire
</h3>
<p className="text-white/50 font-sans-clean text-xs leading-relaxed max-w-xs">
Ustalık gerektiren fırınlama ve pişirme aşamalarından geçerek servis edilen tabaklarımız, damak tadınızda unutulmaz izler bırakır.
</p>
</div>
<div className="bg-[#13151A] rounded-2xl p-10 border border-[#C5A880]/10 flex flex-col items-center justify-center text-center shadow-2xl relative">
<span className="text-[#C5A880] text-xs tracking-wider mb-4 block">Atmosphere</span>
<h3 className="font-display text-2xl lg:text-3xl font-black text-white tracking-widest uppercase mb-6 leading-tight">
Refined Space
</h3>
<p className="text-white/50 font-sans-clean text-xs leading-relaxed max-w-xs">
Özenle dekore edilmiş loş, taş döşemeli şık salonumuzda kendinizi İtalya sokaklarında hissedeceksiniz.
</p>
</div>
</div>
</div>
</section>
{/* ── SECTION 3: SPECIAL MEAL ── */}
<section id="special-meal" className="py-32 bg-[#0E0F12] border-t border-[#C5A880]/15 relative z-10 px-6">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<span className="text-[#C5A880] font-display italic text-base tracking-wider block mb-4">our full menu</span>
<h2 className="font-display text-4xl lg:text-5xl font-black tracking-widest text-white uppercase mb-16">
Special Meal
</h2>
</div>
<div className="grid lg:grid-cols-12 gap-16 items-start">
<div className="lg:col-span-12 flex flex-col justify-center">
<div className="space-y-6 max-w-4xl mx-auto w-full">
{menu[aktifKategori]?.urunler.map((item) => (
<div key={item.ad} className="flex flex-col cursor-pointer group">
<div className="flex items-end justify-between gap-4">
<span className="font-display text-base lg:text-lg font-bold text-white group-hover:text-[#C5A880] transition-colors uppercase">
{item.ad}
</span>
<div className="grow border-b border-dashed border-[#C5A880]/20 mb-2 group-hover:border-[#C5A880]/40 transition-colors mx-2" />
<span className="font-display text-base font-black text-[#C5A880] shrink-0">
{item.fiyat}
</span>
</div>
<p className="text-white/50 font-sans-clean text-[11px] mt-1 leading-relaxed">
{item.aciklama}
</p>
</div>
))}
</div>
{/* Category Tab Buttons */}
<div className="flex items-center justify-center gap-4 mt-16 flex-wrap">
{menu.map((kat, idx) => (
<button
key={kat.kategori}
onClick={() => setAktifKategori(idx)}
className="text-[10px] font-sans-clean font-bold tracking-[2px] uppercase px-5 py-3 rounded-lg border transition-all cursor-pointer"
style={aktifKategori === idx
? { backgroundColor: "#C5A880", color: "#090A0C", borderColor: "#C5A880" }
: { backgroundColor: "transparent", color: "rgba(255,255,255,0.5)", borderColor: "rgba(197,168,128,0.2)" }
}
>
{kat.kategori}
</button>
))}
</div>
</div>
</div>
</div>
</section>
{/* ── SECTION 5: SERVICES ── */}
<section id="services" className="py-24 bg-[#090A0C] border-t border-[#C5A880]/15 relative z-10 px-6">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<span className="text-[#C5A880] font-display italic text-base tracking-wider block mb-3">Our Offerings</span>
<h2 className="font-display text-3xl lg:text-4xl font-black tracking-widest text-white uppercase">
Hizmetlerimiz
</h2>
</div>
<div className="grid md:grid-cols-4 gap-6">
{hizmetler.map((h, i) => (
<motion.div
key={h.baslik}
className="bg-[#13151A] rounded-xl p-8 border border-white/5 hover:border-[#C5A880]/30 hover:bg-[#13151A]/80 transition-all duration-300 flex flex-col items-center text-center group cursor-default"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.1 }}
whileHover={{ y: -4 }}
>
<div className="w-14 h-14 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center mb-5 transition-all group-hover:bg-[#C5A880]/10 group-hover:border-[#C5A880]/30">
{getIconSvg(h.ikon)}
</div>
<h3 className="font-display font-bold text-white text-sm mb-3 uppercase tracking-wider leading-snug">
{h.baslik}
</h3>
<p className="text-white/50 font-sans-clean text-[10px] leading-relaxed">
{h.aciklama}
</p>
</motion.div>
))}
</div>
</div>
</section>
{/* ── SECTION 6: TESTIMONIALS ── */}
<section id="yorumlar" className="py-28 bg-[#0E0F12] border-t border-[#C5A880]/15 px-6">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<span className="text-[#C5A880] font-display italic text-base tracking-wider block mb-3">social proof</span>
<h2 className="font-display text-3xl lg:text-4xl font-black tracking-widest text-white uppercase">
Misafir Deneyimleri
</h2>
</div>
<div className="grid md:grid-cols-3 gap-6">
{yorumlar.map((y, i) => (
<motion.div
key={y.yazar}
className="bg-[#13151A] border border-[#C5A880]/10 rounded-xl p-8 flex flex-col justify-between"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.1 }}
whileHover={{ y: -4, borderColor: "rgba(197,168,128,0.3)" }}
>
<div className="flex gap-1 mb-6">
{[...Array(y.puan)].map((_, j) => (
<span key={j} className="text-[#C5A880]"></span>
))}
</div>
<p className="text-white/80 font-sans-clean text-xs leading-relaxed italic mb-8">
&ldquo;{y.yorum}&rdquo;
</p>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full border border-[#C5A880]/30 flex items-center justify-center text-lg bg-[#090A0C] shrink-0">
{y.emoji}
</div>
<div>
<h4 className="font-display text-[#C5A880] text-xs font-bold uppercase tracking-wider">
{y.yazar}
</h4>
<span className="font-sans-clean text-[9px] text-white/40 uppercase tracking-widest">
{y.tarih}
</span>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ── SECTION 7: RESERVATION ── */}
<section id="randevu" className="py-32 bg-[#090A0C] border-t border-[#C5A880]/15 relative z-10 px-6">
<div className="max-w-3xl mx-auto">
<div className="bg-[#13151A] rounded-2xl p-8 md:p-12 shadow-2xl border border-[#C5A880]/20">
<div className="text-center mb-10">
<span className="text-[#C5A880] font-display italic text-base tracking-wider block mb-3">booking reservation</span>
<h2 className="font-display text-3xl md:text-4xl font-black tracking-widest text-white uppercase">
Masa Rezervasyonu
</h2>
<p className="text-white/60 font-sans-clean text-xs mt-2 leading-relaxed">
Benzersiz bir fine dining deneyimi için masanızı önceden ayırtın.
</p>
</div>
<div className="grid grid-cols-2 gap-5">
{[
{ label: "Ad Soyad", placeholder: "Adınız Soyadınız", type: "text", full: false },
{ label: "Telefon", placeholder: "05xx xxx xx xx", type: "tel", full: false },
{ label: "Tarih", placeholder: "", type: "date", full: false },
{ label: "Saat", placeholder: "", type: "time", full: false },
{ label: "Kişi Sayısı", placeholder: "2", type: "number", full: false },
{ label: "Özel İstek / Not", placeholder: "Diyet hassasiyetleri, kutlamalar vb.", type: "text", full: true },
].map((field, i) => (
<div key={i} className={field.full ? "col-span-2" : "col-span-1"}>
<label className="block text-[9px] font-sans-clean font-bold text-[#C5A880] mb-1.5 uppercase tracking-widest">{field.label}</label>
<input
type={field.type}
placeholder={field.placeholder}
className="w-full px-4 py-3 rounded-xl border border-white/10 bg-[#090A0C] text-sm text-white focus:outline-none focus:ring-1 focus:ring-[#C5A880] placeholder-white/20 transition-all font-sans-clean"
/>
</div>
))}
</div>
<motion.button
className="mt-8 w-full py-4 rounded-xl text-[#090A0C] font-sans-clean font-black tracking-[2px] uppercase bg-[#C5A880] hover:bg-[#D5B990] transition-colors cursor-pointer text-xs"
whileHover={{ scale: 1.02, boxShadow: "0 10px 30px rgba(197, 168, 128, 0.25)" }}
whileTap={{ scale: 0.98 }}
>
Masa Ayırt
</motion.button>
</div>
</div>
</section>
{/* ── FOOTER ── */}
<footer className="bg-[#090A0C] border-t border-white/5 px-6 pt-20 pb-8 relative z-10">
<div className="max-w-7xl mx-auto">
<div className="grid md:grid-cols-4 gap-12 pb-16 border-b border-white/5">
<div className="md:col-span-2">
<span className="font-display text-2xl font-black text-[#C5A880] tracking-widest uppercase mb-4 block">
{firma.adi}
</span>
<p className="text-white/60 font-sans-clean text-xs leading-relaxed max-w-xs mb-8">
{firma.slogan} Geleneksel zanaat, modern vizyon.
</p>
<p className="text-white/60 font-sans-clean text-xs">📍 {firma.adres}</p>
<p className="text-white/60 font-sans-clean text-xs mt-1">📞 {firma.telefon}</p>
</div>
<div>
<h4 className="font-display text-white text-xs font-bold uppercase tracking-wider mb-5">
Şefin Menüsü
</h4>
{menu.map(kat => (
<a
key={kat.kategori}
href="#special-meal"
className="block text-white/50 hover:text-white text-xs mb-2.5 transition-colors font-sans-clean uppercase tracking-wider"
>
{kat.kategori}
</a>
))}
</div>
<div>
<h4 className="font-display text-white text-xs font-bold uppercase tracking-wider mb-5">
Karaköy Lezzet
</h4>
{["Heritage", "Special Meal", "Reservation"].map(item => (
<a
key={item}
href="#"
className="block text-white/50 hover:text-white text-xs mb-2.5 transition-colors font-sans-clean"
>
{item}
</a>
))}
</div>
</div>
<div className="flex flex-col md:flex-row justify-between items-center pt-8 gap-4 font-sans-clean text-white/40">
<p className="text-xs">© 2026 {firma.adi}. Tüm hakları saklıdır.</p>
<p className="text-[10px]">Gizlilik Politikası · Çerez Ayarları</p>
</div>
</div>
</footer>
{/* ── FAST BOOKING OVERLAY MODAL ── */}
<AnimatePresence>
{showBookingModal && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-6 bg-black/80 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
className="bg-[#13151A] rounded-2xl p-8 max-w-md w-full relative shadow-2xl border border-[#C5A880]/30"
initial={{ scale: 0.95, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.95, y: 20 }}
>
<button
className="absolute top-6 right-6 w-8 h-8 rounded-full bg-white/5 flex items-center justify-center text-white/60 hover:text-white transition-colors"
onClick={() => setShowBookingModal(false)}
>
</button>
<h3 className="font-display font-black text-white text-xl mb-1 uppercase tracking-wider">
Hızlı Rezervasyon
</h3>
<p className="text-white/50 font-sans-clean text-[10px] mb-6 uppercase tracking-widest">
Karaköy Rezervasyon Hattı
</p>
<div className="space-y-4">
<div>
<label className="block text-[9px] font-sans-clean font-bold text-[#C5A880] mb-1 uppercase tracking-wider">Ad Soyad</label>
<input type="text" className="w-full px-4 py-3 rounded-xl border border-white/10 bg-[#090A0C] text-sm text-white focus:outline-none focus:ring-1 focus:ring-[#C5A880]" placeholder="Adınız Soyadınız" />
</div>
<div>
<label className="block text-[9px] font-sans-clean font-bold text-[#C5A880] mb-1 uppercase tracking-wider">Telefon Numarası</label>
<input type="tel" className="w-full px-4 py-3 rounded-xl border border-white/10 bg-[#090A0C] text-sm text-white focus:outline-none focus:ring-1 focus:ring-[#C5A880]" placeholder="05xx xxx xx xx" />
</div>
<motion.button
className="w-full py-4 rounded-xl bg-[#C5A880] text-[#090A0C] font-sans-clean font-black text-xs uppercase tracking-wider mt-4"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowBookingModal(false)}
>
Bilgileri Gönder
</motion.button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
+460
View File
@@ -0,0 +1,460 @@
"use client";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import { useRef, useState } from "react";
import type { DemoData } from "@/data/demos";
import AnimatedCounter from "@/components/ui/AnimatedCounter";
const easeOutExpo = [0.16, 1, 0.3, 1] as [number, number, number, number];
const fadeUp = {
hidden: { opacity: 0, y: 40 },
show: { opacity: 1, y: 0, transition: { duration: 0.7, ease: easeOutExpo } },
};
const stagger = {
hidden: {},
show: { transition: { staggerChildren: 0.1 } },
};
export default function RestoranTemplate2({ data }: { data: DemoData }) {
const { firma, istatistikler, hizmetler, menu = [], yorumlar = [] } = data;
const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "20%"]);
const heroOpacity = useTransform(scrollYProgress, [0, 0.8], [1, 0]);
const [aktifKategori, setAktifKategori] = useState(0);
const [showBookingModal, setShowBookingModal] = useState(false);
// SVG Render Helper for clean vector icons (No Emojis)
const getIconSvg = (name: string) => {
switch (name) {
case "alacarte":
return (
<svg className="w-6 h-6 stroke-[#0038A8]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" />
</svg>
);
case "special":
return (
<svg className="w-6 h-6 stroke-[#0038A8]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
);
case "catering":
return (
<svg className="w-6 h-6 stroke-[#0038A8]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v18M3 12h18" />
</svg>
);
case "delivery":
return (
<svg className="w-6 h-6 stroke-[#0038A8]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 18.75a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM19.5 18.75a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75" />
</svg>
);
default:
return (
<svg className="w-6 h-6 stroke-[#0038A8]" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path strokeLinecap="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21" />
</svg>
);
}
};
return (
<div className="bg-[#FDFBF7] text-[#121C22] font-body">
{/* ── HEADER ── */}
<motion.header
className="fixed top-4 left-4 right-4 z-50 px-4"
initial={{ y: -80, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.7, ease: easeOutExpo }}
>
<div className="mx-auto max-w-7xl h-20 flex items-center justify-between px-8 bg-[#FDFBF7]/90 backdrop-blur-xl border border-[#0038A8]/10 rounded-2xl shadow-lg">
<a href="#" className="flex items-center gap-2">
<span className="text-xl font-bold tracking-tight text-[#0038A8] font-display flex items-center gap-1.5 uppercase">
<span className="text-2xl">🍋</span> {firma.adi}
</span>
</a>
<nav className="hidden lg:flex items-center gap-8">
{[
{ label: "Our Story", href: "#story" },
{ label: "Pasta & Pizza", href: "#pasta-pizza" },
{ label: "Lemon Orchard", href: "#lemon" },
{ label: "Hizmetlerimiz", href: "#services" },
{ label: "Rezervasyon", href: "#randevu" }
].map(item => (
<a
key={item.label}
href={item.href}
className="text-[11px] font-sans-clean font-bold tracking-[1.5px] uppercase text-[#0038A8]/70 hover:text-[#0038A8] transition-colors"
>
{item.label}
</a>
))}
</nav>
<div className="flex items-center gap-4">
<motion.button
onClick={() => setShowBookingModal(true)}
className="text-[11px] font-sans-clean font-black tracking-[2px] uppercase text-white bg-[#0038A8] hover:bg-[#002266] px-6 py-3 rounded-xl transition-all cursor-pointer shadow-md shadow-[#0038A8]/20"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
>
Book Table
</motion.button>
</div>
</div>
</motion.header>
{/* ── HERO SECTION ── */}
<section ref={heroRef} className="relative min-h-screen flex items-center bg-[#FDFBF7] pt-24">
<div className="relative z-10 max-w-7xl mx-auto px-6 w-full py-12">
<div className="grid lg:grid-cols-12 gap-12 items-center">
{/* Left Content */}
<motion.div
className="lg:col-span-12 z-20 text-center"
variants={stagger}
initial="hidden"
animate="show"
>
<motion.div
variants={fadeUp}
className="inline-flex items-center gap-2 text-[10px] font-sans-clean font-black uppercase tracking-widest text-[#0038A8]/60 mb-6 bg-[#0038A8]/5 border border-[#0038A8]/10 px-4 py-2 rounded-full"
>
<span className="w-1.5 h-1.5 rounded-full bg-[#0038A8]" />
{firma.slogan}
</motion.div>
<motion.h1
variants={fadeUp}
className="font-display text-6xl md:text-7xl font-black text-[#0038A8] leading-[1.05] tracking-tight mb-8"
>
Linen, Lemons
<br />
<span className="italic font-light opacity-90 text-[#3C5A48]">&amp; Wood-fired</span>
</motion.h1>
<motion.p
variants={fadeUp}
className="text-gray-600 font-sans-clean text-sm lg:text-base leading-relaxed max-w-md mx-auto mb-12 font-medium"
>
Karaköy limanında, Sicilya rüzgarları esiyor. El yapımı makarnalar, taze baharatlar ve odun ateşinde karamelize olan lezzetler.
</motion.p>
<motion.div variants={fadeUp}>
<motion.a
href="#pasta-pizza"
className="inline-flex items-center gap-3 px-8 py-4 rounded-full bg-[#0038A8] hover:bg-[#002266] text-white font-sans-clean font-black text-xs transition-all shadow-lg shadow-[#0038A8]/30 cursor-pointer"
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.97 }}
>
Discover Menu <span className="text-[10px]"></span>
</motion.a>
</motion.div>
</motion.div>
</div>
</div>
</section>
{/* ── SECTION 2: OUR STORY ── */}
<section id="story" className="py-32 bg-[#F5F2EB] px-6 relative z-10 border-t border-[#0038A8]/5">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-12 gap-16 items-center">
<motion.div
className="lg:col-span-12 text-center"
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.2 }}
variants={stagger}
>
<motion.span
variants={fadeUp}
className="text-[#0038A8] font-display italic text-base tracking-wider block mb-4"
>
Sicilian Baking Heritage
</motion.span>
<motion.h2
variants={fadeUp}
className="font-display text-4xl lg:text-5xl font-black tracking-tight text-[#0038A8] uppercase mb-8"
>
Our Story
</motion.h2>
<motion.p
variants={fadeUp}
className="text-gray-600 font-sans-clean text-xs leading-relaxed mb-12 max-w-md mx-auto font-medium"
>
Odun ateşinin alevi ve zeytin ağacı közlerinin aroması. Sicilia Tavola, nesiller boyu aktarılan İtalyan tariflerini zanaatkar ellerle buluşturuyor. Her pizza taş fırınımızda 485 derecede saniyeler içinde mükemmelleşiyor.
</motion.p>
{/* Grid Metrics */}
<motion.div
variants={fadeUp}
className="grid grid-cols-2 sm:grid-cols-4 gap-4 max-w-4xl mx-auto"
>
{istatistikler.map((s, i) => (
<div key={i} className="bg-white p-6 rounded-2xl border border-[#0038A8]/5">
<div className="text-3xl font-black text-[#0038A8] font-display">
{s.deger}
</div>
<div className="text-gray-400 text-[10px] uppercase font-sans-clean font-bold tracking-widest mt-1">
{s.etiket}
</div>
</div>
))}
</motion.div>
</motion.div>
</div>
</div>
</section>
{/* ── SECTION 3: PASTA & PIZZA MENU ── */}
<section id="pasta-pizza" className="py-32 bg-[#FDFBF7] relative z-10 px-6 border-t border-[#0038A8]/5">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16">
<span className="text-[#0038A8] font-display italic text-base tracking-wider block mb-4">our fresh selections</span>
<h2 className="font-display text-4xl lg:text-5xl font-black tracking-tight text-[#0038A8] uppercase mb-16">
Pasta &amp; Pizza
</h2>
</div>
<div className="space-y-6 max-w-3xl mx-auto w-full">
{menu[aktifKategori]?.urunler.map((item) => (
<div key={item.ad} className="flex flex-col cursor-pointer group">
<div className="flex items-end justify-between gap-4">
<span className="font-display text-base lg:text-lg font-bold text-[#0038A8] group-hover:opacity-85 transition-opacity uppercase">
{item.ad}
</span>
<div className="grow border-b border-dashed border-[#0038A8]/20 mb-2 transition-colors mx-2" />
<span className="font-display text-base font-black text-[#0038A8] shrink-0">
{item.fiyat}
</span>
</div>
<p className="text-gray-500 font-sans-clean text-[11px] mt-1 leading-relaxed">
{item.aciklama}
</p>
</div>
))}
</div>
{/* Category Tab Buttons */}
<div className="flex items-center justify-center gap-4 mt-16 flex-wrap">
{menu.map((kat, idx) => (
<button
key={kat.kategori}
onClick={() => setAktifKategori(idx)}
className="text-[10px] font-sans-clean font-bold tracking-[1.5px] uppercase px-5 py-3 rounded-lg border transition-all cursor-pointer"
style={aktifKategori === idx
? { backgroundColor: "#0038A8", color: "white", borderColor: "#0038A8" }
: { backgroundColor: "transparent", color: "rgba(0, 56, 168, 0.6)", borderColor: "rgba(0, 56, 168, 0.2)" }
}
>
{kat.kategori}
</button>
))}
</div>
</div>
</section>
{/* ── SECTION 5: SERVICES ── */}
<section id="services" className="py-24 bg-[#FDFBF7] border-t border-[#0038A8]/5 relative z-10 px-6">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<span className="text-[#0038A8] font-display italic text-base tracking-wider block mb-3">Our Offerings</span>
<h2 className="font-display text-3xl lg:text-4xl font-black tracking-tight text-[#0038A8] uppercase">
Hizmetlerimiz
</h2>
</div>
<div className="grid md:grid-cols-4 gap-6">
{hizmetler.map((h, i) => (
<motion.div
key={h.baslik}
className="bg-white rounded-2xl p-8 border border-[#0038A8]/5 hover:border-[#0038A8]/30 hover:bg-[#FDFBF7] transition-all duration-300 flex flex-col items-center text-center group cursor-default shadow-sm"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.1 }}
whileHover={{ y: -4 }}
>
<div className="w-14 h-14 rounded-full bg-[#0038A8]/5 flex items-center justify-center mb-5 transition-transform group-hover:scale-110">
{getIconSvg(h.ikon)}
</div>
<h3 className="font-display font-bold text-[#0038A8] text-sm mb-3 uppercase tracking-wider leading-snug">
{h.baslik}
</h3>
<p className="text-gray-400 font-sans-clean text-[10px] leading-relaxed">
{h.aciklama}
</p>
</motion.div>
))}
</div>
</div>
</section>
{/* ── SECTION 6: TESTIMONIALS ── */}
<section id="yorumlar" className="py-28 bg-[#F5F2EB] border-t border-[#0038A8]/5 px-6">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<span className="text-[#0038A8] font-display italic text-base tracking-wider block mb-3">guest stories</span>
<h2 className="font-display text-3xl lg:text-4xl font-black tracking-tight text-[#0038A8] uppercase">
Misafir Yorumları
</h2>
</div>
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
{yorumlar.map((y, i) => (
<motion.div
key={y.yazar}
className="bg-white border border-[#0038A8]/5 rounded-[28px] p-8 flex flex-col justify-between shadow-sm"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.1 }}
whileHover={{ y: -4, borderColor: "rgba(0, 56, 168, 0.3)" }}
>
<div className="flex gap-1 mb-6">
{[...Array(y.puan)].map((_, j) => (
<span key={j} className="text-[#0038A8]"></span>
))}
</div>
<p className="text-gray-600 font-sans-clean text-xs leading-relaxed italic mb-8 font-medium">
&ldquo;{y.yorum}&rdquo;
</p>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full border border-[#0038A8]/20 flex items-center justify-center text-lg bg-[#FDFBF7] shrink-0 shadow-sm">
{y.emoji}
</div>
<div>
<h4 className="font-display text-[#0038A8] text-xs font-bold uppercase tracking-wider">
{y.yazar}
</h4>
<span className="font-sans-clean text-[9px] text-gray-400 uppercase tracking-widest">
{y.tarih}
</span>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
{/* ── SECTION 7: RESERVATION ── */}
<section id="randevu" className="py-32 bg-[#FDFBF7] border-t border-[#0038A8]/5 relative z-10 px-6">
<div className="max-w-3xl mx-auto">
<div className="bg-white rounded-[36px] p-8 md:p-12 shadow-2xl border border-[#0038A8]/10">
<div className="text-center mb-10">
<span className="text-[#0038A8] font-display italic text-base tracking-wider block mb-3">Tavola Reservation</span>
<h2 className="font-display text-3xl md:text-4xl font-black tracking-tight text-[#0038A8] uppercase">
Masa Rezervasyonu
</h2>
<p className="text-gray-400 font-sans-clean text-xs mt-2 leading-relaxed font-medium">
Karaköy Trattoria&apos;mızda unutulmaz bir akşam yemeği için yerinizi ayırtın.
</p>
</div>
<div className="grid grid-cols-2 gap-5">
{[
{ label: "Ad Soyad", placeholder: "Adınız Soyadınız", type: "text", full: false },
{ label: "Telefon", placeholder: "05xx xxx xx xx", type: "tel", full: false },
{ label: "Tarih", placeholder: "", type: "date", full: false },
{ label: "Saat", placeholder: "", type: "time", full: false },
{ label: "Kişi Sayısı", placeholder: "2", type: "number", full: false },
{ label: "Özel Notlar", placeholder: "Diyet sınırlandırmaları veya özel kutlamalar...", type: "text", full: true },
].map((field, i) => (
<div key={i} className={field.full ? "col-span-2" : "col-span-1"}>
<label className="block text-[9px] font-sans-clean font-bold text-[#0038A8] mb-1.5 uppercase tracking-widest">{field.label}</label>
<input
type={field.type}
placeholder={field.placeholder}
className="w-full px-4 py-3 rounded-xl border border-gray-200 bg-[#FDFBF7] text-sm text-[#121C22] focus:outline-none focus:ring-1 focus:ring-[#0038A8] placeholder-gray-300 transition-all font-sans-clean"
/>
</div>
))}
</div>
<motion.button
className="mt-8 w-full py-4 rounded-xl text-white font-sans-clean font-black tracking-[2px] uppercase bg-[#0038A8] hover:bg-[#002266] transition-colors cursor-pointer text-xs"
whileHover={{ scale: 1.02, boxShadow: "0 10px 30px rgba(0, 56, 168, 0.25)" }}
whileTap={{ scale: 0.98 }}
>
Masa Ayırt
</motion.button>
</div>
</div>
</section>
{/* ── FOOTER ── */}
<footer className="bg-[#002266] text-white border-t border-white/5 px-6 pt-20 pb-8 relative z-10">
<div className="max-w-6xl mx-auto flex flex-col sm:flex-row justify-between items-center pt-8 gap-4 font-sans-clean text-white/40">
<p className="text-xs">© 2026 {firma.adi}. Tüm hakları saklıdır.</p>
<p className="text-[10px]">Gizlilik Politikası · Çerez Ayarları</p>
</div>
</footer>
{/* ── FAST BOOKING OVERLAY MODAL ── */}
<AnimatePresence>
{showBookingModal && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
className="bg-white rounded-2xl p-8 max-w-md w-full relative shadow-2xl border border-[#0038A8]/20"
initial={{ scale: 0.95, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.95, y: 20 }}
>
<button
className="absolute top-6 right-6 w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center text-gray-500 hover:bg-gray-200 transition-colors"
onClick={() => setShowBookingModal(false)}
>
</button>
<h3 className="font-display font-black text-[#0038A8] text-xl mb-1 uppercase tracking-wider">
Hızlı Rezervasyon
</h3>
<p className="text-gray-400 font-sans-clean text-[10px] mb-6 uppercase tracking-widest font-semibold">
Karaköy Trattoria Hattı
</p>
<div className="space-y-4">
<div>
<label className="block text-[9px] font-sans-clean font-bold text-[#0038A8] mb-1 uppercase tracking-wider">Ad Soyad</label>
<input type="text" className="w-full px-4 py-3 rounded-xl border border-gray-200 bg-[#FDFBF7] text-sm focus:outline-none focus:ring-1 focus:ring-[#0038A8]" placeholder="Adınız Soyadınız" />
</div>
<div>
<label className="block text-[9px] font-sans-clean font-bold text-[#0038A8] mb-1 uppercase tracking-wider">Telefon Numarası</label>
<input type="tel" className="w-full px-4 py-3 rounded-xl border border-gray-200 bg-[#FDFBF7] text-sm focus:outline-none focus:ring-1 focus:ring-[#0038A8]" placeholder="05xx xxx xx xx" />
</div>
<motion.button
className="w-full py-4 rounded-xl bg-[#0038A8] text-white font-sans-clean font-black text-xs uppercase tracking-wider mt-4"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowBookingModal(false)}
>
Bilgileri Gönder
</motion.button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
+34
View File
@@ -0,0 +1,34 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useInView } from "framer-motion";
export default function AnimatedCounter({ target, suffix = "" }: { target: number; suffix?: string }) {
const [count, setCount] = useState(0);
const ref = useRef(null);
const inView = useInView(ref, { once: true });
useEffect(() => {
if (!inView) return;
const duration = 1800;
const steps = 60;
const increment = target / steps;
let current = 0;
const timer = setInterval(() => {
current += increment;
if (current >= target) {
setCount(target);
clearInterval(timer);
} else {
setCount(Math.floor(current));
}
}, duration / steps);
return () => clearInterval(timer);
}, [inView, target]);
return (
<span ref={ref}>
{count.toLocaleString("tr-TR")}
{suffix}
</span>
);
}
+243
View File
@@ -0,0 +1,243 @@
export interface BlogPostContent {
title: string;
excerpt: string;
readingTime: string;
category: string;
tags: string[];
content: string;
}
export interface BlogPost {
id?: number;
slug: string;
date: string;
author: string;
authorRole: string;
image: string;
tr: BlogPostContent;
en: BlogPostContent;
}
export const blogPosts: BlogPost[] = [
{
slug: "real-time-anomaly-detection-ai",
date: "2026-05-15",
author: "Selin Arslan",
authorRole: "Head of AI Research",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1200&q=80",
tr: {
title: "Yapay Zekâ ile Anomali Tespiti: Finansal Sistemleri Koruma Yöntemleri",
excerpt: "Yapay zekâ destekli gözetim sistemleri, günümüz fintech uygulamalarında dolandırıcılığı sıfıra indirmek için nasıl konumlandırılıyor?",
readingTime: "5 dk okuma",
category: "YZ · Finans",
tags: ["AI", "Kafka", "Machine Learning", "Fintech", "FastAPI"],
content: `## Finansal Mimarilerde Hız ve Güvenlik Dengesi
Günümüz finans ekosistemlerinde hız her şeydir. Günde milyonlarca işlemin aktığı sistemlerde, tek bir şüpheli işlemin gözden kaçması milyonlarca dolarlık kayıplara ve itibar zedelenmesine yol açabilir. Eski nesil toplu (batch) işleme sistemleri, işlemleri saatler sonra kontrol ettiği için dolandırıcılığı engellemede yetersiz kalmaktadır.
İşte tam bu noktada **Yapay Zekâ destekli gerçek zamanlı anomali tespiti** devreye giriyor. Ayris Tech olarak geliştirdiğimiz mimarilerde, milisaniyeler seviyesinde karar üreten sistemleri nasıl tasarladığımızı inceleyelim.
### Olay Akışı (Event Streaming) ve Kafka Entegrasyonu
Gerçek zamanlı bir sistemin kalbi veri taşıma hatlarıdır. Apache Kafka kullanarak, işlem isteklerini kuyruğa alıp eşzamansız (asynchronous) olarak ML modeline aktarıyoruz:
\`\`\`javascript
// Sistem olay akışı şeması
[İşlem Talebi] -> [API Gateway] -> [Kafka Topic] -> [FastAPI ML Model] -> [Karar Servisi]
\`\`\`
Bu sayede ön yüz uygulamalarında donma veya gecikme yaşanmadan arka planda saniyede on binlerce veri paketi analiz edilmektedir.
### Anomali Tespiti Modelleri
Geleneksel kural tabanlı sistemler yerine, **Isolation Forest** ve **Autoencoder (Oto-kodlayıcı)** yapay sinir ağları kullanıyoruz. Oto-kodlayıcılar, normal finansal işlemleri sıkıştırıp yeniden inşa etmeyi öğrenir. Eğer gelen yeni bir işlem normal şablona uymuyorsa, yeniden inşa hatası (reconstruction error) yüksek çıkar ve sistem bunu anında anomali olarak işaretler.
- **Düşük Gecikme:** TensorFlow C++ runtime entegrasyonu sayesinde model çıkarım (inference) süresini 10ms'nin altına indiriyoruz.
- **Sıfır Kesinti:** Modellerimizi canlı yayında (hot-swap) güncelleyebiliyoruz, böylece sistem durmadan yeni dolandırıcılık yöntemlerine karşı güncelleniyor.
> **Sonuç:** Doğru tasarlanmış bir yapay zekâ hattı, şirketlerin finansal risklerini neredeyse sıfıra indirir.`
},
en: {
title: "Real-time Anomaly Detection with AI: Securing Financial Architectures",
excerpt: "How are artificial intelligence-driven surveillance engines positioned to minimize fintech fraud to absolute zero?",
readingTime: "5 min read",
category: "AI · Fintech",
tags: ["AI", "Kafka", "Machine Learning", "Fintech", "FastAPI"],
content: `## Speed vs. Security in Modern Financial Stacks
In today's global financial ecosystems, speed is everything. With millions of transactions flowing through databases daily, letting a single fraudulent activity slip by can lead to catastrophic losses and severely damaged brand trust. Traditional batch processing systems are obsolete—detecting fraud hours after a transaction completes is no longer acceptable.
This is where **artificial intelligence-driven real-time anomaly detection** changes the game. Let's analyze how we architect sub-10ms neural evaluation engines at Ayris Tech.
### Event Streaming and Kafka Architecture
The cornerstone of any real-time system is the data ingestion pipeline. Utilizing Apache Kafka, we ingest transaction event streams asynchronously and route them directly to our inference nodes:
\`\`\`javascript
// Event streaming schema
[Transaction Request] -> [API Gateway] -> [Kafka Topic] -> [FastAPI ML Engine] -> [Decision Hub]
\`\`\`
This decouples the heavy neural analysis from the transactional user interface, ensuring perfect responsive stability under heavy loads.
### Anomaly Detection Models
Rather than hardcoded conditional rules, we utilize advanced **Isolation Forests** and Deep Neural **Autoencoders**. Autoencoders learn to compress and reconstruct normal transactional behaviors. When an anomalous transaction occurs, the reconstruction error spikes, immediately triggering an automated lock.
- **Sub-10ms Latency:** Deployed via optimized TensorFlow runtimes, model inference lags remain well under 10ms.
- **Dynamic Hot-Swaps:** Models are retrained and updated on-the-fly, providing uninterrupted security layers against changing vectors.
> **Verdict:** A properly engineered streaming ML pipeline reduces financial exposure to absolute zero.`
}
},
{
slug: "web3-consortium-networks-logistics",
date: "2026-05-10",
author: "Emre Doğan",
authorRole: "Web3 Lead Engineer",
image: "https://images.unsplash.com/photo-1639762681485-074b7f938ba0?auto=format&fit=crop&w=1200&q=80",
tr: {
title: "Küresel Lojistikte Web3 Devrimi: Konsorsiyum Ağı Nasıl Kurulur?",
excerpt: "Çok aktörlü tedarik zinciri ağlarında şeffaflığı ve veri bütünlüğünü akıllı sözleşmeler ve değişmez kayıtlarla sağlamanın yolları.",
readingTime: "7 dk okuma",
category: "Web3 · Lojistik",
tags: ["Solidity", "Blockchain", "IPFS", "EVM", "Smart Contracts"],
content: `## Lojistikte Güven Problemi
Küresel ticarette bir ürün, üreticiden tüketiciye ulaşana kadar ortalama 15 farklı aracı firmanın (limanlar, gümrükler, yerel lojistik firmaları, taşıyıcılar) elinden geçer. Her aktörün kendi veritabanını tutması; veri uyuşmazlıklarına, kayıp evraklara ve haftalar süren mutabakat süreçlerine sebep olur.
**Konsorsiyum blokzincir ağları (Consortium Blockchains)**, tüm bu tarafların ortak, değişmez ve şeffaf bir defter üzerinde anlaşmasını sağlar.
### Solidity ile Gaz Optimizasyonlu Akıllı Sözleşmeler
Lojistik olaylarında her el değiştirme (handoff) blokzincire yazıldığından, işlem ücretleri (gas fee) kritik öneme sahiptir. EVM (Ethereum Virtual Machine) tabanlı sistemlerde maliyetleri düşürmek için şu teknikleri kullanıyoruz:
1. **Batching (Yığınlama):** Birden fazla kargo durum güncellemesini tek bir işlemde toplu olarak zincire göndermek.
2. **Proxy Patterns (Vekil Kalıpları):** Sözleşme kodunu her seferinde baştan dağıtmak yerine, ortak mantığı barındıran tek bir vekil üzerinden çalıştırarak bellek tasarrufu sağlamak.
\`\`\`solidity
// Gas optimized handoff event struct
struct Handoff {
bytes32 packageId;
address sender;
address receiver;
uint32 timestamp;
bytes32 docHash; // IPFS document reference
}
\`\`\`
### Değişmez Belge Saklama: IPFS Entegrasyonu
Faturalar, konşimentolar ve gümrük belgeleri gibi büyük dosyaları doğrudan blokzincir üzerinde saklamak inanılmaz pahalıdır. Bu yüzden, dosyaları **IPFS (InterPlanetary File System)** üzerinde saklayıp, sadece bu belgelerin cryptographic hash (parmak izi) değerlerini akıllı sözleşmeye kaydediyoruz. Belgelerde yapılacak en ufak bir değişiklik parmak izini bozacağı için evrakta sahtecilik tamamen imkansız hale gelir.
Lojistik ağınızı blokzincir teknolojisiyle güçlendirerek, mutabakat sürelerini günlerden dakikalara indirebilirsiniz.`
},
en: {
title: "The Web3 Revolution in Global Logistics: Architecting Consortium Networks",
excerpt: "How smart contracts and immutable logs secure multi-actor transparency and data integrity in modern supply chains.",
readingTime: "7 min read",
category: "Web3 · Logistics",
tags: ["Solidity", "Blockchain", "IPFS", "EVM", "Smart Contracts"],
content: `## The Trust Gap in Modern Cargo
Before a physical good moves from manufacturer to consumer, it passes through an average of 15 intermediaries—customs brokers, freight forwarders, terminal operators, and local distributors. When every actor maintains a siloed legacy database, friction, lost documents, and massive reconciliation overheads are inevitable.
A **Consortium Blockchain** establishes a single, shared, and cryptographically immutable ledger that all actors can trust implicitly.
### Solidity Optimizations for Real-World Workflows
Because every logistics event represents an on-chain transaction, optimizing gas consumption within EVM (Ethereum Virtual Machine) sandboxes is of paramount importance:
1. **Transaction Batching:** Grouping multiple state shifts into a single cryptographic payload.
2. **Proxy Patterns:** Deploying upgradeable structural proxies (ERC-1967) to drastically reduce initial contract footprint and variable deployment gas fees.
\`\`\`solidity
// Gas optimized handoff event struct
struct Handoff {
bytes32 packageId;
address sender;
address receiver;
uint32 timestamp;
bytes32 docHash; // IPFS document reference
}
\`\`\`
### Immutable Assets: IPFS Document Anchoring
Uploading multi-megabyte PDFs (such as bills of lading) directly to Ethereum storage is financially unviable. We bypass this by offloading files to **IPFS (InterPlanetary File System)**. The generated cryptographic hash is then written to the ledger. This guarantees document integrity: even a single modified pixel in a PDF will yield a totally different hash, making tampering instantly visible.
Transitioning to consortium ledger architectures shrinks contract reconciliation latency from 18 days to less than 4 hours.`
}
},
{
slug: "headless-ecommerce-conversion-rates",
date: "2026-05-01",
author: "Mustafa Yıldız",
authorRole: "Founder & Architect",
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=1200&q=80",
tr: {
title: "Headless E-Ticaret Mimarisi ve Sayfa Hızının Dönüşüm Oranlarına Etkisi",
excerpt: "Milisaniyeler satış demektir. Headless Next.js altyapılarıyla yükleme sürelerini %80 kısaltıp sepet terk oranını nasıl düşürdük?",
readingTime: "6 dk okuma",
category: "Web · E-Ticaret",
tags: ["Next.js", "Headless CMS", "GraphQL", "Performance", "SEO"],
content: `## Milisaniyelerin Ticari Değeri
E-ticarette her milisaniye cironuzu etkiler. Google verilerine göre, mobil sayfa yüklenme süresi 1 saniyeden 3 saniyeye çıktığında hemen çıkma oranı (bounce rate) %32 artmaktadır. Geleneksel monolitik e-ticaret altyapıları (eski Magento, WooCommerce vb.), sunucu taraflı hantal yapıları ve sıkışık veri sorguları nedeniyle bu hız hedeflerini yakalayamazlar.
**Headless (Kafasız) Mimari**, arka plan ticaret mantığı ile ön yüz görsel katmanını birbirinden tamamen ayırarak bu sorunu kökten çözer.
### Headless Next.js ile 99/100 Performansı
Ayris Tech e-ticaret projelerinde, ön yüzde **Next.js App Router** kullanırken arka planda GraphQL API ağ geçidiyle haberleşen headless motorları tercih ediyoruz. Bu yaklaşımın temel avantajları:
- **Static Generation (SSG):** Ürün sayfalarını derleme aşamasında (build time) statik HTML olarak üretiyor ve küresel CDN'ler üzerinden anında yüklenecek şekilde dağıtıyoruz.
- **Incremental Static Regeneration (ISR):** Fiyat veya stok değiştiğinde tüm siteyi baştan derlemek yerine, sadece ilgili ürün sayfasını arka planda milisaniyeler içinde güncelliyoruz.
\`\`\`javascript
// Next.js ISR (Incremental Static Regeneration) örneği
export const revalidate = 60; // sayfayı en fazla dakikada bir güncelle
\`\`\`
### Arama Deneyimi ve Algolia Entegrasyonu
Kullanıcılar arama çubuğuna tıkladığında binlerce ürün arasından aradıklarını anında bulmalıdır. Bunun için headless altyapımızı **Algolia** anlık arama indeksi ile destekliyoruz. Her tuşa basıldığında (on keypress) 10ms içinde sonuçlar listelenir, bu da doğrudan sepet ekleme oranlarını %24 artırır.
Eğer sepet terk oranlarınızı düşürmek ve küresel çapta en hızlı alışveriş deneyimini sunmak istiyorsanız, e-ticaret sitenizi modern headless mimarilere taşımalısınız.`
},
en: {
title: "Headless E-commerce & Page Speed Impact on Conversion Rates",
excerpt: "Milliseconds are sales. How we cut load times by 80% and minimized shopping cart abandonment with headless Next.js layouts.",
readingTime: "6 min read",
category: "Web · E-commerce",
tags: ["Next.js", "Headless CMS", "GraphQL", "Performance", "SEO"],
content: `## The Financial Value of Milliseconds
In the digital storefront arena, milliseconds directly correlate to transactional yield. Google data proves that when mobile page load speeds increase from 1 to 3 seconds, bounce rates spike by over 32%. Monolithic legacy suites (such as un-decoupled Magento or standard WooCommerce instances) collapse under these performance parameters due to heavy database roundtrips and synchronous asset delivery.
**Headless Architecture** breaks this bottleneck by decoupling the backend transactional commerce engine from the client-facing presentation layer.
### Yielding 99/100 Lighthouse Grades with Next.js
At Ayris Tech, we deploy **Next.js App Router** on the edge combined with GraphQL API gateways to serve transactional data. This approach offers distinct performance advantages:
- **Static Generation (SSG):** Product catalogues are compiled into lightweight static HTML at build time and distributed across global edge nodes for near-instant rendering.
- **Incremental Static Regeneration (ISR):** When pricing or stock variables change, we update specific nodes on-the-fly in the background rather than rebuild the entire portal.
\`\`\`javascript
// Next.js ISR (Incremental Static Regeneration) config
export const revalidate = 60; // invalidate cached pages every 60 seconds
\`\`\`
### Instant Search with Algolia
Slow search bars kill conversions. We connect our headless catalogues with **Algolia** indexing arrays to deliver predictive, instant search results on keypress inside of 10ms. This micro-interaction alone yields an average 24% uplift in Add-to-Cart events.
Migrating to high-craft headless setups is no longer a luxury—it is the modern benchmark for high-converting global trade.`
}
}
];
export function getBlogPostBySlug(slug: string): BlogPost | undefined {
return blogPosts.find((p) => p.slug === slug);
}
+269
View File
@@ -0,0 +1,269 @@
export interface DemoData {
slug: string;
template: "klinik" | "restoran" | "kurumsal" | "dental" | "restoran2";
firma: {
adi: string;
slogan: string;
sehir: string;
adres: string;
telefon: string;
email: string;
logoEmoji: string;
renkAna: string;
renkKoyu: string;
renkAcik: string;
};
istatistikler: { deger: string; etiket: string }[];
hizmetler: { ikon: string; baslik: string; aciklama: string }[];
yorumlar: { yazar: string; yorum: string; puan: number; tarih: string; emoji: string }[];
doktorlar?: { ad: string; uzmanlik: string; puan: string; yil: string; hasta: string; emoji: string }[];
menu?: { kategori: string; ikon: string; urunler: { ad: string; aciklama: string; fiyat: string }[] }[];
projeler?: { baslik: string; sektor: string; aciklama: string; ikon: string; renk: string; resim?: string }[];
ekip?: { ad: string; rol: string; emoji: string }[];
}
export const demos: Record<string, DemoData> = {
"dogan-tip-merkezi": {
slug: "dogan-tip-merkezi",
template: "klinik",
firma: {
adi: "Doğan Tıp Merkezi",
slogan: "Sağlığınız, En Değerli Varlığınız",
sehir: "İstanbul",
adres: "Bağcılar Mah. Sağlık Cad. No:12, Bağcılar / İstanbul",
telefon: "0212 555 44 33",
email: "info@dogantip.com",
logoEmoji: "🏥",
renkAna: "#0ea5e9",
renkKoyu: "#0284c7",
renkAcik: "#e0f2fe",
},
istatistikler: [
{ deger: "4200", etiket: "Mutlu Hasta" },
{ deger: "14", etiket: "Uzman Doktor" },
{ deger: "12", etiket: "Yıl Deneyim" },
{ deger: "620", etiket: "Yorum" },
],
hizmetler: [
{ ikon: "stethoscope", baslik: "Dahiliye", aciklama: "Erişkin hastalıklarında kapsamlı tanı ve tedavi hizmetleri." },
{ ikon: "heart", baslik: "Kardiyoloji", aciklama: "Kalp ve damar sağlığı için ileri tanı ve tedavi." },
{ ikon: "tooth", baslik: "Diş Hekimliği", aciklama: "İmplant, ortodonti ve estetik diş uygulamaları." },
{ ikon: "eye", baslik: "Göz Sağlığı", aciklama: "Lazer tedavileri ve göz cerrahisi." },
{ ikon: "flask", baslik: "Laboratuvar", aciklama: "Hızlı ve güvenilir tahlil sonuçları." },
{ ikon: "image", baslik: "Radyoloji", aciklama: "MR, tomografi ve ultrason görüntüleme." },
],
doktorlar: [
{ ad: "Dr. Ahmet Yılmaz", uzmanlik: "Dahiliye Uzmanı", puan: "4.9", yil: "15", hasta: "2400", emoji: "👨‍⚕️" },
{ ad: "Dr. Ayşe Kaya", uzmanlik: "Kardiyoloji", puan: "4.8", yil: "12", hasta: "1800", emoji: "👩‍⚕️" },
{ ad: "Dr. Mehmet Demir", uzmanlik: "Göz Hastalıkları", puan: "5.0", yil: "10", hasta: "1200", emoji: "👨‍⚕️" },
{ ad: "Dr. Zeynep Arslan", uzmanlik: "Diş Hekimi", puan: "4.9", yil: "8", hasta: "900", emoji: "👩‍⚕️" },
],
yorumlar: [
{ yazar: "Fatma K.", yorum: "Çok iyi hizmet aldım. Doktorlar son derece ilgili ve profesyonel. Randevu sistemi de çok pratik.", puan: 5, tarih: "2 hafta önce", emoji: "😊" },
{ yazar: "Mustafa A.", yorum: "Temiz, modern ortam. Muayenemi çok hızlı yaptılar ve sonuçları aynı gün aldım.", puan: 5, tarih: "1 ay önce", emoji: "🙂" },
{ yazar: "Elif T.", yorum: "Online randevu sistemi harikaydı. Güvenilir ve kaliteli bir klinik. Tüm ailemle tercih ediyoruz.", puan: 5, tarih: "3 hafta önce", emoji: "😄" },
],
},
"lezzet-mutfagi": {
slug: "lezzet-mutfagi",
template: "restoran",
firma: {
adi: "Lezzet Mutfağı",
slogan: "Taste The Difference",
sehir: "İstanbul",
adres: "Karaköy Mah. Liman Cad. No:7, Beyoğlu / İstanbul",
telefon: "0212 444 55 66",
email: "rezervasyon@lezzetmutfagi.com",
logoEmoji: "🍽️",
renkAna: "#C5A880",
renkKoyu: "#0A0B0D",
renkAcik: "#14161B",
},
istatistikler: [
{ deger: "12", etiket: "Years of Heritage" },
{ deger: "48+", etiket: "Signature Dishes" },
{ deger: "4.9", etiket: "Average Guest Rating" },
{ deger: "18k+", etiket: "Monthly Diners" },
],
hizmetler: [
{ ikon: "alacarte", baslik: "À la Carte", aciklama: "Seçkin malzemelerle hazırlanan imza yemeklerimizi keşfedin." },
{ ikon: "special", baslik: "Özel Günler", aciklama: "Doğum günü, evlilik yıldönümü ve kutlamalar için özel menüler." },
{ ikon: "catering", baslik: "Kurumsal Etkinlik", aciklama: "İş yemekleri ve kurumsal organizasyonlar için catering hizmeti." },
{ ikon: "delivery", baslik: "Paket Servis", aciklama: "Şehrin her köşesine hızlı ve sıcak teslimat." },
],
menu: [
{
kategori: "Başlangıçlar", ikon: "salad",
urunler: [
{ ad: "Ev Yapımı Hummus", aciklama: "Fırından taze lavaş ve trüf yağı ile", fiyat: "₺180" },
{ ad: "Karidesli Bruschetta", aciklama: "Taze domates, fesleğen, marine sarımsak", fiyat: "₺220" },
{ ad: "Burrata Salatası", aciklama: "Cherry domates, pesto, taze roka yaprakları", fiyat: "₺240" },
],
},
{
kategori: "Ana Yemekler", ikon: "steak",
urunler: [
{ ad: "Wagyu Bonfile", aciklama: "Fırınlanmış kuşkonmaz, trüf mantarlı patates püresi", fiyat: "₺680" },
{ ad: "Levrek Fileto", aciklama: "Limonlu tereyağı sosu, kapari ve taze otlar", fiyat: "₺420" },
{ ad: "Mantar Risotto", aciklama: "Yabani orman mantarları ve Parmigiano Reggiano", fiyat: "₺360" },
],
},
{
kategori: "Tatlılar", ikon: "dessert",
urunler: [
{ ad: "Crème Brûlée", aciklama: "Gerçek Madagaskar vanilyası, karamelize şeker kıtırı", fiyat: "₺160" },
{ ad: "Çikolatalı Fondant", aciklama: "Belçika çikolatası dolgulu sıcak kek, vanilyalı dondurma", fiyat: "₺180" },
{ ad: "Künefe", aciklama: "Halis tereyağlı çıtır tel kadayıf, manda kaymağı", fiyat: "₺200" },
],
},
],
yorumlar: [
{ yazar: "Selin A.", yorum: "Hayatımda yediğim en iyi bonfile. Servis mükemmeldi, ortam son derece loş ve şık. Kesinlikle tekrar geleceğiz.", puan: 5, tarih: "1 hafta önce", emoji: "🍷" },
{ yazar: "Burak T.", yorum: "Yıldönümümüzü burada kutladık. Detaylara gösterilen özen, menü kalitesi ve ambiyans harikaydı.", puan: 5, tarih: "2 hafta önce", emoji: "🥂" },
{ yazar: "Canan M.", yorum: "Karaköy'ün en iyi restoranı. Risotto ve tatlılar inanılmazdı. Şefin ellerine sağlık.", puan: 5, tarih: "1 ay önce", emoji: "✨" },
],
},
"atlas-lojistik": {
slug: "atlas-lojistik",
template: "kurumsal",
firma: {
adi: "Atlas Lojistik",
slogan: "Güvenli Taşımacılık, Zamanında Teslimat",
sehir: "İstanbul",
adres: "Esenyurt Lojistik Merkezi, Esenyurt / İstanbul",
telefon: "0212 333 22 11",
email: "info@atlaslojistik.com",
logoEmoji: "🚛",
renkAna: "#6366f1",
renkKoyu: "#4f46e5",
renkAcik: "#eef2ff",
},
istatistikler: [
{ deger: "15", etiket: "Yıl Deneyim" },
{ deger: "850+", etiket: "Aktif Müşteri" },
{ deger: "12", etiket: "Şehir" },
{ deger: "%99.2", etiket: "Zamanında Teslimat" },
],
hizmetler: [
{ ikon: "truck", baslik: "Karayolu Taşımacılığı", aciklama: "Yurtiçi ve uluslararası karayolu ile güvenli ve hızlı taşımacılık." },
{ ikon: "building", baslik: "Depolama & Lojistik", aciklama: "Modern depolarımızda güvenli stok yönetimi ve dağıtım hizmetleri." },
{ ikon: "box", baslik: "E-ticaret Lojistiği", aciklama: "Son mile teslimat, iade yönetimi ve fulfillment çözümleri." },
{ ikon: "globe", baslik: "Uluslararası Nakliye", aciklama: "Gümrük işlemleri dahil kapıdan kapıya uluslararası taşımacılık." },
{ ikon: "thermometer", baslik: "Soğuk Zincir", aciklama: "Gıda ve ilaç sektörü için kontrollü sıcaklıkta taşıma." },
{ ikon: "smartphone", baslik: "Anlık Takip", aciklama: "Yükünüzü 7/24 gerçek zamanlı olarak takip edin." },
],
projeler: [
{ baslik: "Migros Dağıtım Ağı", sektor: "Perakende", aciklama: "12 şehirde günlük 400+ noktaya soğuk zincir dağıtım yönetimi.", ikon: "shopping-cart", renk: "#10b981" },
{ baslik: "Trendyol Fulfillment", sektor: "E-ticaret", aciklama: "Günlük 8.000+ sipariş işleme ve son mile teslimat operasyonu.", ikon: "package", renk: "#f97316" },
{ baslik: "Ford Otosan Tedarik", sektor: "Otomotiv", aciklama: "JIT modeli ile fabrikaya zamanında parça tedarik lojistiği.", ikon: "car", renk: "#6366f1" },
{ baslik: "Pfizer İlaç Lojistiği", sektor: "İlaç", aciklama: "GDP sertifikalı soğuk zincir ile ilaç dağıtım ağı yönetimi.", ikon: "pill", renk: "#0ea5e9" },
],
ekip: [
{ ad: "Murat Yıldız", rol: "Genel Müdür", emoji: "👨‍💼" },
{ ad: "Hande Çelik", rol: "Operasyon Direktörü", emoji: "👩‍💼" },
{ ad: "Serkan Aydın", rol: "Teknoloji Müdürü", emoji: "👨‍💻" },
{ ad: "Neslihan Kara", rol: "Müşteri Deneyimi", emoji: "👩‍💼" },
],
yorumlar: [
{ yazar: "Kemal B. — Tedarik Zinciri Müdürü", yorum: "3 yıldır çalışıyoruz, tek bir gecikmemiz olmadı. Gerçek anlamda güvenilir bir iş ortağı.", puan: 5, tarih: "1 ay önce", emoji: "👍" },
{ yazar: "Derya S. — E-ticaret Direktörü", yorum: "E-ticaret operasyonumuzu tamamen Atlas'a devrettik. Müşteri memnuniyetimiz %94'e çıktı.", puan: 5, tarih: "2 ay önce", emoji: "🙌" },
{ yazar: "Tahir A. — Satın Alma Müdürü", yorum: "Anlık takip sistemi ve proaktif iletişim anlayışı sektörde fark yaratıyor.", puan: 5, tarih: "3 hafta önce", emoji: "⭐" },
],
},
"odentries": {
slug: "odentries",
template: "dental",
firma: {
adi: "Odentries",
slogan: "Seamless Dental Care",
sehir: "İstanbul",
adres: "Nişantaşı Mah. Valikonağı Cad. No:45, Şişli / İstanbul",
telefon: "0212 999 88 77",
email: "hello@odentries.com",
logoEmoji: "🦷",
renkAna: "#1E2E38",
renkKoyu: "#121C22",
renkAcik: "#EBF5F0",
},
istatistikler: [
{ deger: "80%", etiket: "Exclusive Member Savings: Save 60% - 80% on Dental Procedures, including Oral Exams, Cleanings, and X-Rays." },
{ deger: "40%", etiket: "Enhanced Member Benefits: Save 40% on All Other Dental Services, including Cosmetic, Restorative, and Specialty Dental Procedures." },
],
hizmetler: [
{ ikon: "shield", baslik: "Prevent cavities and gum disease", aciklama: "Kapsamlı diş muayeneleri ve koruyucu hekimlik uygulamaları ile dişlerinizi koruyoruz." },
{ ikon: "sparkles", baslik: "Keep your teeth sparkling clean", aciklama: "Profesyonel temizleme ve beyazlatma teknikleriyle parıldayan sağlıklı gülüşler yaratıyoruz." },
{ ikon: "search", baslik: "Early detection of dental issues", aciklama: "İleri teknoloji röntgen ve teşhis araçlarıyla sorunları büyümeden yakalıyoruz." },
],
projeler: [
{ baslik: "Teeth Straightening", sektor: "002 - Our Works", aciklama: "Impressive results with cleaning.", ikon: "sparkles", renk: "#EBF5F0" },
{ baslik: "Revitalized Cleaning", sektor: "002 - Our Works", aciklama: "A simple way to enhance your smile.", ikon: "tooth", renk: "#FAF7F2" },
{ baslik: "Dental Implant", sektor: "002 - Our Works", aciklama: "Gorgeous and durable smile updates.", ikon: "microscope", renk: "#EBF5F0" },
],
yorumlar: [
{ yazar: "Melis Y.", yorum: "Harika bir diş sağlığı deneyimiydi. Tasarım muhteşem, ekip inanılmaz profesyonel. Odentries bir numara!", puan: 5, tarih: "1 hafta önce", emoji: "😊" },
{ yazar: "Arda K.", yorum: "Klinik çok temiz ve ferah. Tedavi süresince hiçbir acı hissetmedim. Herkese tavsiye ederim.", puan: 5, tarih: "3 hafta önce", emoji: "👍" },
],
},
"sicilia-tavola": {
slug: "sicilia-tavola",
template: "restoran2",
firma: {
adi: "Sicilia Tavola",
slogan: "Linen, Lemons & Wood-fired Heritage",
sehir: "İstanbul",
adres: "Karaköy Mah. Gümrük Sok. No:14, Beyoğlu / İstanbul",
telefon: "0212 555 77 88",
email: "ciao@siciliavol.com",
logoEmoji: "🍋",
renkAna: "#0038A8",
renkKoyu: "#002266",
renkAcik: "#FDFBF7",
},
istatistikler: [
{ deger: "1892", etiket: "Sicilian Baking Roots" },
{ deger: "100%", etiket: "Organic Cold Pressed Oil" },
{ deger: "4.9", etiket: "Average Gastronomy Rating" },
{ deger: "12k+", etiket: "Happy Diners Annually" },
],
hizmetler: [
{ ikon: "alacarte", baslik: "À la Carte Gastronomy", aciklama: "Odun ateşinde pişen taze makarnalar, taze deniz mahsulleri ve Sicilya klasikleri." },
{ ikon: "special", baslik: "Trattoria Geceleri", aciklama: "Özel canlı akordeon dinletileri ve şefin tadım menüleri eşliğinde Sicilya akşamları." },
{ ikon: "catering", baslik: "Zeytinyağı Tadımı", aciklama: "Kendi bahçelerimizden gelen %100 soğuk sıkım sızma zeytinyağlarimizi keşfedin." },
{ ikon: "delivery", baslik: "Tavola Evinizde", aciklama: "Özel korumalı kuryelerimizle en taze gurme lezzetleri kapınıza getiriyoruz." },
],
menu: [
{
kategori: "Primi Piatti", ikon: "salad",
urunler: [
{ ad: "Caprese di Burrata", aciklama: "Manda burrata, pembe domates dilimleri, taze fesleğen ve zeytinyağı", fiyat: "₺280" },
{ ad: "Carpaccio di Polpo", aciklama: "İnce dilimlenmiş marine ahtapot, kapari, bebek roka ve limon emülsiyonu", fiyat: "₺340" },
{ ad: "Focaccia al Rosmarino", aciklama: "Taş fırından yeni çıkmış deniz tuzu, taze biberiye ve sızma zeytinyağlı", fiyat: "₺180" },
],
},
{
kategori: "Secondi", ikon: "steak",
urunler: [
{ ad: "Tagliatelle al Ragu di Polpo", aciklama: "Ağır ateşte pişmiş ahtapot ragu, taze el yapımı tagliatelle", fiyat: "₺460" },
{ ad: "Pizza Margherita DOP", aciklama: "Odun ateşinde taş fırın pizza, San Marzano domates, taze mozzarella di bufala", fiyat: "₺380" },
{ ad: "Polpo alla Griglia", aciklama: "Izgara ahtapot kolları, kapari, cherry domates ve ezilmiş sarımsaklı bebek patates", fiyat: "₺680" },
],
},
{
kategori: "Dolci", ikon: "dessert",
urunler: [
{ ad: "Cannoli Siciliani", aciklama: "Çıtır hamur tüpleri içinde tatlı ricotta kreması, çikolata parçacıkları ve Antep fıstığı", fiyat: "₺190" },
{ ad: "Tiramisu al Limone", aciklama: "Limon likörlü hafif mascarpone kreması, taze limon kabuğu rendesi ile", fiyat: "₺210" },
{ ad: "Gelato di Pistacchio", aciklama: "Kendi imalatımız gerçek Bronte Antep fıstıklı İtalyan dondurması", fiyat: "₺160" },
],
},
],
yorumlar: [
{ yazar: "Ender M.", yorum: "Limon kokuları ve çalan müzikler eşliğinde kendimizi Sicilya'da hissettik. Ahtapot makarna tek kelimeyle kusursuzdu.", puan: 5, tarih: "3 gün önce", emoji: "🍋" },
{ yazar: "Zeynep S.", yorum: "Focaccia ekmeği ve sızma zeytinyağının kalitesi buranın zanaatkarlığını kanıtlıyor. Mutlaka rezervasyon yaptırın.", puan: 5, tarih: "2 hafta önce", emoji: "✨" },
],
},
};
export function getDemoBySlug(slug: string): DemoData | null {
return demos[slug] ?? null;
}
+291
View File
@@ -0,0 +1,291 @@
{
"nav": {
"services": "Services",
"work": "Work",
"process": "Process",
"blog": "Blog",
"faq": "FAQ",
"quote": "Get a Quote",
"admin": "Admin",
"partners": "Partners",
"brandName": "Ayris",
"brandSub": "Tech"
},
"hero": {
"badge": "Ayris Tech — Peak of Engineering",
"titleLine1": "Forging the",
"titleLine2": "Digital Future",
"desc": "Ayris Tech engineered high-performance digital transformation. We build the impossible for the next generation of business using AI, Blockchain, and modern web architectures.",
"ctaQuote": "Get a Quote",
"ctaWork": "Proven Work"
},
"stats": {
"deliveries": "Shipped Deliveries",
"retention": "Client Retentiveness",
"regions": "Global Regions Served"
},
"services": {
"badge": "Capability Spectrum",
"title": "OUR SERVICES",
"desc": "We engineer highly technical deliverables tailored for next-generation enterprise scalability.",
"deliverablesLabel": "Execution Deliverables",
"items": {
"ai": {
"title": "AI Solutions",
"desc": "Architecting custom machine learning models, semantic NLP pipelines, and enterprise LLM integrations tailored for structural scale."
},
"blockchain": {
"title": "Blockchain Dev",
"desc": "Engineering highly secure smart contracts, custom decentralized applications (dApps), and sovereign private blockchain ecosystems."
},
"mobile": {
"title": "Mobile Apps",
"desc": "Crafting beautiful, fluid multi-platform mobile applications using Flutter and React Native, prioritizing pure speed and native UX."
},
"web": {
"title": "Web Platforms",
"desc": "Developing ultra-fast, SEO-optimized web systems utilizing Next.js and server-side configurations built to convert at scale."
}
}
},
"work": {
"badge": "Selected Blueprints",
"title": "PROVEN WORK",
"desc": "Data-backed, highly optimized operational case studies with custom deliverables.",
"specsLabel": "Performance Matrix",
"analyzeBtn": "Analyze Specs",
"galleryTitle": "Application Blueprints",
"items": [
{
"num": "01",
"slug": "financeai-dashboard",
"title": "FinanceAI Dashboard",
"tag": "AI · Fintech",
"desc": "A custom portfolio management suite analyzing over 2,000,000 transactions daily with automated deep anomaly detection.",
"spec": "Real-time stream, sub-10ms response lag.",
"year": "2024",
"client": "Confidential — Series B Fintech",
"duration": "14 weeks",
"tech": ["Python", "TensorFlow", "Next.js", "PostgreSQL", "Redis", "Kafka"],
"challenge": "The client needed to process and flag anomalies across 2M+ daily financial transactions in near-real-time. Their legacy batch system introduced 40-minute detection delays, causing significant financial exposure.",
"solution": "We architected a streaming ML pipeline using Apache Kafka for event ingestion and a fine-tuned TensorFlow anomaly detection model served via FastAPI. The frontend dashboard was built on Next.js with server-sent events for live feed updates.",
"results": [
"Sub-10ms average anomaly detection latency",
"99.97% uptime across 6 months of production",
"$2.4M in prevented fraudulent transactions in first quarter",
"40-minute detection lag reduced to under 10 seconds"
]
},
{
"num": "02",
"slug": "chainsupply-network",
"title": "ChainSupply Network",
"tag": "Web3 · Logistics",
"desc": "Transforming transparency paths for global logistics. Let enterprise clients track $800M+ worth of inventory in real time.",
"spec": "Ethereum EVM, gas-optimized contracts.",
"year": "2024",
"client": "Pan-European Logistics Consortium",
"duration": "20 weeks",
"tech": ["Solidity", "React", "IPFS", "Node.js", "Hardhat", "The Graph"],
"challenge": "A consortium of 12 European logistics companies had zero shared visibility into cross-border shipments. Disputes over cargo handoffs cost the group €3M annually in manual reconciliation.",
"solution": "We built a permissioned Ethereum-compatible chain with gas-optimized smart contracts for cargo handoff events. IPFS stores shipment documents immutably, while a React dashboard with The Graph subgraph provides real-time tracking.",
"results": [
"$800M+ inventory value tracked in real time",
"Dispute resolution time cut from 18 days to 4 hours",
"Gas costs reduced by 64% via batching and proxy patterns",
"Zero data integrity incidents since launch"
]
},
{
"num": "03",
"slug": "meditrack-mobile",
"title": "MediTrack Mobile",
"tag": "Mobile · Healthcare",
"desc": "A beautiful, HIPAA-compliant platform deployed across 120+ clinical nodes in Turkey, serving 50k+ active users monthly.",
"spec": "Symmetric encryption, biometrics.",
"year": "2023",
"client": "Ministry-affiliated Healthcare Network",
"duration": "18 weeks",
"tech": ["Flutter", "Firebase", "Django", "AWS", "AES-256", "Face ID / Touch ID"],
"challenge": "120 clinics across Turkey operated on disconnected paper-based patient intake systems. Staff needed a unified, secure mobile platform meeting strict HIPAA and Turkish KVKK data regulations.",
"solution": "We delivered a cross-platform Flutter app with biometric auth, AES-256 encrypted local storage, and a Django REST backend on AWS. Offline-first architecture ensures continuity in low-connectivity rural clinics.",
"results": [
"50,000+ active monthly users across 120 clinical nodes",
"Patient intake time reduced by 73%",
"Full HIPAA + KVKK compliance audit passed",
"99.9% uptime with offline-first resilience"
]
},
{
"num": "04",
"slug": "novamart-platform",
"title": "NovaMart Platform",
"tag": "Web · E-commerce",
"desc": "Headless e-commerce system built for modular scale. Fully optimized bundle handles 15k daily purchases seamlessly.",
"spec": "99/100 Lighthouse performance grade.",
"year": "2023",
"client": "NovaMart — Regional Retail Brand",
"duration": "12 weeks",
"tech": ["Next.js", "GraphQL", "Stripe", "Redis", "Vercel", "Algolia"],
"challenge": "NovaMart's monolithic Magento store was collapsing under 8k daily orders, with page load times exceeding 6 seconds and a 74% cart abandonment rate on mobile.",
"solution": "We rebuilt the platform as a headless Next.js storefront backed by a GraphQL API gateway, Stripe for payments, Redis for session caching, and Algolia for instant product search. Deployed globally on Vercel's edge network.",
"results": [
"Page load time: 6.2s → 0.9s (85% reduction)",
"Lighthouse performance score: 38 → 99",
"Cart abandonment rate dropped from 74% to 31%",
"15,000 daily orders handled without infrastructure changes"
]
}
]
},
"process": {
"badge": "Execution Paths",
"title": "OUR PROCESS",
"desc": "Meticulous technical progression from system architectures to stable production scale.",
"items": [
{
"num": "01",
"title": "Discovery & Blueprinting",
"desc": "We align deeply on business goals, product strategies, architectural limits, and target scopes. You receive a complete technical blueprint before any code is written.",
"timeframe": "Week 1"
},
{
"num": "02",
"title": "System Architecture",
"desc": "We design clean schemas, select modern protocols, and configure databases. We establish automated pipelines and telemetry registries to ensure high speed.",
"timeframe": "Week 2"
},
{
"num": "03",
"title": "Symmetric Sprint Cycles",
"desc": "Rapid iterative development. Every single Friday, you receive a fully compiled, working demo build. Continuous feedback loops eliminate deployment delays.",
"timeframe": "Weeks 3 - 6"
},
{
"num": "04",
"title": "Launch & Autopilot",
"desc": "Deploying via Docker and Kubernetes. We initialize telemetry registers (Jaeger, Prometheus) and configure automated scaling rules so the product scales.",
"timeframe": "Week 7+"
}
]
},
"blog": {
"badge": "Knowledge Registry",
"title": "TECHNICAL BLOCK",
"desc": "In-depth technological research, robust architectures, and elite engineering paradigms.",
"viewAll": "Explore All Registry",
"readMore": "Read Article",
"back": "Return to Registry"
},
"faq": {
"badge": "Telemetry Registry",
"title": "FAQ",
"desc": "Frequently asked questions regarding our engineering lifecycle, security, and engagement models.",
"items": [
{
"q": "What is your typical engagement lifecycle?",
"a": "Most focused MVPs ship fully in 48 weeks. Enterprise-scale platforms generally cycle within 36 months from blueprinting to production launch."
},
{
"q": "Do you collaborate with early-stage ventures?",
"a": "Yes, we partner with promising founders. We support cash + equity setups for companies with strong product fit."
},
{
"q": "How are project updates handled?",
"a": "We operate on tight weekly sprints. Every Friday you receive an interactive demo build, not just a static report."
},
{
"q": "Do you offer post-deployment operational support?",
"a": "We provide comprehensive operational retainers covering real-time monitoring, security patching, and serverless scaling support."
},
{
"q": "What security frameworks do you deploy?",
"a": "We adhere strictly to OWASP guidelines, perform automated static analysis (SAST) on all pipelines, and implement rigid multi-sig and cryptographic configurations on blockchain contracts."
},
{
"q": "Can you help migrate legacy architectures?",
"a": "Yes. We specialize in decoupling monolithic legacy structures into modular microservices or serverless architectures with zero downtime."
}
]
},
"contact": {
"badge": "Initialize Connection",
"title": "GET A QUOTE",
"desc": "Transmit your project blueprint specs, and our founding council will evaluate it immediately.",
"infoBase": "Operational Base",
"infoBaseText": "Istanbul, Turkey",
"infoBaseSub": "Sarıyer, Tech Valley District",
"infoDirect": "Direct Telemetry",
"infoBlueprint": "Corporate Blueprint",
"infoBlueprintText": "Our technical leads typically respond within 12 operational hours. Every request is reviewed directly by our founding architectural council.",
"submittedTitle": "Transmission Secure",
"submittedText": "Your project specs have been received by our telemetry registers. An architect will reach out shortly.",
"formName": "Your Name",
"formNamePlaceholder": "e.g. John Doe",
"formEmail": "Email Address",
"formEmailPlaceholder": "e.g. john@company.com",
"formCompany": "Company Name",
"formCompanyPlaceholder": "e.g. Acme Corp",
"formTrack": "Required Track",
"formScope": "Project Scope / Details",
"formScopePlaceholder": "Describe your technical specifications...",
"formSubmit": "TRANSMIT SPECIFICATIONS"
},
"footer": {
"desc": "Enterprise-grade digital transformation. We build the impossible for the next generation of business.",
"spectrum": "Spectrum",
"registry": "Registry",
"privacy": "Privacy Blueprint",
"terms": "Terms of Operations",
"rights": "All rights reserved."
},
"partners": {
"badge": "Strategic Ecosystem",
"title": "OUR PARTNERS",
"desc": "Deep alignments and key pipeline integrations with global tech giants, specialized research centers, and infrastructure leaders.",
"items": [
{
"name": "Stellar Neural Labs",
"tag": "AI Advancements",
"mono": "SNL",
"year": "2024",
"desc": "Active collaborative research focusing on sovereign language training networks and semantic retrieval nodes."
},
{
"name": "EVM Labs",
"tag": "Web3 Core",
"mono": "EVM",
"year": "2023",
"desc": "Strategic development partner for gas-minimized execution structures and Layer-2 rollups."
},
{
"name": "Alpha Logistics Hub",
"tag": "Supply Chain Networks",
"mono": "ALH",
"year": "2024",
"desc": "Joint consortium integrations mapping immutable custody streams for international cargo nodes."
},
{
"name": "Vercel Edge Network",
"tag": "Headless Architectures",
"mono": "VCL",
"year": "2023",
"desc": "Edge engine partner providing static delivery frameworks and global caching layers."
},
{
"name": "Stripe Systems",
"tag": "Enterprise Payments",
"mono": "STP",
"year": "2024",
"desc": "Seamless financial processing API partner for global, multi-currency ledger settlements."
},
{
"name": "Kafka Streams Inc",
"tag": "Real-time Telemetry",
"mono": "KFK",
"year": "2025",
"desc": "High-throughput data stream configuration and low-latency pipeline designs."
}
]
}
}
+319
View File
@@ -0,0 +1,319 @@
{
"nav": {
"services": "Hizmetler",
"work": "Projeler",
"process": "Süreç",
"blog": "Blog",
"faq": "S.S.S.",
"quote": "Teklif Al",
"admin": "Yönetim",
"partners": "Partnerler",
"brandName": "Ayris",
"brandSub": "Tech"
},
"hero": {
"badge": "Ayris Tech — Mühendislik Zirvesi",
"titleLine1": "Geleceği",
"titleLine2": "İnşa Ediyoruz",
"desc": "AyrisTech, kurumsal dijital dönüşüm için yüksek performanslı sistemler üretir. Yapay Zekâ, Blockchain ve modern web mimarileriyle geleceğin iş dünyasını şekillendiriyoruz.",
"ctaQuote": "Teklif Al",
"ctaWork": "Projelerimizi İncele"
},
"stats": {
"deliveries": "Tamamlanan Proje",
"retention": "Müşteri Bağlılığı",
"regions": "Hizmet Verilen Bölge"
},
"services": {
"badge": "Yetenek Yelpazesi",
"title": "HİZMETLERİMİZ",
"desc": "Geleceğin standartlarını belirleyen teknik derinliğe sahip çözümler üretiyoruz.",
"deliverablesLabel": "Çıktılar",
"items": {
"ai": {
"title": "Yapay Zekâ Çözümleri",
"desc": "Şirketinize özel makine öğrenimi modelleri, anlamsal NLP boru hatları ve kurumsal LLM entegrasyonları tasarlıyoruz."
},
"blockchain": {
"title": "Blockchain Geliştirme",
"desc": "Güvenli akıllı sözleşmeler, özel merkeziyetsiz uygulamalar (dApps) ve bağımsız özel blokzincir ekosistemleri kuruyoruz."
},
"mobile": {
"title": "Mobil Uygulamalar",
"desc": "Flutter ve React Native kullanarak hız, performans ve kusursuz yerel kullanıcı deneyimini öncelikleyen mobil uygulamalar geliştiriyoruz."
},
"web": {
"title": "Web Platformları",
"desc": "Next.js ve sunucu tarafı yapılandırmalarla yüksek performanslı, SEO odaklı ve ölçeklenebilir web sistemleri kuruyoruz."
}
}
},
"work": {
"badge": "Seçilmiş Projeler",
"title": "PROJELERİMİZ",
"desc": "Rakamlarla kanıtlanmış, yüksek performanslı dijital ürünler.",
"specsLabel": "Performans Matrisi",
"analyzeBtn": "Özellikleri Analiz Et",
"galleryTitle": "Uygulama Arayüzleri",
"items": [
{
"num": "01",
"slug": "financeai-dashboard",
"title": "FinanceAI Paneli",
"tag": "YZ · Finans",
"desc": "Günde 2 milyondan fazla işlemi yapay zekâ destekli anomali tespitiyle analiz eden portföy yönetim sistemi.",
"spec": "Gerçek zamanlı akış, 10ms altı yanıt süresi.",
"year": "2024",
"client": "Gizli — Seri B Fintech",
"duration": "14 hafta",
"tech": [
"Python",
"TensorFlow",
"Next.js",
"PostgreSQL",
"Redis",
"Kafka"
],
"challenge": "Müşteri, 2M+ günlük finansal işlemi neredeyse gerçek zamanlı olarak işlemek ve anomalileri tespit etmek zorundaydı. Eski sistem 40 dakikalık gecikme yaratıyordu.",
"solution": "Apache Kafka olay alımı ve FastAPI üzerinden sunulan TensorFlow modeli ile bir akış ML boru hattı tasarladık. Ön uç paneli, canlı akışlar için sunucu tarafı olaylarıyla Next.js üzerine inşa edildi.",
"results": [
"10ms altı ortalama anomali tespit gecikmesi",
"6 aylık üretimde %99,97 çalışma süresi",
"İlk çeyrekte önlenen 2,4 milyon dolar dolandırıcılık",
"40 dakikalık gecikme 10 saniyenin altına düşürüldü"
]
},
{
"num": "02",
"slug": "chainsupply-network",
"title": "ChainSupply Ağı",
"tag": "Web3 · Lojistik",
"desc": "Küresel lojistik için şeffaflık kanalları. Müşterilerin 800 milyon doları aşan envanterlerini anlık takip etmesini sağlıyor.",
"spec": "Ethereum EVM, gaz optimizasyonlu akıllı sözleşmeler.",
"year": "2024",
"client": "Pan-Avrupa Lojistik Konsorsiyumu",
"duration": "20 hafta",
"tech": [
"Solidity",
"React",
"IPFS",
"Node.js",
"Hardhat",
"The Graph"
],
"challenge": "12 Avrupalı lojistik şirketi, sınır ötesi sevkiyatlarda hiç ortak görünürlüğe sahip değildi. Yıllık 3 milyon Euro manuel mutabakat maliyeti oluşuyordu.",
"solution": "Kargo el değiştirme olayları için gaz optimizasyonlu akıllı sözleşmeler içeren Ethereum uyumlu bir zincir inşa ettik. IPFS sevkiyat belgelerini değişmez şekilde saklıyor.",
"results": [
"800M$+ envanter değeri gerçek zamanlı takip altında",
"Uyuşmazlık çözme süresi 18 günden 4 saate düştü",
"Yığınlama ve proxy pattern ile gaz maliyeti %64 azaltıldı",
"Başlangıçtan bu yana sıfır veri bütünlüğü olayı"
]
},
{
"num": "03",
"slug": "meditrack-mobile",
"title": "MediTrack Mobil",
"tag": "Mobil · Sağlık",
"desc": "Türkiye genelinde 120'den fazla klinikte kullanılan, ayda 50 binden fazla aktif kullanıcıya hizmet veren HIPAA uyumlu platform.",
"spec": "Simetrik şifreleme, biyometrik protokoller.",
"year": "2023",
"client": "Bakanlık Bağlantılı Sağlık Ağı",
"duration": "18 hafta",
"tech": [
"Flutter",
"Firebase",
"Django",
"AWS",
"AES-256",
"Yüz Tanıma / Parmak İzi"
],
"challenge": "Türkiye genelindeki 120 klinik, bağlantısız kağıt tabanlı hasta kabul sistemleriyle çalışıyordu. HIPAA ve KVKK düzenlemelerini karşılayan güvenli bir mobil platform gerekiyordu.",
"solution": "Biyometrik kimlik doğrulama, AES-256 şifreli yerel depolama ve AWS üzerinde Django REST backend ile çapraz platform Flutter uygulaması teslim ettik.",
"results": [
"120 klinik düğümünde 50.000+ aylık aktif kullanıcı",
"Hasta kabul süresi %73 azaldı",
"HIPAA + KVKK uyumluluk denetimi başarıyla geçildi",
"Çevrimdışı öncelikli mimari ile %99,9 çalışma süresi"
]
},
{
"num": "04",
"slug": "novamart-platform",
"title": "NovaMart Platformu",
"tag": "Web · E-Ticaret",
"desc": "Modüler ölçeklenebilirlik için tasarlanmış headless e-ticaret altyapısı. Günde 15 binden fazla satışı sorunsuz yönetir.",
"spec": "99/100 Lighthouse performans skoru.",
"year": "2023",
"client": "NovaMart — Bölgesel Perakende Markası",
"duration": "12 hafta",
"tech": [
"Next.js",
"GraphQL",
"Stripe",
"Redis",
"Vercel",
"Algolia"
],
"challenge": "NovaMart'ın monolitik Magento mağazası günde 8.000 sipariş altında çöküyordu. Sayfa yüklenme süresi 6 saniyeyi aşıyor, mobilde sepet terk oranı %74'e ulaşmıştı.",
"solution": "Platformu GraphQL API ağ geçidi, Stripe ödeme sistemi, Redis oturum önbelleği ve Algolia anlık ürün araması ile desteklenen headless Next.js vitrine dönüştürdük.",
"results": [
"Sayfa yükleme: 6,2sn → 0,9sn (%85 azalma)",
"Lighthouse skoru: 38 → 99",
"Sepet terk oranı %74'ten %31'e düştü",
"Altyapı değişikliği olmadan günde 15.000 sipariş karşılandı"
]
}
]
},
"process": {
"badge": "Yürütme Yolları",
"title": "SÜRECİMİZ",
"desc": "Fikir aşamasından canlıya alınma anına kadar her adımda şeffaf ve metodik ilerliyoruz.",
"items": [
{
"num": "01",
"title": "Keşif ve Yol Haritası",
"desc": "İş hedefleri ve mimari sınırlar üzerinde derinlemesine hizalanıyoruz. Kod yazılmadan önce eksiksiz teknik yol haritasını alırsınız.",
"timeframe": "1. Hafta"
},
{
"num": "02",
"title": "Sistem Mimarisi",
"desc": "Veritabanı şemalarını ve haberleşme protokollerini tasarlıyoruz. Yüksek hız için otomatik boru hatlarını ve telemetri kayıtlarını hazırlıyoruz.",
"timeframe": "2. Hafta"
},
{
"num": "03",
"title": "Haftalık Demo Döngüleri",
"desc": "Hızlı yinelemeli geliştirme. Her cuma günü çalışan bir demo sürümü alırsınız. Sürekli geri bildirim döngüleriyle gecikmeleri önlüyoruz.",
"timeframe": "3 - 6. Hafta"
},
{
"num": "04",
"title": "Lansman ve Otopilot",
"desc": "Docker ve Kubernetes ile dağıtımı gerçekleştiriyoruz. Jaeger ve Prometheus telemetrilerini kurarak otomatik ölçeklendirme kurallarını tanımlıyoruz.",
"timeframe": "7+ Hafta"
}
]
},
"blog": {
"badge": "Bilgi Deposu",
"title": "TEKNİK BLOK",
"desc": "Derinlemesine teknoloji analizleri, sistem mimarileri ve mühendislik pratikleri.",
"viewAll": "Tüm Yazıları Gör",
"readMore": "Yazıyı Oku",
"back": "Geri Dön"
},
"faq": {
"badge": "Telemetri Kayıt Defteri",
"title": "S.S.S.",
"desc": "Sürecimiz, güvenlik ve iş birliği modellerimiz hakkında merak edilenler.",
"items": [
{
"q": "Proje teslim süreniz nedir?",
"a": "Odaklanmış MVP'ler genellikle 4-8 hafta içinde tamamlanır. Kurumsal düzeydeki platformlar ise analizden lansmana kadar 3-6 ay sürer."
},
{
"q": "Erken aşama girişimlerle çalışıyor musunuz?",
"a": "Evet, güçlü ürün-pazar uyumu hedefleri olan girişimler için nakit + hisse ortaklığı modellerini destekliyoruz."
},
{
"q": "Proje güncellemeleri nasıl yapılıyor?",
"a": "Haftalık sprintler halinde çalışıyoruz. Her cuma günü sadece statik rapor değil, etkileşimli çalışan bir demo sürümü alırsınız."
},
{
"q": "Lansman sonrası destek veriyor musunuz?",
"a": "Evet, gerçek zamanlı izleme, güvenlik yamaları ve sunucusuz ölçeklendirme desteğini içeren kapsamlı bakım sözleşmeleri sunuyoruz."
},
{
"q": "Hangi güvenlik protokollerini uyguluyorsunuz?",
"a": "OWASP kurallarına sıkı sıkıya bağlıyız, boru hatlarımızda otomatik statik kod analizi (SAST) yapıyor ve akıllı sözleşmelerde çoklu imza kullanıyoruz."
},
{
"q": "Mevcut eski sistemlerimizi taşıyabilir misiniz?",
"a": "Evet. Monolitik eski sistemleri, sıfır kesintiyle modern modüler mikroservislere veya sunucusuz mimarilere dönüştürüyoruz."
}
]
},
"contact": {
"badge": "Bağlantıyı Başlat",
"title": "TEKLİF ALIN",
"desc": "Gelecek projenizi bizimle paylaşın, uzman ekibimiz hemen iletişime geçsin.",
"infoBase": "Operasyonel Merkez",
"infoBaseText": "İstanbul, Türkiye",
"infoBaseSub": "Sarıyer, Teknopark Bölgesi",
"infoDirect": "Doğrudan İletişim",
"infoBlueprint": "Kurumsal Yaklaşım",
"infoBlueprintText": "Mühendislik ekibimiz genellikle 12 iş saati içinde yanıt verir. Her talep kurucu konseyimiz tarafından incelenir.",
"submittedTitle": "Gönderim Güvenli",
"submittedText": "Proje detaylarınız telemetri sistemlerimize ulaştı. Mühendislerimiz kısa süre içinde sizinle iletişime geçecektir.",
"formName": "Adınız",
"formNamePlaceholder": "Örn: Ahmet Yılmaz",
"formEmail": "E-posta Adresiniz",
"formEmailPlaceholder": "Örn: ahmet@sirket.com",
"formCompany": "Şirket Adı",
"formCompanyPlaceholder": "Örn: Acme A.Ş.",
"formTrack": "Gerekli Alan",
"formScope": "Proje Kapsamı / Detaylar",
"formScopePlaceholder": "Teknik gereksinimlerinizi ve projenizi açıklayın...",
"formSubmit": "DETAYLARI GÖNDER"
},
"footer": {
"desc": "Kurumsal dijital dönüşüm. Geleceğin iş dünyası için imkansızı inşa ediyoruz.",
"spectrum": "Yelpaze",
"registry": "Kayıtlar",
"privacy": "Gizlilik Politikası",
"terms": "Kullanım Şartları",
"rights": "Tüm hakları saklıdır."
},
"partners": {
"badge": "Stratejik Ekosistem",
"title": "PARTNERLERİMİZ",
"desc": "Küresel teknoloji liderleri, araştırma laboratuvarları ve kurumsal altyapı sağlayıcılarıyla olan ortaklıklarımız.",
"items": [
{
"name": "Stellar Neural Labs",
"tag": "YZ Araştırmaları",
"mono": "SNL",
"year": "2024",
"desc": "Büyük dil modelleri (LLM) ve anlamsal veri analizi algoritmaları üzerine ortak Ar-Ge çalışmaları yürütüyoruz."
},
{
"name": "EVM Labs",
"tag": "Web3 Altyapısı",
"mono": "EVM",
"year": "2023",
"desc": "Gaz optimizasyonu yüksek akıllı sözleşmeler ve L2 ölçeklendirme çözümleri geliştirme ortağımız."
},
{
"name": "Alpha Logistics Hub",
"tag": "Tedarik Zinciri",
"mono": "ALH",
"year": "2024",
"desc": "Küresel ölçekte değişmez izlenebilirlik ağları kurmak üzere konsorsiyum blokzincir entegrasyon ortağımız."
},
{
"name": "Vercel Edge Network",
"tag": "Headless Web",
"mono": "VCL",
"year": "2023",
"desc": "Next.js sunucu tarafı işleme ve sınır bilgi işlem (edge computing) teknolojileri entegrasyon ortağımız."
},
{
"name": "Stripe Systems",
"tag": "Küresel Ödemeler",
"mono": "STP",
"year": "2024",
"desc": "Çok para birimli, yüksek güvenlikli finansal akış ödeme geçitleri kurumsal entegrasyon partnerimiz."
},
{
"name": "Kafka Streams Inc",
"tag": "Gerçek Zamanlı Veri",
"mono": "KFK",
"year": "2025",
"desc": "Saniyede on binlerce olayı milisaniyeler seviyesinde işleyen veri boru hatları tasarım ortağımız."
}
]
}
}
+11
View File
@@ -0,0 +1,11 @@
import "server-only";
import type { Locale } from "./i18n-config";
const dictionaries = {
en: () => import("./dictionaries/en.json").then((module) => module.default),
tr: () => import("./dictionaries/tr.json").then((module) => module.default),
};
export const getDictionary = async (locale: Locale) => {
return dictionaries[locale]?.() ?? dictionaries.tr();
};
+6
View File
@@ -0,0 +1,6 @@
export const i18n = {
defaultLocale: "tr",
locales: ["en", "tr"],
} as const;
export type Locale = (typeof i18n)["locales"][number];
+50
View File
@@ -0,0 +1,50 @@
import crypto from "crypto";
const SECRET = process.env.JWT_SECRET || "default_ayristech_super_secure_admin_jwt_secret_key_2026";
// ── PASSWORD HASHING ──
export function hashPassword(password: string): string {
const salt = crypto.randomBytes(16).toString("hex");
const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, "sha512").toString("hex");
return `${salt}:${hash}`;
}
export function verifyPassword(password: string, storedHash: string): boolean {
const [salt, hash] = storedHash.split(":");
if (!salt || !hash) return false;
const verify = crypto.pbkdf2Sync(password, salt, 1000, 64, "sha512").toString("hex");
return hash === verify;
}
// ── NATIVE LIGHTWEIGHT JWT SIGN & VERIFY ──
export function signToken(payload: any): string {
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
// Expire session in 24 hours
const body = Buffer.from(JSON.stringify({ ...payload, exp: Date.now() + 24 * 60 * 60 * 1000 })).toString("base64url");
const hmac = crypto.createHmac("sha256", SECRET);
hmac.update(`${header}.${body}`);
const signature = hmac.digest("base64url");
return `${header}.${body}.${signature}`;
}
export function verifyToken(token: string): any {
try {
const [header, body, signature] = token.split(".");
if (!header || !body || !signature) return null;
const hmac = crypto.createHmac("sha256", SECRET);
hmac.update(`${header}.${body}`);
const expectedSignature = hmac.digest("base64url");
if (signature !== expectedSignature) return null;
const payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
if (payload.exp < Date.now()) return null; // Expired
return payload;
} catch {
return null;
}
}
+25
View File
@@ -0,0 +1,25 @@
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import pkg from "pg";
const { Pool } = pkg;
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
const getPrismaClient = () => {
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
throw new Error("DATABASE_URL is not configured in your environment variables.");
}
const pool = new Pool({ connectionString });
const adapter = new PrismaPg(pool);
return new PrismaClient({ adapter });
};
export const prisma = globalForPrisma.prisma ?? getPrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+6
View File
@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
};
export default nextConfig;
+3254
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
{
"name": "ayristech",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"framer-motion": "^12.40.0",
"next": "16.2.6",
"pg": "^8.21.0",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/pg": "^8.20.0",
"@types/react": "^19",
"@types/react-dom": "^19",
"prisma": "^7.8.0",
"tailwindcss": "^4",
"ts-node": "^10.9.2",
"typescript": "^5"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
+7
View File
@@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
+15
View File
@@ -0,0 +1,15 @@
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "ts-node ./prisma/seed.ts",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});
+67
View File
@@ -0,0 +1,67 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model Project {
id Int @id @default(autoincrement())
num String @unique
slug String @unique
title String
tag String
desc String
spec String
year String
client String
duration String
tech String[]
challenge String
solution String
results String[]
image String @default("")
gallery String[] @default([])
website String @default("")
}
model BlogPost {
id Int @id @default(autoincrement())
slug String @unique
date String
author String
authorRole String
image String
trTitle String
trExcerpt String
trReadingTime String
trCategory String
trTags String[]
trContent String
enTitle String
enExcerpt String
enReadingTime String
enCategory String
enTags String[]
enContent String
}
model Partner {
id Int @id @default(autoincrement())
name String @unique
tag String
mono String
year String
desc String
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String // Salty pbkdf2 hashed password
createdAt DateTime @default(now())
}
+258
View File
@@ -0,0 +1,258 @@
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import pkg from "pg";
import crypto from "crypto";
const { Pool } = pkg;
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
throw new Error("DATABASE_URL is not configured in your environment variables.");
}
const pool = new Pool({ connectionString });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
function hashPassword(password: string): string {
const salt = crypto.randomBytes(16).toString("hex");
const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, "sha512").toString("hex");
return `${salt}:${hash}`;
}
async function main() {
console.log("🌱 Database seeding started...");
// ── CLEAR EXISTING DATA ──
console.log("🧹 Clearing existing data...");
await prisma.project.deleteMany({});
await prisma.blogPost.deleteMany({});
await prisma.partner.deleteMany({});
await prisma.user.deleteMany({});
// ── SEED PROJECTS ──
console.log("📁 Seeding Projects...");
const projects = [
{
num: "01",
slug: "financeai-dashboard",
title: "FinanceAI Paneli",
tag: "YZ · Finans",
desc: "Günde 2 milyondan fazla işlemi yapay zekâ destekli anomali tespitiyle analiz eden portföy yönetim sistemi.",
spec: "Gerçek zamanlı akış, 10ms altı yanıt süresi.",
year: "2024",
client: "Gizli — Seri B Fintech",
duration: "14 hafta",
tech: ["Python", "TensorFlow", "Next.js", "PostgreSQL", "Redis", "Kafka"],
challenge: "Müşteri, 2M+ günlük finansal işlemi neredeyse gerçek zamanlı olarak işlemek ve anomalileri tespit etmek zorundaydı. Eski sistem 40 dakikalık gecikme yaratıyordu.",
solution: "Apache Kafka olay alımı ve FastAPI üzerinden sunulan TensorFlow modeli ile bir akış ML boru hattı tasarladık. Ön uç paneli, canlı akışlar için sunucu tarafı olaylarıyla Next.js üzerine inşa edildi.",
results: [
"10ms altı ortalama anomali tespit gecikmesi",
"6 aylık üretimde %99,97 çalışma süresi",
"İlk çeyrekte önlenen 2,4 milyon dolar dolandırıcılık",
"40 dakikalık gecikme 10 saniyenin altına düşürüldü"
]
},
{
num: "02",
slug: "chainsupply-network",
title: "ChainSupply Ağı",
tag: "Web3 · Lojistik",
desc: "Küresel lojistik için şeffaflık kanalları. Müşterilerin 800 milyon doları aşan envanterlerini anlık takip etmesini sağlıyor.",
spec: "Ethereum EVM, gaz optimizasyonlu akıllı sözleşmeler.",
year: "2024",
client: "Pan-Avrupa Lojistik Konsorsiyumu",
duration: "20 hafta",
tech: ["Solidity", "React", "IPFS", "Node.js", "Hardhat", "The Graph"],
challenge: "12 Avrupalı lojistik şirketi, sınır ötesi sevkiyatlarda hiç ortak görünürlüğe sahip değildi. Yıllık 3 milyon Euro manuel mutabakat maliyeti oluşuyordu.",
solution: "Kargo el değiştirme olayları için gaz optimizasyonlu akıllı sözleşmeler içeren Ethereum uyumlu bir zincir inşa ettik. IPFS sevkiyat belgelerini değişmez şekilde saklıyor.",
results: [
"800M$+ envanter değeri gerçek zamanlı takip altında",
"Uyuşmazlık çözme süresi 18 günden 4 saate düştü",
"Yığınlama ve proxy pattern ile gaz maliyeti %64 azaltıldı",
"Başlangıçtan bu yana sıfır veri bütünlüğü olayı"
]
},
{
num: "03",
slug: "meditrack-mobile",
title: "MediTrack Mobil",
tag: "Mobil · Sağlık",
desc: "Türkiye genelinde 120'den fazla klinikte kullanılan, ayda 50 binden fazla aktif kullanıcıya hizmet veren HIPAA uyumlu platform.",
spec: "Simetrik şifreleme, biyometrik protokoller.",
year: "2023",
client: "Bakanlık Bağlantılı Sağlık Ağı",
duration: "18 hafta",
tech: ["Flutter", "Firebase", "Django", "AWS", "AES-256", "Yüz Tanıma / Parmak İzi"],
challenge: "Türkiye genelindeki 120 klinik, bağlantısız kağıt tabanlı hasta kabul sistemleriyle çalışıyordu. HIPAA ve KVKK düzenlemelerini karşılayan güvenli bir mobil platform gerekiyordu.",
solution: "Biyometrik kimlik doğrulama, AES-256 şifreli yerel depolama ve AWS üzerinde Django REST backend ile çapraz platform Flutter uygulaması teslim ettik.",
results: [
"120 klinik düğümünde 50.000+ aylık aktif kullanıcı",
"Hasta kabul süresi %73 azaldı",
"HIPAA + KVKK uyumluluk denetimi başarıyla geçildi",
"Çevrimdışı öncelikli mimari ile %99,9 çalışma süresi"
]
},
{
num: "04",
slug: "novamart-platform",
title: "NovaMart Platformu",
tag: "Web · E-Ticaret",
desc: "Modüler ölçeklenebilirlik için tasarlanmış headless e-ticaret altyapısı. Günde 15 binden fazla satışı sorunsuz yönetir.",
spec: "99/100 Lighthouse performans skoru.",
year: "2023",
client: "NovaMart — Bölgesel Perakende Markası",
duration: "12 hafta",
tech: ["Next.js", "GraphQL", "Stripe", "Redis", "Vercel", "Algolia"],
challenge: "NovaMart'ın monolitik Magento mağazası günde 8.000 sipariş altında çöküyordu. Sayfa yüklenme süresi 6 saniyeyi aşıyor, mobilde sepet terk oranı %74'e ulaşmıştı.",
solution: "Platformu GraphQL API ağ geçidi, Stripe ödeme sistemi, Redis oturum önbelleği ve Algolia anlık ürün araması ile desteklenen headless Next.js vitrine dönüştürdük.",
results: [
"Sayfa yükleme: 6,2sn → 0,9sn (%85 azalma)",
"Lighthouse skoru: 38 → 99",
"Sepet terk oranı %74'ten %31'e düştü",
"Altyapı değişikliği olmadan günde 15.000 sipariş karşılandı"
]
}
];
for (const p of projects) {
await prisma.project.create({ data: p });
}
// ── SEED BLOG POSTS ──
console.log("📝 Seeding Blog Posts...");
const posts = [
{
slug: "real-time-anomaly-detection-ai",
date: "2026-05-15",
author: "Selin Arslan",
authorRole: "Head of AI Research",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1200&q=80",
trTitle: "Yapay Zekâ ile Anomali Tespiti: Finansal Sistemleri Koruma Yöntemleri",
trExcerpt: "Yapay zekâ destekli gözetim sistemleri, günümüz fintech uygulamalarında dolandırıcılığı sıfıra indirmek için nasıl konumlandırılıyor?",
trReadingTime: "5 dk okuma",
trCategory: "YZ · Finans",
trTags: ["AI", "Kafka", "Machine Learning", "Fintech", "FastAPI"],
trContent: `## Finansal Mimarilerde Hız ve Güvenlik Dengesi\n\nGünümüz finans ekosistemlerinde hız her şeydir. Günde milyonlarca işlemin aktığı sistemlerde, tek bir şüpheli işlemin gözden kaçması milyonlarca dolarlık kayıplara ve itibar zedelenmesine yol açabilir. Eski nesil toplu (batch) işleme sistemleri, işlemleri saatler sonra kontrol ettiği için dolandırıcılığı engellemede yetersiz kalmaktadır.\n\nİşte tam bu noktada **Yapay Zekâ destekli gerçek zamanlı anomali tespiti** devreye giriyor. Ayris Tech olarak geliştirdiğimiz mimarilerde, milisaniyeler seviyesinde karar üreten sistemleri nasıl tasarladığımızı inceleyelim.\n\n### Olay Akışı (Event Streaming) ve Kafka Entegrasyonu\n\nGerçek zamanlı bir sistemin kalbi veri taşıma hatlarıdır. Apache Kafka kullanarak, işlem isteklerini kuyruğa alıp eşzamansız (asynchronous) olarak ML modeline aktarıyoruz:\n\n\`\`\`javascript\n// Sistem olay akışı şeması\n[İşlem Talebi] -> [API Gateway] -> [Kafka Topic] -> [FastAPI ML Model] -> [Karar Servisi]\n\`\`\`\n\nBu sayede ön yüz uygulamalarında donma veya gecikme yaşanmadan arka planda saniyede on binlerce veri paketi analiz edilmektedir.\n\n### Anomali Tespiti Modelleri\n\nGeleneksel kural tabanlı sistemler yerine, **Isolation Forest** ve **Autoencoder (Oto-kodlayıcı)** yapay sinir ağları kullanıyoruz. Oto-kodlayıcılar, normal finansal işlemleri sıkıştırıp yeniden inşa etmeyi öğrenir. Eğer gelen yeni bir işlem normal şablona uymuyorsa, yeniden inşa hatası (reconstruction error) yüksek çıkar ve sistem bunu anında anomali olarak işaretler.\n\n- **Düşük Gecikme:** TensorFlow C++ runtime entegrasyonu sayesinde model çıkarım (inference) süresini 10ms'nin altına indiriyoruz.\n- **Sıfır Kesinti:** Modellerimizi canlı yayında (hot-swap) güncelleyebiliyoruz, böylece sistem durmadan yeni dolandırıcılık yöntemlerine karşı güncelleniyor.\n\n> **Sonuç:** Doğru tasarlanmış bir yapay zekâ hattı, şirketlerin finansal risklerini neredeyse sıfıra indirir.`,
enTitle: "Real-time Anomaly Detection with AI: Securing Financial Architectures",
enExcerpt: "How are artificial intelligence-driven surveillance engines positioned to minimize fintech fraud to absolute zero?",
enReadingTime: "5 min read",
enCategory: "AI · Fintech",
enTags: ["AI", "Kafka", "Machine Learning", "Fintech", "FastAPI"],
enContent: `## Speed vs. Security in Modern Financial Stacks\n\nIn today's global financial ecosystems, speed is everything. With millions of transactions flowing through databases daily, letting a single fraudulent activity slip by can lead to catastrophic losses and severely damaged brand trust. Traditional batch processing systems are obsolete—detecting fraud hours after a transaction completes is no longer acceptable.\n\nThis is where **artificial intelligence-driven real-time anomaly detection** changes the game. Let's analyze how we architect sub-10ms neural evaluation engines at Ayris Tech.\n\n### Event Streaming and Kafka Architecture\n\nThe cornerstone of any real-time system is the data ingestion pipeline. Utilizing Apache Kafka, we ingest transaction event streams asynchronously and route them directly to our inference nodes:\n\n\`\`\`javascript\n// Event streaming schema\n[Transaction Request] -> [API Gateway] -> [Kafka Topic] -> [FastAPI ML Engine] -> [Decision Hub]\n\`\`\`\n\nThis decouples the heavy neural analysis from the transactional user interface, ensuring perfect responsive stability under heavy loads.\n\n### Anomaly Detection Models\n\nRather than hardcoded conditional rules, we utilize advanced **Isolation Forests** and Deep Neural **Autoencoders**. Autoencoders learn to compress and reconstruct normal transactional behaviors. When an anomalous transaction occurs, the reconstruction error spikes, immediately triggering an automated lock.\n\n- **Sub-10ms Latency:** Deployed via optimized TensorFlow runtimes, model inference lags remain well under 10ms.\n- **Dynamic Hot-Swaps:** Models are retrained and updated on-the-fly, providing uninterrupted security layers against changing vectors.\n\n> **Verdict:** A properly engineered streaming ML pipeline reduces financial exposure to absolute zero.`
},
{
slug: "web3-consortium-networks-logistics",
date: "2026-05-10",
author: "Emre Doğan",
authorRole: "Web3 Lead Engineer",
image: "https://images.unsplash.com/photo-1639762681485-074b7f938ba0?auto=format&fit=crop&w=1200&q=80",
trTitle: "Küresel Lojistikte Web3 Devrimi: Konsorsiyum Ağı Nasıl Kurulur?",
trExcerpt: "Çok aktörlü tedarik zinciri ağlarında şeffaflığı ve veri bütünlüğünü akıllı sözleşmeler ve değişmez kayıtlarla sağlamanın yolları.",
trReadingTime: "7 dk okuma",
trCategory: "Web3 · Lojistik",
trTags: ["Solidity", "Blockchain", "IPFS", "EVM", "Smart Contracts"],
trContent: `## Lojistikte Güven Problemi\n\nKüresel ticarette bir ürün, üreticiden tüketiciye ulaşana kadar ortalama 15 farklı aracı firmanın (limanlar, gümrükler, yerel lojistik firmaları, taşıyıcılar) elinden geçer. Her aktörün kendi veritabanını tutması; veri uyuşmazlıklarına, kayıp evraklara ve haftalar süren mutabakat süreçlerine sebep olur.\n\n**Konsorsiyum blokzincir ağları (Consortium Blockchains)**, tüm bu tarafların ortak, değişmez ve şeffaf bir defter üzerinde anlaşmasını sağlar.\n\n### Solidity ile Gaz Optimizasyonlu Akıllı Sözleşmeler\n\nLojistik olaylarında her el değiştirme (handoff) blokzincire yazıldığından, işlem ücretleri (gas fee) kritik öneme sahiptir. EVM (Ethereum Virtual Machine) tabanlı sistemlerde maliyetleri düşürmek için şu teknikleri kullanıyoruz:\n\n1. **Batching (Yığınlama):** Birden fazla kargo durum güncellemesini tek bir işlemde toplu olarak zincire göndermek.\n2. **Proxy Patterns (Vekil Kalıpları):** Sözleşme kodunu her seferinde baştan dağıtmak yerine, ortak mantığı barındıran tek bir vekil üzerinden çalıştırarak bellek tasarrufu sağlamak.\n\n\`\`\`solidity\n// Gas optimized handoff event struct\nstruct Handoff {\n bytes32 packageId;\n address sender;\n address receiver;\n uint32 timestamp;\n bytes32 docHash; // IPFS document reference\n}\n\`\`\`\n\n### Değişmez Belge Saklama: IPFS Entegrasyonu\n\nFaturalar, konşimentolar ve gümrük belgeleri gibi büyük dosyaları doğrudan blokzincir üzerinde saklamak inanılmaz pahalıdır. Bu yüzden, dosyaları **IPFS (InterPlanetary File System)** üzerinde saklayıp, sadece bu belgelerin cryptographic hash (parmak izi) değerlerini akıllı sözleşmeye kaydediyoruz. Belgelerde yapılacak en ufak bir değişiklik parmak izini bozacağı için evrakta sahtecilik tamamen imkansız hale gelir.\n\nLojistik ağınızı blokzincir teknolojisiyle güçlendirerek, mutabakat sürelerini günlerden dakikalara indirebilirsiniz.`,
enTitle: "The Web3 Revolution in Global Logistics: Architecting Consortium Networks",
enExcerpt: "How smart contracts and immutable logs secure multi-actor transparency and data integrity in modern supply chains.",
enReadingTime: "7 min read",
enCategory: "Web3 · Logistics",
enTags: ["Solidity", "Blockchain", "IPFS", "EVM", "Smart Contracts"],
enContent: `## The Trust Gap in Modern Cargo\n\nBefore a physical good moves from manufacturer to consumer, it passes through an average of 15 intermediaries—customs brokers, freight forwarders, terminal operators, and local distributors. When every actor maintains a siloed legacy database, friction, lost documents, and massive reconciliation overheads are inevitable.\n\nA **Consortium Blockchain** establishes a single, shared, and cryptographically immutable ledger that all actors can trust implicitly.\n\n### Solidity Optimizations for Real-World Workflows\n\nBecause every logistics event represents an on-chain transaction, optimizing gas consumption within EVM (Ethereum Virtual Machine) sandboxes is of paramount importance:\n\n1. **Transaction Batching:** Grouping multiple state shifts into a single cryptographic payload.\n2. **Proxy Patterns:** Deploying upgradeable structural proxies (ERC-1967) to drastically reduce initial contract footprint and variable deployment gas fees.\n\n\`\`\`solidity\n// Gas optimized handoff event struct\nstruct Handoff {\n bytes32 packageId;\n address sender;\n address receiver;\n uint32 timestamp;\n bytes32 docHash; // IPFS document reference\n}\n\`\`\`\n\n### Immutable Assets: IPFS Document Anchoring\n\nUploading multi-megabyte PDFs (such as bills of lading) directly to Ethereum storage is financially unviable. We bypass this by offloading files to **IPFS (InterPlanetary File System)**. The generated cryptographic hash is then written to the ledger. This guarantees document integrity: even a single modified pixel in a PDF will yield a totally different hash, making tampering instantly visible.\n\nTransitioning to consortium ledger architectures shrinks contract reconciliation latency from 18 days to less than 4 hours.`
},
{
slug: "headless-ecommerce-conversion-rates",
date: "2026-05-01",
author: "Mustafa Yıldız",
authorRole: "Founder & Architect",
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=1200&q=80",
trTitle: "Headless E-Ticaret Mimarisi ve Sayfa Hızının Dönüşüm Oranlarına Etkisi",
trExcerpt: "Milisaniyeler satış demektir. Headless Next.js altyapılarıyla yükleme sürelerini %80 kısaltıp sepet terk oranını nasıl düşürdük?",
trReadingTime: "6 dk okuma",
trCategory: "Web · E-Ticaret",
trTags: ["Next.js", "Headless CMS", "GraphQL", "Performance", "SEO"],
trContent: `## Milisaniyelerin Ticari Değeri\n\nE-ticarette her milisaniye cironuzu etkiler. Google verilerine göre, mobil sayfa yüklenme süresi 1 saniyeden 3 saniyeye çıktığında hemen çıkma oranı (bounce rate) %32 artmaktadır. Geleneksel monolitik e-ticaret altyapıları (eski Magento, WooCommerce vb.), sunucu taraflı hantal yapıları ve sıkışık veri sorguları nedeniyle bu hız hedeflerini yakalayamazlar.\n\n**Headless (Kafasız) Mimari**, arka plan ticaret mantığı ile ön yüz görsel katmanını birbirinden tamamen ayırarak bu sorunu kökten çözer.\n\n### Headless Next.js ile 99/100 Performansı\n\nAyris Tech e-ticaret projelerinde, ön yüzde **Next.js App Router** kullanırken arka planda GraphQL API ağ geçidiyle haberleşen headless motorları tercih ediyoruz. Bu yaklaşımın temel avantajları:\n\n- **Static Generation (SSG):** Ürün sayfalarını derleme aşamasında (build time) statik HTML olarak üretiyor ve küresel CDN'ler üzerinden anında yüklenecek şekilde dağıtıyoruz.\n- **Incremental Static Regeneration (ISR):** Fiyat veya stok değiştiğinde tüm siteyi baştan derlemek yerine, sadece ilgili ürün sayfasını arka planda milisaniyeler içinde güncelliyoruz.\n\n\`\`\`javascript\n// Next.js ISR (Incremental Static Regeneration) örneği\nexport const revalidate = 60; // sayfayı en fazla dakikada bir güncelle\n\`\`\`\n\n### Arama Deneyimi ve Algolia Entegrasyonu\n\nKullanıcılar arama çubuğuna tıkladığında binlerce ürün arasından aradıklarını anında bulmalıdır. Bunun için headless altyapımızı **Algolia** anlık arama indeksi ile destekliyoruz. Her tuşa basıldığında (on keypress) 10ms içinde sonuçlar listelenir, bu da doğrudan sepet ekleme oranlarını %24 artırır.\n\nEğer sepet terk oranlarınızı düşürmek ve küresel çapta en hızlı alışveriş deneyimini sunmak istiyorsanız, e-ticaret sitenizi modern headless mimarilere taşımalısınız.`,
enTitle: "Headless E-commerce & Page Speed Impact on Conversion Rates",
enExcerpt: "Milliseconds are sales. How we cut load times by 80% and minimized shopping cart abandonment with headless Next.js layouts.",
enReadingTime: "6 min read",
enCategory: "Web · E-commerce",
enTags: ["Next.js", "Headless CMS", "GraphQL", "Performance", "SEO"],
enContent: `## The Financial Value of Milliseconds\n\nIn the digital storefront arena, milliseconds directly correlate to transactional yield. Google data proves that when mobile page load speeds increase from 1 to 3 seconds, bounce rates spike by over 32%. Monolithic legacy suites (such as un-decoupled Magento or standard WooCommerce instances) collapse under these performance parameters due to heavy database roundtrips and synchronous asset delivery.\n\n**Headless Architecture** breaks this bottleneck by decoupling the backend transactional commerce engine from the client-facing presentation layer.\n\n### Yielding 99/100 Lighthouse Grades with Next.js\n\nAt Ayris Tech, we deploy **Next.js App Router** on the edge combined with GraphQL API gateways to serve transactional data. This approach offers distinct performance advantages:\n\n- **Static Generation (SSG):** Product catalogues are compiled into lightweight static HTML at build time and distributed across global edge nodes for near-instant rendering.\n- **Incremental Static Regeneration (ISR):** When pricing or stock variables change, we update specific nodes on-the-fly in the background rather than rebuild the entire portal.\n\n\`\`\`javascript\n// Next.js ISR (Incremental Static Regeneration) config\nexport const revalidate = 60; // invalidate cached pages every 60 seconds\n\`\`\`\n\n### Instant Search with Algolia\n\nSlow search bars kill conversions. We connect our headless catalogues with **Algolia** indexing arrays to deliver predictive, instant search results on keypress inside of 10ms. This micro-interaction alone yields an average 24% uplift in Add-to-Cart events.\n\nMigrating to high-craft headless setups is no longer a luxury—it is the modern benchmark for high-converting global trade.`
}
];
for (const post of posts) {
await prisma.blogPost.create({ data: post });
}
// ── SEED PARTNERS ──
console.log("🤝 Seeding Partners...");
const partners = [
{
name: "Stellar Neural Labs",
tag: "YZ Araştırmaları",
mono: "SNL",
year: "2024",
desc: "Büyük dil modelleri (LLM) ve anlamsal veri analizi algoritmaları üzerine ortak Ar-Ge çalışmaları yürütüyoruz."
},
{
name: "EVM Labs",
tag: "Web3 Altyapısı",
mono: "EVM",
year: "2023",
desc: "Gaz optimizasyonu yüksek akıllı sözleşmeler ve L2 ölçeklendirme çözümleri geliştirme ortağımız."
},
{
name: "Alpha Logistics Hub",
tag: "Tedarik Zinciri",
mono: "ALH",
year: "2024",
desc: "Küresel ölçekte değişmez izlenebilirlik ağları kurmak üzere konsorsiyum blokzincir entegrasyon ortağımız."
},
{
name: "Vercel Edge Network",
tag: "Headless Web",
mono: "VCL",
year: "2023",
desc: "Next.js sunucu tarafı işleme ve sınır bilgi işlem (edge computing) teknolojileri entegrasyon ortağımız."
},
{
name: "Stripe Systems",
tag: "Küresel Ödemeler",
mono: "STP",
year: "2024",
desc: "Çok para birimli, yüksek güvenlikli finansal akış ödeme geçitleri kurumsal entegrasyon partnerimiz."
},
{
name: "Kafka Streams Inc",
tag: "Gerçek Zamanlı Veri",
mono: "KFK",
year: "2025",
desc: "Saniyede on binlerce olayı milisaniyeler seviyesinde işleyen veri boru hatları tasarım ortağımız."
}
];
for (const p of partners) {
await prisma.partner.create({ data: p });
}
// ── SEED USERS ──
console.log("👤 Seeding Admin User...");
const adminPasswordHash = hashPassword("admin123");
await prisma.user.create({
data: {
username: "admin",
password: adminPasswordHash,
},
});
console.log("✅ Database seeding complete!");
}
main()
.catch((e) => {
console.error("❌ Seeding failed:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
+64
View File
@@ -0,0 +1,64 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { i18n } from "./i18n-config";
function getLocale(request: NextRequest): string {
const acceptLanguage = request.headers.get("accept-language");
if (!acceptLanguage) return i18n.defaultLocale;
// Simple and ultra-robust parsing for accept-language header
const preferredLocales = acceptLanguage
.split(",")
.map((lang) => {
const [locale, q] = lang.split(";q=");
return {
locale: locale.trim().split("-")[0].toLowerCase(), // e.g. "tr", "en"
priority: q ? parseFloat(q) : 1.0,
};
})
.sort((a, b) => b.priority - a.priority);
for (const pref of preferredLocales) {
if (i18n.locales.includes(pref.locale as any)) {
return pref.locale;
}
}
return i18n.defaultLocale;
}
export function proxy(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// Skip static assets, internal paths, and favicon
if (
pathname.startsWith("/_next") ||
pathname.startsWith("/api") ||
pathname.startsWith("/favicon.ico") ||
pathname.match(/\.(png|jpg|jpeg|gif|svg|webp|ico|css|js|woff|woff2|ttf|otf|json)$/)
) {
return;
}
// Check if pathname already contains a supported locale prefix
const pathnameIsMissingLocale = i18n.locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
const locale = getLocale(request);
// Redirect /xxx to /locale/xxx
return NextResponse.redirect(
new URL(
`/${locale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,
request.url
)
);
}
}
export const config = {
// Matcher ignoring static items and api routes
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
+41
View File
@@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"paths": {
"@/*": [
"./*"
]
},
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
File diff suppressed because one or more lines are too long