feat: implement programmatic SEO routes and optimize meta tags
This commit is contained in:
@@ -7,8 +7,8 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
return {
|
||||
title: `${dict.blog.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
|
||||
description: dict.blog.desc,
|
||||
title: dict.meta.blog.title,
|
||||
description: dict.meta.blog.desc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
return {
|
||||
title: `${dict.contact.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
|
||||
description: dict.contact.desc,
|
||||
title: dict.meta.contact.title,
|
||||
description: dict.meta.contact.desc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { getDictionary } from "@/get-dictionary";
|
||||
import type { Locale } from "@/i18n-config";
|
||||
import WorkClient from "@/components/WorkClient";
|
||||
import { getProjects } from "@/app/actions";
|
||||
import { slugify } from "@/lib/utils";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale, slug: string }> }) {
|
||||
const { lang, slug } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
const projects = await getProjects();
|
||||
|
||||
const filteredProjects = projects.filter((p: any) => slugify(p.tag) === slug);
|
||||
if (filteredProjects.length === 0) return { title: "Not Found" };
|
||||
|
||||
const tagName = filteredProjects[0].tag;
|
||||
const titleStr = lang === "tr" ? `${tagName} Ajansı` : `${tagName} Agency`;
|
||||
|
||||
return {
|
||||
title: `${titleStr} | ${dict.nav.brandName}`,
|
||||
description: `${titleStr} - ${dict.work.desc}`,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ExpertisePage({ params }: { params: Promise<{ lang: Locale, slug: string }> }) {
|
||||
const { lang, slug } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
const projects = await getProjects();
|
||||
|
||||
const filteredProjects = projects.filter((p: any) => slugify(p.tag) === slug);
|
||||
|
||||
if (filteredProjects.length === 0) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const tagName = filteredProjects[0].tag;
|
||||
const titleStr = lang === "tr" ? `${tagName} Ajansı` : `${tagName} Agency`;
|
||||
|
||||
// Clone dict to overwrite the work title specifically for this page
|
||||
const customDict = {
|
||||
...dict,
|
||||
work: {
|
||||
...dict.work,
|
||||
title: titleStr
|
||||
}
|
||||
};
|
||||
|
||||
return <WorkClient lang={lang} dict={customDict} initialProjects={filteredProjects} />;
|
||||
}
|
||||
@@ -6,8 +6,8 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
return {
|
||||
title: `${dict.faq.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
|
||||
description: dict.faq.desc,
|
||||
title: dict.meta.faq.title,
|
||||
description: dict.meta.faq.desc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+41
-34
@@ -21,41 +21,48 @@ const ibmPlexMono = IBM_Plex_Mono({
|
||||
variable: "--font-ibm-plex-mono",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://ayris.tech'),
|
||||
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"],
|
||||
openGraph: {
|
||||
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.",
|
||||
url: 'https://ayris.tech',
|
||||
siteName: 'Ayris Tech',
|
||||
images: [
|
||||
{
|
||||
url: '/og-image.jpg',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Ayris Tech - AI & Blockchain Solutions'
|
||||
}
|
||||
],
|
||||
locale: 'en_US',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: "Ayris Tech | Forging the Future with AI & Blockchain",
|
||||
description: "Ayris Tech delivers enterprise-grade digital transformation.",
|
||||
images: ['/og-image.jpg'],
|
||||
},
|
||||
alternates: {
|
||||
canonical: '/',
|
||||
languages: {
|
||||
'en': '/en',
|
||||
'tr': '/tr',
|
||||
import { getDictionary } from "@/get-dictionary";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }): Promise<Metadata> {
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
|
||||
return {
|
||||
metadataBase: new URL('https://ayris.tech'),
|
||||
title: dict.meta.home.title,
|
||||
description: dict.meta.home.desc,
|
||||
keywords: ["AI Solutions", "Blockchain Development", "Mobile App", "Web Development", "Digital Transformation"],
|
||||
openGraph: {
|
||||
title: dict.meta.home.title,
|
||||
description: dict.meta.home.desc,
|
||||
url: 'https://ayris.tech',
|
||||
siteName: 'Ayris Tech',
|
||||
images: [
|
||||
{
|
||||
url: '/og-image.jpg',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Ayris Tech - AI & Blockchain Solutions'
|
||||
}
|
||||
],
|
||||
locale: lang === 'tr' ? 'tr_TR' : 'en_US',
|
||||
type: 'website',
|
||||
},
|
||||
},
|
||||
};
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: dict.meta.home.title,
|
||||
description: dict.meta.home.desc,
|
||||
images: ['/og-image.jpg'],
|
||||
},
|
||||
alternates: {
|
||||
canonical: '/',
|
||||
languages: {
|
||||
'en': '/en',
|
||||
'tr': '/tr',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
return {
|
||||
title: `${dict.nav.brandName}${dict.nav.brandSub} | ${dict.hero.badge}`,
|
||||
description: dict.hero.desc,
|
||||
title: dict.meta.home.title,
|
||||
description: dict.meta.home.desc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
return {
|
||||
title: `${dict.process.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
|
||||
description: dict.process.desc,
|
||||
title: dict.meta.process.title,
|
||||
description: dict.meta.process.desc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
return {
|
||||
title: `${dict.services.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
|
||||
description: dict.services.desc,
|
||||
title: dict.meta.services.title,
|
||||
description: dict.meta.services.desc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
return {
|
||||
title: `${dict.work.title} | ${dict.nav.brandName}${dict.nav.brandSub}`,
|
||||
description: dict.work.desc,
|
||||
title: dict.meta.work.title,
|
||||
description: dict.meta.work.desc,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export async function getProjects() {
|
||||
try {
|
||||
return await prisma.project.findMany({
|
||||
orderBy: { num: "asc" },
|
||||
take: 50,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching projects:", error);
|
||||
|
||||
+32
-2
@@ -1,9 +1,11 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
import { getProjects } from '@/app/actions';
|
||||
import { slugify } from '@/lib/utils';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const baseUrl = 'https://ayris.tech';
|
||||
|
||||
return [
|
||||
const staticUrls: MetadataRoute.Sitemap = [
|
||||
{
|
||||
url: `${baseUrl}`,
|
||||
lastModified: new Date(),
|
||||
@@ -47,4 +49,32 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
priority: 0.8,
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
const projects = await getProjects();
|
||||
const uniqueTags = Array.from(new Set(projects.map((p: any) => p.tag)));
|
||||
|
||||
const expertiseUrls: MetadataRoute.Sitemap = uniqueTags.flatMap((tag) => {
|
||||
if (typeof tag !== 'string') return [];
|
||||
const slug = slugify(tag);
|
||||
return [
|
||||
{
|
||||
url: `${baseUrl}/en/expertise/${slug}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.7,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/tr/expertise/${slug}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.7,
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
return [...staticUrls, ...expertiseUrls];
|
||||
} catch(e) {
|
||||
return staticUrls;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-579
@@ -10,10 +10,10 @@ import {
|
||||
} 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";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const HomeClientBelowFold = dynamic(() => import('./HomeClientBelowFold'));
|
||||
|
||||
// ── EASING ──
|
||||
const expo: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
||||
@@ -181,42 +181,7 @@ export default function HomeClient({ lang, dict }: { lang: Locale; dict: any })
|
||||
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]">
|
||||
@@ -318,546 +283,7 @@ export default function HomeClient({ lang, dict }: { lang: Locale; dict: any })
|
||||
</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} />
|
||||
<HomeClientBelowFold lang={lang} dict={dict} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,731 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
motion,
|
||||
AnimatePresence,
|
||||
useSpring,
|
||||
useMotionValue
|
||||
} from "framer-motion";
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import type { Locale } from "@/i18n-config";
|
||||
import Link from "next/link";
|
||||
import { blogPosts } from "@/data/blog";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
// ── 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>;
|
||||
}
|
||||
|
||||
// ── 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>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomeClientBelowFold({ lang, dict }: { lang: Locale; dict: any }) {
|
||||
const [activeFaq, setActiveFaq] = useState<number | null>(null);
|
||||
const [formSent, setFormSent] = useState(false);
|
||||
const [hoverCase, setHoverCase] = useState<number | null>(null);
|
||||
|
||||
const serviceIcons = [<IconAI key="ai"/>, <IconChain key="chain"/>, <IconMobile key="mobile"/>, <IconWeb key="web"/>];
|
||||
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 (
|
||||
<>
|
||||
{/* ──────────────── 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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -287,5 +287,36 @@
|
||||
"desc": "High-throughput data stream configuration and low-latency pipeline designs."
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"home": {
|
||||
"title": "Top Software Agency & Digital Solutions | Ayris Tech",
|
||||
"desc": "Looking for a top-tier software agency? We engineer scalable web, mobile & enterprise systems. Get a free project blueprint today! ★"
|
||||
},
|
||||
"services": {
|
||||
"title": "Enterprise Tech Services: AI, Web3 & Mobile | Ayris Tech",
|
||||
"desc": "Accelerate growth with custom engineering. We build high-performance AI models, secure dApps, and ultra-fast web platforms. Explore capabilities! 🚀"
|
||||
},
|
||||
"work": {
|
||||
"title": "Proven Tech Case Studies & Blueprints | Ayris Tech",
|
||||
"desc": "Analyze our high-performance digital products. Discover how we solved complex challenges for fintech and healthcare sectors. View portfolio! ⚡"
|
||||
},
|
||||
"process": {
|
||||
"title": "Engineering Process & Delivery Lifecycle | Ayris Tech",
|
||||
"desc": "From blueprinting to production autopilot. Learn how our 4-step agile engineering process delivers scalable products in record time. ✓"
|
||||
},
|
||||
"blog": {
|
||||
"title": "Tech Registry: AI & Engineering Insights | Ayris Tech",
|
||||
"desc": "Deep dive into our technical registry. Explore expert articles on scalable architectures, AI implementations, and Web3 innovations. Read more! 📚"
|
||||
},
|
||||
"faq": {
|
||||
"title": "Frequently Asked Engineering Questions | Ayris Tech",
|
||||
"desc": "Got questions about our tech stacks, security frameworks, or engagement models? Find answers in our technical telemetry registry. ★"
|
||||
},
|
||||
"contact": {
|
||||
"title": "Get a Project Quote & Blueprint | Ayris Tech",
|
||||
"desc": "Ready to build the impossible? Transmit your project specs to our engineering council and get a custom architectural blueprint within 12 hours. 🚀"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -315,5 +315,35 @@
|
||||
"desc": "Saniyede on binlerce olayı milisaniyeler seviyesinde işleyen veri boru hatları tasarım ortağımız."
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"home": {
|
||||
"title": "Lider Yazılım Ajansı & Dijital Dönüşüm | Ayris Tech",
|
||||
"desc": "Kurumsal dijital dönüşüme hazır mısınız? Ölçeklenebilir web, mobil ve kurumsal sistemler üretiyoruz. Projeniz için hemen teklif alın! ★"
|
||||
},
|
||||
"services": {
|
||||
"title": "Teknoloji Hizmetleri: Yapay Zeka & Web3 | Ayris Tech",
|
||||
"desc": "Özel mühendislik çözümleriyle büyümeyi hızlandırın. Yüksek performanslı YZ modelleri, dApp'ler ve web platformları kuruyoruz. İnceleyin! 🚀"
|
||||
},
|
||||
"work": {
|
||||
"title": "Kanıtlanmış Projeler ve Vaka Analizleri | Ayris Tech",
|
||||
"desc": "Yüksek performanslı dijital ürünlerimizi inceleyin. Finans, lojistik ve sağlık sektörlerindeki başarı hikayelerimizi keşfedin! ⚡"
|
||||
},
|
||||
"process": {
|
||||
"title": "Mühendislik Süreçlerimiz & Teslimat | Ayris Tech",
|
||||
"desc": "Fikir aşamasından canlıya alınma anına kadar her adımda şeffaf ilerliyoruz. 4 adımlı metodik çalışma sürecimizi şimdi keşfedin. ✓"
|
||||
},
|
||||
"blog": {
|
||||
"title": "Teknoloji Blogu: YZ & Mühendislik | Ayris Tech",
|
||||
"desc": "Teknik bilgi depomuza dalın! Ölçeklenebilir mimariler, yapay zeka entegrasyonları ve Web3 yenilikleri hakkında uzman makalelerini okuyun. 📚"
|
||||
},
|
||||
"faq": {
|
||||
"title": "Sıkça Sorulan Sorular (S.S.S.) | Ayris Tech",
|
||||
"desc": "Sürecimiz, güvenlik standartlarımız ve iş modellerimiz hakkında sorularınız mı var? Yanıtları teknik telemetri kayıtlarımızda bulun. ★"
|
||||
},
|
||||
"contact": {
|
||||
"title": "Proje Teklifi Alın ve Bize Ulaşın | Ayris Tech",
|
||||
"desc": "İmkansızı inşa etmeye hazır mısınız? Proje detaylarınızı mühendislik ekibimize iletin ve 12 saat içinde teknik yol haritanızı alın! 🚀"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function slugify(text: string) {
|
||||
if (!text) return "";
|
||||
const trMap: { [key: string]: string } = {
|
||||
'ç': 'c', 'ğ': 'g', 'ı': 'i', 'ö': 'o', 'ş': 's', 'ü': 'u',
|
||||
'Ç': 'C', 'Ğ': 'G', 'İ': 'I', 'Ö': 'O', 'Ş': 'S', 'Ü': 'U'
|
||||
};
|
||||
const normalized = text.replace(/[çğıöşüÇĞİÖŞÜ]/g, (match) => trMap[match]);
|
||||
return normalized
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9 -]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-');
|
||||
}
|
||||
Reference in New Issue
Block a user