This commit is contained in:
2026-04-13 00:49:59 +03:00
parent a282bdbab0
commit e71f19605a
30 changed files with 1055 additions and 66 deletions

55
Dockerfile Normal file
View File

@@ -0,0 +1,55 @@
# 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
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
USER nextjs
EXPOSE 3000
ENV PORT=3000
# set hostname to localhost
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

287
app/about/page.tsx Normal file
View File

@@ -0,0 +1,287 @@
'use client'
import { useRef, useState, useEffect } from 'react'
import { motion, useScroll, useTransform } from 'framer-motion'
import Image from 'next/image'
import Link from 'next/link'
const services = [
{
title: 'Mimari Hizmetler',
description: 'A.N.Tarchitecture, proaktif entegre tasarım yaklaşımıyla kapsamlı bir profesyonel mimari hizmet yelpazesi sunar:',
list: [
'Proje Geliştirme: Saha analizi, potansiyel çalışma, sosyal kabul edilebilirlik stratejileri ve planlama',
'Plan ve Şartnamelerin Tasarımı ve Üretimi.',
'Proje Modelleme Yönetimi: Entegre BIM (Yapı Bilgi Modellemesi) tasarımı',
'Düzenleyici Stratejiler: Yönetmelikler, saha çalışması, enerji ve güneş ışığı analizi',
'Kentsel Tasarım: Master planlama, peyzaj tasarımı ve inşaat mühendisliği işleri',
'3D Görselleştirme: 3D perspektifler, modelleme ve animasyonlar',
'İnşaat Hizmeti ve Yönetimi: İnşaat yönetimi ve saha denetimi'
],
button: 'PROJELERİ GÖRÜNTÜLE',
image: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?q=80&w=2070&auto=format&fit=crop'
},
{
title: 'İç Mimari Hizmetleri',
description: 'A.N.Tarchitecture, sürdürülebilirlik, zarafet ve işlevselliği şirketinizin değerleriyle uyumlu akıllı düzenlerle birleştirerek, imajını ve işe alım kapasitesini artıran iç mimari konusunda derin bir uzmanlık sunar:',
list: [
'Aktif Müşteri Dinleme: Alan programlama ve planlama, bütçe analizi',
'Konsept Geliştirme: Plan ve şartnamelerin üretimi, malzeme seçimi, entegre mobilya ve özel objelerin kavramsallaştırılması',
'Entegre Tabela: Plan ve şartnamelerin modellenmesi ve üretimi',
'3D Görselleştirme: 3D perspektifler, modelleme ve animasyonlar'
],
button: 'TÜM PROJELER',
image: 'https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?q=80&w=2070&auto=format&fit=crop'
},
{
title: 'Planlama ve Uzmanlık Hizmetleri',
description: 'A.N.Tarchitecture, bina planlama, programlama ve denetleme konularında, müşteri ihtiyaçlarını ve gereksinimlerini net bir şekilde tanımlamak için titiz ve sistematik yöntemler kullanarak çeşitli uzmanlıklar sunar.',
list: [
'Fonksiyonel ve Teknik Programların (FTP) Geliştirilmesi: Müdahale stratejileri, organizasyonun genel özellikleri, işlevsellik ve alanlar',
'Fizibilite Çalışması ve Ön Konseptler: Teknik ve düzenleyici koşulların ve kısıtlamaların analizi',
'Teknik Uzmanlık: Su yalıtım sorunlarının ve erken yaşlanmanın tespiti, yaşam döngüsü analizi'
],
button: null,
image: 'https://images.unsplash.com/photo-1518780664697-55e3ad937233?q=80&w=2070&auto=format&fit=crop'
}
]
const projectPhases = [
{ num: '00', title: 'ÖN KAVRAMSAL ÇALIŞMALAR', desc: 'Bir binanın kapsamını belirlemek için ihtiyaçlarını ve işlevlerini anlamaya ve belgelemeye yardımcı olan hazırlık aşaması.' },
{ num: '01', title: 'TASARIM AŞAMASI', desc: 'Bu aşama saha incelemesi, program incelemesi, ilk eskiz tasarımı ve ön bütçe analizini içerir.' },
{ num: '02', title: 'TASARIM GELİŞTİRME', desc: 'Eskizler onaylandıktan sonra, gerekli ön planları, kesitleri ve görünüşleri oluşturarak projeyi daha detaylı geliştiriyoruz.' },
{ num: '03', title: 'İNŞAAT BELGELERİ', desc: 'Bu inşaat için gerekli olan teknik planların gerçek geliştirilme aşamasıdır.' },
{ num: '04', title: 'İHALE', desc: 'Genel yüklenici tekliflerinin analizinde ve inşaat sözleşmesinin imzalanmasında müşteriye yardımcı oluyor ve danışmanlık yapıyoruz.' },
{ num: '05', title: 'SAHA DENETİMİ', desc: 'Projenin planlara sadık kalarak yürütülmesini sağlıyoruz. İnşaat sahasındaki öngörülemeyen sorunlara tasarımı uyarlamak için çözümler sunuyoruz.' }
]
export default function AboutPage() {
const targetRef = useRef<HTMLDivElement>(null)
const { scrollY } = useScroll()
const [isAtBottom, setIsAtBottom] = useState(false)
useEffect(() => {
const handleScroll = () => {
const windowHeight = window.innerHeight
const documentHeight = document.documentElement.scrollHeight
const scrollPosition = window.scrollY + windowHeight
setIsAtBottom(scrollPosition > documentHeight - 150)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
const cargoFontSize = useTransform(scrollY, [0, 300], ["10vw", "4vw"])
const archFontSize = useTransform(scrollY, [0, 300], ["8vw", "3vw"])
const bottomPadding = useTransform(scrollY, [0, 300], ["2.5rem", "1rem"])
const { scrollYProgress } = useScroll({
target: targetRef,
offset: ["start start", "end end"]
})
return (
<main className="relative bg-[#0a0a0a] text-white selection:bg-white selection:text-black" style={{ overflowX: 'clip' }}>
{/* SECTION 1: Intro */}
<section className="relative min-h-screen flex items-center justify-center pt-40 pb-32 px-6 md:px-16">
<div className="max-w-[1400px] w-full grid grid-cols-1 lg:grid-cols-[1fr_400px] gap-12 lg:gap-24 items-start">
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
>
<p className="text-[24px] md:text-[36px] lg:text-[42px] leading-[1.2] font-normal tracking-tight text-white/95">
Mimariyi, teknik disiplin ile yaratıcı vizyonun kusursuz dengesi olarak tanımlıyorum. Tasarım sürecimde hem hem de dış mekân projelerinde bütüncül bir yaklaşımı benimsiyor; projelerimi sadece birer yapı olarak değil, çevresiyle nefes alan yaşam alanları olarak tasarlıyorum.
Projelerimin teknik omurgasını AutoCAD üzerinde detaylı plan ve kesit çalışmalarıyla oluştururken, 3D modelleme ve görselleştirme süreçlerinde sunduğum gerçekçi sunumlarla tasarıma derinlik kazandırıyorum. Konsept tasarımından uygulama detaylarına kadar her aşamada, mekânın işlevselliğini estetik bir değerle harmanlamayı amaçlıyorum. </p>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.2, delay: 0.3, ease: [0.22, 1, 0.36, 1] }}
className="flex flex-col space-y-12 lg:space-y-16 mt-4"
>
<p className="text-[13px] md:text-[14px] leading-relaxed text-zinc-400 font-medium">
2006 yılında Toronto'da CGBWstudio adıyla Charles-Bernard Gagnon tarafından kurulan ve 2008'de Quebec City'ye taşınan firma, felsefesi ve sürecin her aşamasında yüksek kaliteli profesyonel hizmet sunma taahhüdü sayesinde bölge pazarında öne çıkmıştır. Bu yaklaşım, firmanın yenilikçi projelerden oluşan bir portföy oluşturmasını sağlamıştır. 2013 yılında ekip, daha anlamlı bir isim olan A.N.Tarchitecture adını seçmiştir.
</p>
<div className="flex flex-col space-y-6">
<div className="relative w-full aspect-[3/4] rounded-[1px] overflow-hidden grayscale border border-white/10 group max-w-[300px] lg:max-w-none">
<Image
src="https://images.unsplash.com/photo-1595392756136-ba72c36bcfe0?q=80&w=1974&auto=format&fit=crop"
alt="Ayça Nur Turhan"
fill
sizes="(max-width: 1024px) 300px, 400px"
className="object-cover transition-transform duration-1000 group-hover:scale-105"
/>
</div>
<div className="text-[10px] md:text-[11px] font-bold uppercase tracking-[0.2em] leading-[1.5] text-white/60">
AYÇA NUR TURHAN KURUCU <br />MİMAR, OAQ
</div>
</div>
</motion.div>
</div>
</section>
{/* SECTION 2: Services Sequential Scroll Animation */}
<section ref={targetRef} className="relative md:h-[180vh] bg-[#0a0a0a]">
<div className="hidden md:block sticky top-0 h-screen w-full p-4">
<div className="grid grid-cols-3 gap-4 w-full h-full">
{services.map((service, index) => (
<ServiceCard
key={index}
index={index}
service={service}
scrollYProgress={scrollYProgress}
/>
))}
</div>
</div>
{/* Mobile Vertical Stack */}
<div className="md:hidden flex flex-col space-y-1 px-4 pb-20">
{services.map((service, index) => (
<ServiceCardMobile key={index} service={service} />
))}
</div>
</section>
{/* SECTION 3: Project Phases */}
<section className="relative min-h-screen bg-[#0a0a0a] pt-24 md:pt-40 pb-40 md:pb-60 px-6 md:px-16 border-t border-white/10">
<div className="max-w-[1400px] mx-auto grid grid-cols-1 lg:grid-cols-[1fr_2fr] gap-12 md:gap-20">
<div className="flex flex-col items-start space-y-6 md:space-y-8">
<h2 className="text-[36px] md:text-[52px] font-normal tracking-tight">Proje Aşamaları</h2>
<Link
href="/contact"
className="bg-white text-black px-6 py-2.5 text-[10px] font-extrabold tracking-[0.2em] uppercase hover:bg-zinc-200 transition-colors rounded-[1px]"
>
PROJENİZİ BAŞLATIN
</Link>
</div>
<div className="flex flex-col">
{projectPhases.map((phase, idx) => (
<div
key={idx}
className="grid grid-cols-1 sm:grid-cols-[60px_1.2fr_2fr] gap-4 md:gap-8 py-8 md:py-10 border-t border-white/20 first:border-t-0 px-4 md:px-6 -mx-4 md:-mx-6 hover:bg-white hover:text-black transition-all duration-300 group cursor-default"
>
<div className="text-[12px] font-bold text-white/40 group-hover:text-black/40">{phase.num}</div>
<div className="text-[12px] font-extrabold tracking-widest uppercase">{phase.title}</div>
<div className="text-[13px] md:text-[14px] leading-relaxed text-zinc-400 group-hover:text-black md:pr-10">{phase.desc}</div>
</div>
))}
<div className="border-t border-white/20 w-full" />
</div>
</div>
</section>
{/* BOTTOM BRANDING */}
<motion.div
animate={{ opacity: isAtBottom ? 0 : 1 }}
transition={{ duration: 0.3 }}
className="fixed bottom-0 left-0 w-full pointer-events-none z-50 overflow-hidden pt-10"
>
<motion.div
style={{ padding: bottomPadding }}
className="flex justify-between items-end"
>
<motion.h1 style={{ fontSize: cargoFontSize }} className="font-bebas leading-[0.8] text-black mix-blend-difference tracking-tighter">A.N.T</motion.h1>
<motion.h1 style={{ fontSize: archFontSize }} className="font-bebas leading-[0.8] text-black mix-blend-difference tracking-tighter">ARCHITECTURE</motion.h1>
</motion.div>
</motion.div>
</main>
)
}
function ServiceCard({ service, index, scrollYProgress }: { service: any, index: number, scrollYProgress: any }) {
// Cards start visible but at different Y positions (staggered cascade)
// Card 0: y=0 (top), Card 1: y=200px lower, Card 2: y=400px lower
// As user scrolls, each card slides UP to y=0 to align with Card 0
const initialOffsets = [0, 200, 400]
// Sequential timing: Card 1 arrives first, Card 2 arrives after
const ranges: [number, number][] = [
[0, 0.01], // Card 0: always in place
[0, 0.50], // Card 1: slides up during first half
[0.15, 0.85], // Card 2: starts after Card 1 is moving, arrives by 85%
]
const y = useTransform(
scrollYProgress,
ranges[index],
[initialOffsets[index], 0]
)
return (
<motion.div
style={{ y }}
className="relative w-full h-full bg-white text-black flex flex-col rounded-2xl overflow-hidden"
>
{/* Content Area */}
<div className="flex-1 p-10 lg:p-14 overflow-hidden">
<h2 className="text-[32px] lg:text-[40px] mb-10 font-normal tracking-tight leading-[1.1]">
{service.title}
</h2>
<p className="text-[15px] mb-10 text-black/80 leading-relaxed font-medium">
{service.description}
</p>
<ul className="space-y-4 mb-12">
{service.list.map((item: string, i: number) => (
<li key={i} className="text-[14px] text-black/70 flex items-start border-b border-black/5 pb-2 last:border-0 leading-snug">
<span className="mr-3 text-black/30"></span>
<span>{item}</span>
</li>
))}
</ul>
{service.button && (
<Link href="/projects" className="inline-block border border-black text-[10px] font-bold tracking-[0.2em] px-8 py-3.5 uppercase hover:bg-black hover:text-white transition-all duration-300">
{service.button}
</Link>
)}
</div>
{/* Fixed Image at Bottom */}
<div className="h-[35%] lg:h-[40%] relative w-full overflow-hidden grayscale group hover:grayscale-0 transition-all duration-700">
<Image
src={service.image}
alt={service.title}
fill
sizes="33vw"
className="object-cover scale-110 group-hover:scale-100 transition-transform duration-1000"
/>
</div>
</motion.div>
)
}
function ServiceCardMobile({ service }: { service: any }) {
return (
<div className="w-full bg-white text-black flex flex-col overflow-hidden rounded-[1px] mb-1">
<div className="p-8">
<h2 className="text-[26px] mb-6 font-normal tracking-tight leading-tight">{service.title}</h2>
<p className="text-[14px] mb-6 text-black/80 leading-relaxed">{service.description}</p>
<ul className="space-y-3 mb-8">
{service.list.map((item: string, i: number) => (
<li key={i} className="text-[13px] text-black/70 flex items-start border-b border-black/5 pb-2 last:border-0">
<span className="mr-2 text-black/30"></span>
<span>{item}</span>
</li>
))}
</ul>
{service.button && (
<Link href="/projects" className="inline-block border border-black text-[9px] font-bold tracking-widest px-6 py-3 uppercase">
{service.button}
</Link>
)}
</div>
<div className="h-[250px] relative w-full grayscale">
<Image src={service.image} alt={service.title} fill sizes="100vw" className="object-cover" />
</div>
</div>
)
}

68
app/contact/page.tsx Normal file
View File

@@ -0,0 +1,68 @@
'use client'
import Link from 'next/link'
const socialLinks = [
{ name: 'FACEBOOK', href: 'https://facebook.com' },
{ name: 'INSTAGRAM', href: 'https://instagram.com' },
{ name: 'LINKEDIN', href: 'https://linkedin.com' },
{ name: '***', href: '**' },
]
const offices = [
{
num: '01',
name: 'Fethiye | Merkez Ofis',
address: 'Karagözler, 48300 Fethiye/Muğla',
phone: '0553 093 72 25'
}
]
export default function ContactPage() {
return (
<main className="relative min-h-screen bg-[#111111] text-white flex flex-col selection:bg-white selection:text-black">
<div className="flex-1 flex flex-col justify-center items-end px-10 pt-40">
<div className="flex flex-wrap justify-end gap-4">
{socialLinks.map((link) => (
<Link
key={link.name}
href={link.href}
className="border border-white/20 px-4 py-2 text-[10px] font-extrabold tracking-widest hover:bg-white hover:text-black transition-all duration-300 rounded-[1px]"
>
{link.name}
</Link>
))}
</div>
</div>
<div className="w-full px-10 pb-40">
<div className="border-t border-white/20">
{offices.map((office) => (
<div
key={office.num}
className="grid grid-cols-1 md:grid-cols-[60px_1fr_1fr_1fr] gap-8 py-10 border-b border-white/20 items-start hover:bg-white hover:text-black px-6 -mx-6 transition-all duration-300 group cursor-default"
>
<div className="text-[12px] font-bold text-white/40 group-hover:text-black/40">
{office.num}
</div>
<div className="text-[20px] md:text-[24px] font-normal tracking-tight">
{office.name}
</div>
<div className="text-[12px] leading-relaxed text-zinc-400 group-hover:text-black whitespace-pre-line">
{office.address}
</div>
<div className="text-[12px] font-bold text-right group-hover:text-black">
{office.phone}
</div>
</div>
))}
</div>
</div>
</main>
)
}

View File

@@ -10,6 +10,8 @@
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans); --font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
--font-bebas: var(--font-bebas-neue);
--font-swiss: var(--font-oswald);
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@@ -22,5 +24,15 @@
body { body {
background: var(--background); background: var(--background);
color: var(--foreground); color: var(--foreground);
font-family: Arial, Helvetica, sans-serif; font-family: var(--font-oswald), Arial, Helvetica, sans-serif;
}
@layer utilities {
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
} }

View File

@@ -1,6 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono, Bebas_Neue, Oswald } from "next/font/google";
import "./globals.css"; import "./globals.css";
import LayoutContent from "@/components/LayoutContent";
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
@@ -12,9 +13,20 @@ const geistMono = Geist_Mono({
subsets: ["latin"], subsets: ["latin"],
}); });
const bebasNeue = Bebas_Neue({
variable: "--font-bebas-neue",
weight: "400",
subsets: ["latin"],
});
const oswald = Oswald({
variable: "--font-oswald",
subsets: ["latin", "latin-ext"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "A.N.T ARCHITECTURE",
description: "Generated by create next app", description: "Ayça Nur Turhan - Mimarlık ve Tasarım",
}; };
export default function RootLayout({ export default function RootLayout({
@@ -24,10 +36,12 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html <html
lang="en" lang="tr"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} className={`${geistSans.variable} ${geistMono.variable} ${bebasNeue.variable} ${oswald.variable} h-full antialiased`}
> >
<body className="min-h-full flex flex-col">{children}</body> <body className="min-h-full flex flex-col bg-white">
<LayoutContent>{children}</LayoutContent>
</body>
</html> </html>
); );
} }

View File

@@ -1,65 +1,108 @@
import Image from "next/image"; 'use client'
import { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import ProjectSlider from '@/components/ProjectSlider'
import { projects } from '@/data/projects'
export default function Home() { export default function Home() {
const [isRevealed, setIsRevealed] = useState(false)
const [isAtCorners, setIsAtCorners] = useState(false)
useEffect(() => {
const reveal = setTimeout(() => setIsRevealed(true), 500)
const move = setTimeout(() => setIsAtCorners(true), 2500)
return () => {
clearTimeout(reveal)
clearTimeout(move)
}
}, [])
const transition = { duration: 1.8, ease: [0.76, 0, 0.24, 1] }
return ( return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black"> <main className="relative h-screen w-full bg-white overflow-hidden">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image <div className="absolute inset-0 z-50 pointer-events-none">
className="dark:invert" <div className="relative w-full h-full">
src="/next.svg"
alt="Next.js logo" {/* Main Container for the dynamic text */}
width={100} <motion.div
height={20} animate={{
priority top: isAtCorners ? "100%" : "50%",
/> left: isAtCorners ? "0%" : "50%",
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left"> x: isAtCorners ? "0%" : "-50%",
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50"> y: isAtCorners ? "-100%" : "-100%",
To get started, edit the page.tsx file. }}
</h1> transition={transition}
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400"> className="absolute overflow-hidden"
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
> >
<Image {/* The Text Reveal & Cross-fade */}
className="dark:invert" <motion.div
src="/vercel.svg" initial={{ y: "100%" }}
alt="Vercel logomark" animate={{ y: isRevealed ? "0%" : "100%" }}
width={16} transition={transition}
height={16} className="relative"
/> >
Deploy Now {/* AYÇA NUR TURHAN */}
</a> <motion.h1
<a animate={{ opacity: isAtCorners ? 0 : 1 }}
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]" transition={{ duration: 0.8, ease: "easeInOut" }}
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" className="text-[10vw] md:text-[8vw] font-bebas leading-[0.75] text-black tracking-tighter whitespace-nowrap"
target="_blank" >
rel="noopener noreferrer" AYÇA NUR TURHAN
</motion.h1>
{/* A.N.T (Cross-fading in) */}
<motion.h1
initial={{ opacity: 0 }}
animate={{ opacity: isAtCorners ? 1 : 0 }}
transition={{ duration: 0.8, ease: "easeInOut" }}
className="absolute inset-0 text-[10vw] md:text-[8vw] font-bebas leading-[0.75] text-black tracking-tighter whitespace-nowrap"
>
A.N.T
</motion.h1>
</motion.div>
</motion.div>
{/* ARCHITECTURE (Remains constant) */}
<motion.div
animate={{
top: isAtCorners ? "100%" : "50%",
left: isAtCorners ? "100%" : "50%",
x: isAtCorners ? "-100%" : "-50%",
y: isAtCorners ? "-100%" : "0%",
}}
transition={transition}
className="absolute overflow-hidden"
> >
Documentation <motion.h1
</a> initial={{ y: "100%" }}
animate={{ y: isRevealed ? "0%" : "100%" }}
transition={{ ...transition, delay: 0.1 }}
className="text-[10vw] md:text-[8vw] font-bebas leading-[0.75] text-black tracking-tighter whitespace-nowrap"
>
ARCHITECTURE
</motion.h1>
</motion.div>
</div> </div>
</main> </div>
</div>
); <AnimatePresence>
{isAtCorners && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.6, duration: 1.2, ease: "easeOut" }}
className="h-full flex flex-col justify-center z-10"
>
<ProjectSlider projects={projects} />
</motion.div>
)}
</AnimatePresence>
</main>
)
} }

87
app/projects/page.tsx Normal file
View File

@@ -0,0 +1,87 @@
'use client'
import { motion, useScroll, useTransform } from 'framer-motion'
import Image from 'next/image'
import { projects } from '@/data/projects'
import { useState, useEffect } from 'react'
export default function ProjectsPage() {
const { scrollY } = useScroll()
const [isAtBottom, setIsAtBottom] = useState(false)
useEffect(() => {
const handleScroll = () => {
const windowHeight = window.innerHeight
const documentHeight = document.documentElement.scrollHeight
const scrollPosition = window.scrollY + windowHeight
setIsAtBottom(scrollPosition > documentHeight - 100)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
const cargoFontSize = useTransform(scrollY, [0, 300], ["10vw", "4vw"])
const archFontSize = useTransform(scrollY, [0, 300], ["8vw", "3vw"])
const bottomPadding = useTransform(scrollY, [0, 300], ["2.5rem", "1rem"])
return (
<main className="relative min-h-screen bg-white pt-32 pb-60 px-6 md:px-10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8">
{projects.map((project, idx) => (
<motion.div
key={project.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: idx * 0.05 }}
className="group cursor-pointer"
>
<div className="relative aspect-[4/3] overflow-hidden rounded-[1px] bg-zinc-100">
<Image
src={project.image}
alt={project.title}
fill
className="object-cover grayscale group-hover:grayscale-0 transition-all duration-700 ease-in-out scale-105 group-hover:scale-100"
/>
</div>
<div className="mt-4 flex flex-col space-y-1 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<span className="text-[10px] font-bold text-black/60 uppercase tracking-wider">
{project.year} {project.location}
</span>
<div className="flex justify-between items-center">
<span className="text-[10px] font-bold text-black uppercase tracking-widest">
{project.title}
</span>
<span className="text-sm"></span>
</div>
</div>
</motion.div>
))}
</div>
<motion.div
animate={{ opacity: isAtBottom ? 0 : 1 }}
transition={{ duration: 0.3 }}
className="fixed bottom-0 left-0 w-full pointer-events-none z-50 overflow-hidden pt-10"
>
<motion.div
style={{ padding: bottomPadding }}
className="flex justify-between items-end"
>
<motion.h1
style={{ fontSize: cargoFontSize }}
className="font-bebas leading-[0.8] text-black tracking-tighter"
>
A.N.T
</motion.h1>
<motion.h1
style={{ fontSize: archFontSize }}
className="font-bebas leading-[0.8] text-black tracking-tighter"
>
ARCHITECTURE
</motion.h1>
</motion.div>
</motion.div>
</main>
)
}

35
components/Footer.tsx Normal file
View File

@@ -0,0 +1,35 @@
import Link from 'next/link'
export default function Footer() {
return (
<footer className="w-full bg-[#111111] text-white pt-12 pb-16 px-6 md:px-10 border-t border-white/10">
<div className="flex flex-col space-y-12 md:space-y-0 md:flex-row md:justify-between md:items-end">
{/* Left Side Branding */}
<div className="text-[32px] md:text-[20px] font-bebas tracking-tighter leading-none text-center md:text-left">
A.N.T
</div>
{/* Center: Copyright & Credits */}
<div className="flex flex-col items-center space-y-4 md:space-y-0 md:pb-1">
<div className="text-[9px] md:text-[10px] font-extrabold uppercase tracking-[0.15em] text-white/40 text-center leading-relaxed max-w-[300px] md:max-w-none">
©A.N.T ARCHITECTURE INC. 2026
</div>
<div className="text-[9px] md:text-[10px] font-extrabold uppercase tracking-[0.15em] text-white/40 text-center flex flex-wrap justify-center gap-x-2">
<span className="hidden md:inline">CREATED BY
</span>
<Link href="https://ayris.tech" className="hover:text-white transition-colors underline md:no-underline">AYRISTECH</Link>
</div>
</div>
{/* Right Side Branding */}
<div className="text-[32px] md:text-[20px] font-bebas tracking-tighter leading-none text-center md:text-right">
ARCHITECTURE
</div>
</div>
</footer>
)
}

View File

@@ -0,0 +1,20 @@
'use client'
import { usePathname } from "next/navigation";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
export default function LayoutContent({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const isHome = pathname === "/";
return (
<>
<Navbar />
<div className="flex-1">
{children}
</div>
{!isHome && <Footer />}
</>
);
}

150
components/Navbar.tsx Normal file
View File

@@ -0,0 +1,150 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { motion, AnimatePresence } from 'framer-motion'
const navLinks = [
{ name: 'ANASAYFA', href: '/' },
{ name: 'PROJELER', href: '/projects' },
{ name: 'HAKKIMIZDA', href: '/about' },
{ name: 'İLETİŞİM', href: '/contact' },
]
// Slot machine style link text rolls down from top on hover
function SlotLink({ href, children, className = '' }: { href: string, children: string, className?: string }) {
return (
<Link href={href} className={`relative inline-block overflow-hidden group ${className}`}>
<span className="block transition-transform duration-300 ease-[cubic-bezier(0.76,0,0.24,1)] group-hover:translate-y-full">
{children}
</span>
<span className="absolute left-0 top-0 block -translate-y-full transition-transform duration-300 ease-[cubic-bezier(0.76,0,0.24,1)] group-hover:translate-y-0" aria-hidden="true">
{children}
</span>
</Link>
)
}
export default function Navbar() {
const [isScrolled, setIsScrolled] = useState(false)
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 80)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
return (
<>
{/* INITIAL NAVBAR */}
<motion.nav
initial={{ y: 0, opacity: 1 }}
animate={{
y: isScrolled ? -100 : 0,
opacity: isScrolled ? 0 : 1
}}
transition={{ duration: 0.5, ease: [0.33, 1, 0.68, 1] }}
className="fixed top-0 left-0 w-full flex items-center justify-center px-6 md:px-10 py-6 z-[100] bg-white/80 backdrop-blur-md uppercase text-[12px] font-extrabold tracking-[0.05em] text-black"
>
{/* Mobile: Logo left + hamburger right */}
<div className="flex-1 md:hidden">
<Link href="/" className="text-[16px] font-bebas tracking-tighter">A.N.T</Link>
</div>
{/* Desktop Links centered */}
<div className="hidden md:flex items-center space-x-8">
{navLinks.map((link) => (
<SlotLink key={link.name} href={link.href}>
{link.name}
</SlotLink>
))}
</div>
{/* Mobile Menu Toggle */}
<div className="md:hidden">
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="flex flex-col space-y-1.5 p-2"
>
<span className={`block w-6 h-0.5 bg-black transition-transform ${isMobileMenuOpen ? 'rotate-45 translate-y-2' : ''}`} />
<span className={`block w-6 h-0.5 bg-black transition-opacity ${isMobileMenuOpen ? 'opacity-0' : ''}`} />
<span className={`block w-6 h-0.5 bg-black transition-transform ${isMobileMenuOpen ? '-rotate-45 -translate-y-2' : ''}`} />
</button>
</div>
</motion.nav>
{/* SCROLLED COMPACT NAVBAR */}
<AnimatePresence>
{isScrolled && !isMobileMenuOpen && (
<motion.div
initial={{ y: -50, opacity: 0, x: 20 }}
animate={{ y: 24, opacity: 1, x: -24 }}
exit={{ y: -50, opacity: 0, x: 20 }}
transition={{ duration: 0.5, ease: [0.33, 1, 0.68, 1] }}
className="fixed top-0 right-0 z-[100] flex items-stretch h-12 bg-black text-white rounded-[2px] overflow-hidden shadow-2xl"
>
<div className="hidden md:flex items-center px-6 space-x-6">
{navLinks.map((link) => (
<SlotLink
key={link.name}
href={link.href}
className="text-[10px] font-extrabold tracking-widest uppercase"
>
{link.name}
</SlotLink>
))}
</div>
{/* Scrolled Mobile Toggle */}
<button
onClick={() => setIsMobileMenuOpen(true)}
className="md:hidden px-4 flex items-center"
>
<span className="text-[10px] font-extrabold tracking-widest">MENÜ</span>
</button>
</motion.div>
)}
</AnimatePresence>
{/* MOBILE OVERLAY MENU */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[150] bg-white flex flex-col p-10"
>
<div className="flex justify-between items-center mb-20">
<Link href="/" onClick={() => setIsMobileMenuOpen(false)} className="text-[24px] font-bebas tracking-tighter">A.N.T</Link>
<button onClick={() => setIsMobileMenuOpen(false)} className="text-[12px] font-extrabold tracking-widest">KAPAT</button>
</div>
<div className="flex flex-col space-y-8">
{navLinks.map((link) => (
<Link
key={link.name}
href={link.href}
onClick={() => setIsMobileMenuOpen(false)}
className="text-[40px] font-bebas tracking-tight text-black hover:pl-4 transition-all duration-300"
>
{link.name}
</Link>
))}
</div>
<div className="mt-auto pt-10 border-t border-black/5">
<div className="flex items-center space-x-6 text-[12px] font-bold text-black">
<button className="underline underline-offset-4">TR</button>
<button className="opacity-40">EN</button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</>
)
}

View File

@@ -0,0 +1,76 @@
'use client'
import { useRef, useEffect } from 'react'
import { motion, useAnimationFrame } from 'framer-motion'
import Image from 'next/image'
import { Project } from '@/data/projects'
interface ProjectSliderProps {
projects: Project[]
}
export default function ProjectSlider({ projects }: ProjectSliderProps) {
const containerRef = useRef<HTMLDivElement>(null)
// Duplicate projects for seamless infinite loop
const doubledProjects = [...projects, ...projects]
// Speed of the automatic scroll
const speed = 0.8
useAnimationFrame(() => {
if (!containerRef.current) return
containerRef.current.scrollLeft += speed
const firstHalfWidth = containerRef.current.scrollWidth / 2
if (containerRef.current.scrollLeft >= firstHalfWidth) {
containerRef.current.scrollLeft = 0
}
})
const handleWheel = (e: React.WheelEvent) => {
if (!containerRef.current) return
containerRef.current.scrollLeft += e.deltaY
}
return (
<div
ref={containerRef}
onWheel={handleWheel}
className="flex-1 flex items-center overflow-x-auto no-scrollbar cursor-grab active:cursor-grabbing py-10"
>
<div className="flex space-x-6 md:space-x-12 px-6 md:px-10">
{doubledProjects.map((project, idx) => (
<div
key={`${project.id}-${idx}`}
className="group relative flex-shrink-0 w-[80vw] sm:w-[500px] md:w-[600px]"
>
{/* Image Container */}
<div className="relative aspect-[4/3] overflow-hidden rounded-[1px]">
<Image
src={project.image}
alt={project.title}
fill
sizes="(max-width: 768px) 80vw, 600px"
className="object-cover grayscale hover:grayscale-0 transition-all duration-700 ease-in-out scale-110 group-hover:scale-100"
/>
</div>
{/* Project Info */}
<div className="mt-4 flex justify-between items-start opacity-0 group-hover:opacity-100 transition-opacity duration-500">
<div className="text-[10px] md:text-[12px] font-bold text-black tracking-wider uppercase">
{project.year} {project.location}
</div>
<div className="flex items-center space-x-2 text-[10px] md:text-[12px] font-bold text-black tracking-wider uppercase">
<span>{project.title}</span>
<span className="text-sm md:text-lg"></span>
</div>
</div>
</div>
))}
</div>
</div>
)
}

91
data/projects.ts Normal file
View File

@@ -0,0 +1,91 @@
export interface Project {
id: number;
year: string;
location: string;
title: string;
image: string;
category?: string;
}
export const projects: Project[] = [
{
id: 1,
year: '2018',
location: 'SAINT-AUGUSTIN-DE-DESMAURES',
title: 'JDHM GENEL MERKEZ',
image: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?q=80&w=2070&auto=format&fit=crop',
category: 'Ticari'
},
{
id: 2,
year: '2019',
location: 'MONTREAL, QC',
title: 'ŞALE',
image: 'https://images.unsplash.com/photo-1518780664697-55e3ad937233?q=80&w=2070&auto=format&fit=crop',
category: 'Konut'
},
{
id: 3,
year: '2020',
location: 'QUEBEC CITY, QC',
title: 'MODERN MÜZE',
image: 'https://images.unsplash.com/photo-1511818966892-d7d671e672a2?q=80&w=2070&auto=format&fit=crop',
category: 'Kültürel'
},
{
id: 4,
year: '2021',
location: 'TORONTO, ON',
title: 'ŞEHİR KULESİ',
image: 'https://images.unsplash.com/photo-1449156001533-cb39414bb589?q=80&w=2070&auto=format&fit=crop',
category: 'Konut'
},
{
id: 5,
year: '2022',
location: 'VANCOUVER, BC',
title: 'ORMAN EVİ',
image: 'https://images.unsplash.com/photo-1500382017468-9049fed747ef?q=80&w=2070&auto=format&fit=crop',
category: 'Konut'
},
{
id: 6,
year: '2023',
location: 'SAINTE-FOY, QC',
title: 'KOCINA',
image: 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070&auto=format&fit=crop',
category: 'Ticari'
},
{
id: 7,
year: '2022',
location: 'MONT-TREMBLANT, QC',
title: 'YAMAÇ EVİ',
image: 'https://images.unsplash.com/photo-1497366216548-37526070297c?q=80&w=2070&auto=format&fit=crop',
category: 'Konut'
},
{
id: 8,
year: '2021',
location: 'LÉVIS, QC',
title: 'MAKUSHAM STÜDYO',
image: 'https://images.unsplash.com/photo-1431540015161-0bf868a2d407?q=80&w=2070&auto=format&fit=crop',
category: 'Kültürel'
},
{
id: 9,
year: '2023',
location: 'BROSSARD, QC',
title: 'MUST SOCIÉTÉ',
image: 'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?q=80&w=2070&auto=format&fit=crop',
category: 'Ticari'
},
{
id: 10,
year: '2022',
location: 'RIMOUSKI, QC',
title: 'GÖL KENARI KULÜBESİ',
image: 'https://images.unsplash.com/photo-1449156001533-cb39414bb589?q=80&w=2070&auto=format&fit=crop',
category: 'Konut'
}
];

0
docs/prd.md Normal file
View File

View File

@@ -1,7 +1,14 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
],
},
}; };
export default nextConfig; export default nextConfig;

43
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "aycanur", "name": "aycanur",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"framer-motion": "^12.38.0",
"next": "16.2.3", "next": "16.2.3",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4" "react-dom": "19.2.4"
@@ -3692,6 +3693,33 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/framer-motion": {
"version": "12.38.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.38.0",
"motion-utils": "^12.36.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -5019,6 +5047,21 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/motion-dom": {
"version": "12.38.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.36.0"
}
},
"node_modules/motion-utils": {
"version": "12.36.0",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@@ -9,6 +9,7 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"framer-motion": "^12.38.0",
"next": "16.2.3", "next": "16.2.3",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4" "react-dom": "19.2.4"

Binary file not shown.

Binary file not shown.

BIN
screenshots/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
screenshots/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

BIN
screenshots/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 KiB

BIN
screenshots/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 KiB

BIN
screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 KiB

BIN
screenshots/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

BIN
screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
screenshots/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
screenshots/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
screenshots/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
screenshots/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
screenshots/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB