first commit

This commit is contained in:
AyrisAI
2026-07-12 20:18:55 +03:00
commit cbc59222c2
65 changed files with 16871 additions and 0 deletions
+398
View File
@@ -0,0 +1,398 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>Marmaris Local — Marka Yönü</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Unbounded:wght@400;600;800&family=Golos+Text:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap"
rel="stylesheet">
<style>
:root {
--stone: #EDEEE3;
--stone-deep: #E2E4D5;
--pine: #123238;
--turquoise: #2E9C9A;
--shutter: #4F7C93;
--gold: #E8A23D;
--bougainvillea: #E85D6E;
--ink: #21231F;
--paper: #FBFAF6;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--stone);
color: var(--ink);
font-family: 'Golos Text', sans-serif;
padding: 64px 24px 100px;
}
.wrap {
max-width: 980px;
margin: 0 auto;
}
/* ---- Header / wordmark ---- */
.brandhead {
display: flex;
align-items: center;
gap: 28px;
margin-bottom: 80px;
}
.seal {
width: 92px;
height: 92px;
border-radius: 50%;
border: 2.5px solid var(--pine);
display: flex;
align-items: center;
justify-content: center;
position: relative;
flex-shrink: 0;
background: var(--paper);
}
.seal::before {
content: "";
position: absolute;
inset: 7px;
border-radius: 50%;
border: 1px dashed var(--turquoise);
}
.seal-mark {
font-family: 'Unbounded', sans-serif;
font-weight: 800;
font-size: 22px;
color: var(--pine);
letter-spacing: -0.5px;
}
.wordmark h1 {
font-family: 'Unbounded', sans-serif;
font-weight: 800;
font-size: 40px;
letter-spacing: -1px;
color: var(--pine);
text-transform: lowercase;
}
.wordmark p {
font-family: 'IBM Plex Mono', monospace;
font-size: 13px;
color: var(--shutter);
margin-top: 6px;
letter-spacing: 0.3px;
}
section {
margin-bottom: 72px;
}
.label {
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--shutter);
margin-bottom: 18px;
display: block;
}
/* ---- Palette ---- */
.palette {
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.swatch {
width: 148px;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 1px 2px rgba(18, 50, 56, 0.08);
}
.swatch .fill {
height: 84px;
}
.swatch .meta {
background: var(--paper);
padding: 10px 12px;
font-family: 'IBM Plex Mono', monospace;
font-size: 11px;
}
.swatch .meta .name {
font-family: 'Golos Text', sans-serif;
font-weight: 600;
font-size: 13px;
margin-bottom: 3px;
}
/* ---- Type specimen ---- */
.type-block {
background: var(--paper);
border-radius: 20px;
padding: 44px;
}
.type-block .display {
font-family: 'Unbounded', sans-serif;
font-weight: 700;
font-size: 42px;
line-height: 1.12;
letter-spacing: -1px;
color: var(--pine);
margin-bottom: 10px;
}
.type-block .display.ru {
font-weight: 600;
font-size: 36px;
color: var(--turquoise);
margin-bottom: 28px;
}
.type-block .body-sample {
font-family: 'Golos Text', sans-serif;
font-size: 16px;
line-height: 1.6;
color: var(--ink);
max-width: 560px;
opacity: 0.85;
}
.type-note {
display: flex;
gap: 32px;
margin-top: 28px;
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
color: var(--shutter);
flex-wrap: wrap;
}
/* ---- Cards ---- */
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.card {
background: var(--paper);
border-radius: 18px;
padding: 22px;
position: relative;
border: 1px solid rgba(18, 50, 56, 0.08);
}
.card .stamp {
position: absolute;
top: 16px;
right: 16px;
width: 46px;
height: 46px;
border-radius: 50%;
border: 1.5px solid var(--turquoise);
display: flex;
align-items: center;
justify-content: center;
transform: rotate(-8deg);
}
.card .stamp span {
font-family: 'IBM Plex Mono', monospace;
font-size: 7.5px;
text-align: center;
line-height: 1.2;
color: var(--turquoise);
font-weight: 500;
}
.card .cat {
font-family: 'IBM Plex Mono', monospace;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--bougainvillea);
margin-bottom: 10px;
}
.card h3 {
font-family: 'Unbounded', sans-serif;
font-size: 19px;
font-weight: 600;
color: var(--pine);
max-width: 75%;
line-height: 1.25;
margin-bottom: 10px;
}
.card .loc {
font-size: 13px;
color: var(--ink);
opacity: 0.65;
margin-bottom: 16px;
}
.card .foot {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
border-top: 1px dashed rgba(18, 50, 56, 0.15);
padding-top: 12px;
}
.card .price {
color: var(--pine);
font-weight: 500;
}
.card .gold {
color: var(--gold);
}
.signature-note {
background: var(--pine);
color: var(--stone);
border-radius: 20px;
padding: 32px 36px;
font-size: 14.5px;
line-height: 1.65;
}
.signature-note b {
color: var(--gold);
font-family: 'Golos Text';
}
</style>
</head>
<body>
<div class="wrap">
<div class="brandhead">
<div class="seal">
<div class="seal-mark">ML</div>
</div>
<div class="wordmark">
<h1>marmaris local</h1>
<p>YEREL BİLGİ · МЕСТНЫЕ ЗНАНИЯ · LOCAL KNOWLEDGE</p>
</div>
</div>
<section>
<span class="label">01 — Renk Paleti</span>
<div class="palette">
<div class="swatch">
<div class="fill" style="background:#123238"></div>
<div class="meta">
<div class="name">Pine Night</div>#123238
</div>
</div>
<div class="swatch">
<div class="fill" style="background:#2E9C9A"></div>
<div class="meta">
<div class="name">Bay Turquoise</div>#2E9C9A
</div>
</div>
<div class="swatch">
<div class="fill" style="background:#4F7C93"></div>
<div class="meta">
<div class="name">Shutter Blue</div>#4F7C93
</div>
</div>
<div class="swatch">
<div class="fill" style="background:#E8A23D"></div>
<div class="meta">
<div class="name">Golden Hour</div>#E8A23D
</div>
</div>
<div class="swatch">
<div class="fill" style="background:#E85D6E"></div>
<div class="meta">
<div class="name">Bougainvillea</div>#E85D6E
</div>
</div>
<div class="swatch">
<div class="fill" style="background:#EDEEE3"></div>
<div class="meta">
<div class="name">Limestone</div>#EDEEE3
</div>
</div>
</div>
</section>
<section>
<span class="label">02 — Tipografi</span>
<div class="type-block">
<div class="display">En iyi yerel adresler,<br>turistin göremediği yerde.</div>
<div class="display ru">Лучшие места, которые знают местные.</div>
<div class="body-sample">Golos Text — gövde metni. Hem Latin hem Kiril alfabesinde eşit ağırlıkta
okunur, çeviri hissi vermez. Restoran açıklamaları, işletme saatleri ve kullanıcı yorumları bu yazı
tipiyle yazılır.</div>
<div class="type-note">
<span>DISPLAY — Unbounded 800/600</span>
<span>BODY — Golos Text 400/500</span>
<span>UTILITY — IBM Plex Mono (fiyat, saat, telefon)</span>
</div>
</div>
</section>
<section>
<span class="label">03 — Kart Sistemi (imza öğe: "Yerel Onaylı" mührü)</span>
<div class="cards">
<div class="card">
<div class="stamp"><span>YEREL<br>ONAYLI</span></div>
<div class="cat">Restoran</div>
<h3>İskele Balık Ocakbaşı</h3>
<div class="loc">Yat Limanı, Marmaris</div>
<div class="foot"><span class="price">₺₺₺</span><span class="gold">★ 4.8</span></div>
</div>
<div class="card">
<div class="stamp"><span>YEREL<br>ONAYLI</span></div>
<div class="cat">Apart</div>
<h3>Zeytin Bahçesi Apart</h3>
<div class="loc">Sedir Mah., Marmaris</div>
<div class="foot"><span class="price">₺₺</span><span class="gold">★ 4.6</span></div>
</div>
<div class="card">
<div class="stamp"><span>YEREL<br>ONAYLI</span></div>
<div class="cat">Hizmet</div>
<h3>Marina Dalış Merkezi</h3>
<div class="loc">İçmeler, Marmaris</div>
<div class="foot"><span class="price">₺₺₺</span><span class="gold">★ 4.9</span></div>
</div>
</div>
</section>
<section>
<span class="label">04 — Neden bu yön</span>
<div class="signature-note">
Palet klasik "krem + turuncu" AI şablonundan kaçınıp doğrudan Marmaris'in kendi görsel dünyasından
geliyor: çam yeşiline kaçan koyu <b>Pine Night</b> zemin, körfezin <b>Bay Turquoise</b>'u imza rengi,
badem çiçeği pembesi <b>Bougainvillea</b> nadir vurgu olarak. <b>Unbounded</b> hem Latin hem Kiril'de
doğal duran, karaktersiz Inter/Manrope klişesine düşmeyen bir başlık fontu — Rus kullanıcıya "sonradan
çevrilmiş" değil, kendisi için tasarlanmış hissi verir. İmza öğe: her mekanın yanındaki daire mühür —
markanın vaadini ("yereldan onaylı") harf yerine görsel bir jestle taşıyor.
</div>
</section>
</div>
</body>
</html>
+225
View File
@@ -0,0 +1,225 @@
# PRD — Marmaris Local
**Domain:** marmarislocal.com
**Tarih:** 2026-07-12
**Durum:** MVP tanımı — geliştirme başka ortamda yapılacak
---
## 1. Proje Özeti
Marmaris Local, Marmaris'teki restoran, apart otel ve yerel işletmeleri (dalış merkezi, tekne kiralama, tur operatörü, transfer vb.) tek bir küratörlü rehberde toplayan, uluslararası turiste (öncelikle Rus ve İngiliz, ayrıca Türk) hitap eden çok dilli bir dizin/rehber sitesi.
Konumlandırma: "Turistin göremediği yerel bilgi." Genel bir Yelp/TripAdvisor klonu değil — her listelemenin bir kürasyon/onay katmanı var ("Yerel Onaylı" mührü), bu da markanın ana güven vaadi.
---
## 2. Marka Kimliği (özet — tam mockup ekte: `marmaris-local-brand.html`)
- **Palet:** Pine Night `#123238` (zemin/koyu), Bay Turquoise `#2E9C9A` (imza rengi), Shutter Blue `#4F7C93`, Golden Hour `#E8A23D` (puan/rozet), Bougainvillea `#E85D6E` (nadir vurgu, kategori etiketleri), Limestone `#EDEEE3` (açık zemin)
- **Tipografi:** Unbounded (başlık, 800/600 — Latin+Kiril), Golos Text (gövde — Latin+Kiril), IBM Plex Mono (fiyat/saat/telefon gibi pratik veri)
- **İmza öğe:** Dairesel "Yerel Onaylı" mührü — her kürasyona giren listelemenin yanında görünür, logo motifiyle aynı dil
---
## 3. Sayfa Envanteri / Site Haritası
```
/ → Anasayfa (öne çıkan listelemeler, kategori girişleri, arama)
/restoranlar → Restoran listesi (filtre: mahalle, mutfak, fiyat)
/apartlar → Apart/konaklama listesi
/isletmeler → Genel işletme listesi (dalış, tekne, tur, transfer, kiralama)
/[kategori]/[slug] → Tekil listeleme detay sayfası
/mahalle/[slug] → Mahalle bazlı liste (Yat Limanı, İçmeler, Armutalan vb.)
/isletme-ekle → İşletme sahibi başvuru formu
/hakkinda → Marka hikayesi, "Yerel Onaylı" nasıl çalışır
/iletisim → İletişim formu
/admin/* → Yönetim paneli (bkz. §8)
```
Tekil listeleme sayfası içeriği: galeri, açıklama (TR/EN/RU), adres + harita, çalışma saatleri, fiyat aralığı, telefon/WhatsApp, "Yerel Onaylı" rozeti (varsa), kategori etiketleri, benzer listelemeler.
---
## 4. Dil Seçenekleri
`next-intl` locale listesi: **tr, en, ru**
- Varsayılan: `tr` (yerel SEO ve Türk kullanıcı için)
- Öncelik sırası: TR → EN → RU (RU içerik girişi başta makine çevirisi + manuel düzeltme olabilir, MVP'de tüm içerik 3 dilde eksiksiz olmalı — Rus turist kitlesi ana hedef olduğu için RU içerik EN kadar özenli olmalı, "sonradan eklenmiş" hissi vermemeli)
- Fiyat/saat gibi sayısal veriler dil bağımsız (IBM Plex Mono ile gösterilir, çeviri gerektirmez)
---
## 5. İçerik Modelleri (Prisma)
| Model | Açıklama |
|---|---|
| `Listing` | Ana varlık — restoran/apart/işletme hepsi bu modelde, `category` alanıyla ayrılır |
| `Category` | Restoran, Apart, Dalış, Tekne Kiralama, Tur Operatörü, Transfer, Araç Kiralama vb. |
| `Neighborhood` | Yat Limanı, İçmeler, Armutalan, Siteler, Turunç vb. |
| `BusinessSubmission` | `/isletme-ekle` formundan gelen, admin onayı bekleyen başvurular |
| `ContactMessage` | `/iletisim` formundan gelen genel mesajlar |
| `Gallery` | Listeleme başına çoklu görsel (Cloudinary) |
### Listing alanları
```prisma
model Listing {
id String @id @default(cuid())
slug String @unique
categoryId String
neighborhoodId String
nameTr String
nameEn String
nameRu String
descriptionTr String
descriptionEn String
descriptionRu String
address String
phone String?
whatsapp String?
website String?
instagram String?
priceRange Int // 1-3 (₺ / ₺₺ / ₺₺₺)
rating Float? // 0-5, admin girişli (MVP'de kullanıcı yorumu yok)
isLocalApproved Boolean @default(false) // "Yerel Onaylı" mührü
latitude Float?
longitude Float?
openingHours Json? // gün bazlı saat aralıkları
images Gallery[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
category Category @relation(fields: [categoryId], references: [id])
neighborhood Neighborhood @relation(fields: [neighborhoodId], references: [id])
}
```
**Not:** MVP'de kullanıcı yorumu/puanlama yok — `rating` admin tarafından manuel giriliyor (küratörlü rehber mantığına uygun). Kullanıcı yorumları P2'de değerlendirilebilir (bkz. §11).
---
## 6. "İşletme Ekle" Başvuru Formu Alanları
`/isletme-ekle` — işletme sahiplerinin kendi mekanlarını öneri olarak gönderdiği form:
- İşletme adı
- Kategori (dropdown → `Category`)
- Mahalle (dropdown → `Neighborhood`)
- Adres
- Telefon / WhatsApp
- Kısa açıklama (tek dilde yeterli, admin çevirip yayınlıyor)
- İletişim eden kişinin adı + e-postası
- Görsel yükleme (opsiyonel, Cloudinary)
`BusinessSubmission` tablosuna düşer, admin panelden onaylanınca `Listing`'e dönüştürülür (manuel "Onayla ve Listeye Ekle" aksiyonu — otomatik değil, kürasyon burada gerçekleşiyor).
---
## 7. İletişim Formu Alanları (`/iletisim`)
- Ad Soyad
- E-posta
- Konu (dropdown: Genel, İşbirliği, Hata Bildirimi, Diğer)
- Mesaj
`ContactMessage` tablosu, admin panelden okunur/işaretlenir.
---
## 8. Admin / Yönetim İhtiyaçları
```
/admin → Dashboard (toplam listeleme, kategori bazlı sayı, bekleyen başvuru sayısı)
/admin/listings → Liste + CRUD (kategori, mahalle filtreli)
/admin/listings/[id] → Düzenleme formu (3 dilde içerik alanları, görsel yönetimi, "Yerel Onaylı" toggle)
/admin/submissions → Bekleyen işletme başvuruları → Onayla (Listing'e dönüştür) / Reddet
/admin/messages → İletişim mesajları
/admin/categories → Kategori yönetimi
/admin/neighborhoods → Mahalle yönetimi
/admin/users → Kullanıcı yönetimi (sabit)
```
Auth: NextAuth (credentials, default) — tek admin rolü yeterli, MVP'de çoklu yetki seviyesi gerekmiyor.
---
## 9. Arama & Filtreleme
- Anasayfa arama kutusu: isim + kategori + mahalle üzerinden basit metin araması
- Kategori sayfalarında filtre: mahalle, fiyat aralığı, sadece "Yerel Onaylı" olanlar
- Harita görünümü: MVP'de opsiyonel (P1) — Leaflet + OpenStreetMap (Google Maps API maliyeti yerine)
---
## 10. Sosyal Medya / 3rd Party — Env Değişkenleri
```env
CLOUDINARY_CLOUD_NAME=""
CLOUDINARY_API_KEY=""
CLOUDINARY_API_SECRET=""
NEXT_PUBLIC_INSTAGRAM_URL=""
NEXT_PUBLIC_WHATSAPP_NUMBER="" # site geneli iletişim WhatsApp
RESEND_API_KEY="" # iletişim formu bildirimleri için
```
Harita için P1'de `NEXT_PUBLIC_MAPBOX_TOKEN` veya Leaflet (ücretsiz, tercih edilir) değerlendirilecek.
---
## 11. SEO & Uluslararası Notlar
- `hreflang` etiketleri tr/en/ru için eksiksiz olmalı — Google'ın doğru dil versiyonunu doğru kullanıcıya göstermesi kritik
- RU içerik gerçek çeviri kalitesinde olmalı, otomatik çeviri kokan metin marka güvenini zedeler (bkz. marka notunda "sonradan çevrilmiş hissi vermemeli")
- Her `Listing` için yapılandırılmış veri (schema.org `LocalBusiness`/`Restaurant`) eklenmeli — Google'da zengin sonuç (rating, adres, fiyat) için
- Sayfa başlıkları ve meta açıklamalar 3 dilde ayrı üretilmeli, tek dilden otomatik türetilmemeli
---
## 12. Teknik Notlar
- Çok-şehirli genişleme ihtimaline karşı: `Listing` modelinde şimdiden bir `city` alanı (MVP'de sabit `"marmaris"`) bulunmalı — ileride Fethiye/Bodrum/Ören/Datça eklenirse migration basit olsun
- Görseller Cloudinary, klasörleme: `marmarislocal/{category}/{listingSlug}/`
- Mock data: MVP gösterimi için en az 3 restoran, 2 apart, 3 işletme — gerçekçi TR/EN/RU içerikle (Unsplash görselli, mevcut demo-site kuralına uygun)
- Standart: soft delete (`deletedAt`), `USE_MOCK` env flag, `requireAdmin()` tüm admin route'larında — mevcut altyapı kurallarıyla birebir uyumlu
---
## 13. MVP Kapsamı
**P0 (MVP'siz olmaz):**
- Listing/Category/Neighborhood modelleri + 3 dilli içerik
- Kategori ve mahalle bazlı listeleme sayfaları + tekil detay sayfası
- "Yerel Onaylı" rozet sistemi
- İşletme ekle formu + admin onay akışı
- İletişim formu
- Admin panel (CRUD + başvuru onayı)
- TR/EN/RU dil desteği
**P1 (hızlı takip):**
- Harita görünümü (Leaflet)
- Gelişmiş filtreleme (fiyat aralığı, açık/kapalı durumu)
- Instagram feed entegrasyonu (anasayfa)
**P2 (gelecek, mimariyi şimdiden zorlamasın):**
- Kullanıcı yorumu/puanlama sistemi
- Çok-şehir desteği (Fethiye Local, Bodrum Local vb. — aynı codebase, `city` filtresiyle)
- İşletme sahibi kendi paneline giriş yapıp kendi listesini güncelleyebilsin (şu an sadece admin düzenliyor)
---
## 14. Açık Sorular
- [ ] Harita için Leaflet/OSM mi yoksa Google Maps mi tercih edilecek? (maliyet vs. tanıdıklık — Rus/İngiliz turist Google Maps'e daha alışkın olabilir)
- [ ] RU içerik çevirisi kim yapacak — profesyonel çeviri mi, DeepSeek/Gemini ile üretim + manuel kontrol mü?
- [ ] "Yerel Onaylı" kriterleri net bir liste halinde tanımlanacak mı (editoryal tutarlılık için), yoksa şimdilik sübjektif admin kararı mı?
- [ ] İlk yayın için hedef listeleme sayısı ve hangi kategoriden başlanacağı (öneri: restoranla başlamak, en yüksek arama hacmi orada)