feat(tasarim): görsel dilin tek kaynağı DESIGN.md, proje skill'i ve tasarım denetim betiği
All checks were successful
Deploy / deploy (push) Successful in 5m33s

- docs/tasarim/DESIGN.md: 9 bölümlük görsel dil belgesi (renk rolleri, tipografi,
  tek kaynak bileşenlerin sınıf dizileri, yerleşim, yap/yapma, brief şablonu)
- kolaytercih-tasarim skill'i: UI işinde tetiklenir, belgeye götürür, örnek verir;
  tasarimci ve yazilimci ajanlarına skills: ile önyüklenir
- scripts/tasarim-denetim.ts: 13 kural (Badge, CTA'da Sparkles, palet dışı ton,
  gradyan metin, parlama, hareket spam'i, çarpı, font); pnpm lint'e bağlandı
- ui-ux-pro-max genel kataloğu skills'ten .agents/skills-devre-disi/ altına taşındı
- AGENTS.md: DESIGN.md kuralı; rozet sınıfı koda göre düzeltildi (font-bricolage yoktu)
- arama-funnel-karti: palet dışı text-cyan-300 → text-orange-500

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-23 14:44:35 +03:00
parent a42a1a9920
commit dc7861737b
29 changed files with 506 additions and 13 deletions

View File

Can't render this file because it contains an unexpected character in line 4 and column 188.

View File

@@ -0,0 +1,86 @@
---
name: kolaytercih-tasarim
description: KolayTercih'in kendi görsel dili — piksel imzası (SectionEyebrow, PixelDivider, PixelCorner), Cal Sans/Clarity City/Bricolage tipografisi, mavi bilgi + turuncu aksiyon rengi, slate nötrler, sondaki ArrowRight'lı CTA. Yeni ekran, bileşen, kart, modal, buton, form ya da sayfa tasarlarken; mevcut arayüzü değiştirirken; "AI gibi duruyor", "sitenin diline uymuyor", "piksel detayı kaybolmuş" denince kullanılır. docs/tasarim/DESIGN.md'nin giriş kapısıdır.
---
# KolayTercih tasarım dili
Bu proje için genel tasarım kütüphanesi (stil katalogları, palet listeleri) **kullanılmaz**;
yön zaten belli. Kaynak tek: `docs/tasarim/DESIGN.md`. Bu dosya onu nasıl uygulayacağını söyler.
## 1. Başlamadan önce (zorunlu)
1. `docs/tasarim/DESIGN.md` dosyasını **tamamını** oku (181 satır). Özellikle §4 tek kaynaklar ve §7 yap/yapma.
2. Dört satırlık brief'i doldur (DESIGN.md §9): amaç · referans ekran · tek kaynak parçalar · durumlar.
3. Referans ekranı bul ve aç; kopyalayacağın sınıf dizilerini oradan al:
```bash
grep -rn "SectionEyebrow" src --include='*.tsx' -l # rozetli başlık bloğu örnekleri
grep -rn "bg-orange-500" src --include='*.tsx' -l # turuncu CTA örnekleri
grep -rn "PixelCorner\|PagePixelDivider" src -l --include='*.tsx' # piksel imzası taşıyan ekranlar
```
Ana sayfa bölüm iskeleti: `src/app/page.tsx` (Problem bölümü, ~satır 268). Alt sayfa başlığı: `src/app/meraklisina/page.tsx`.
## 2. Yazarken
- Kanonik parçaları içe aktar, yeniden çizme: `SectionEyebrow`, `PagePixelDivider`, `PixelCorner`
(`@/components/pixel-decor`); `Button` (`@/components/ui/button`); `Card` (`@/components/ui/card`);
`DialogContent` varsayılan çarpısı (`@/components/ui/dialog`); `ArrowRight`, `XIcon` (`lucide-react`).
- Sınıf dizilerini DESIGN.md'den **birebir** kopyala; "yaklaşık aynı" yazma.
- Her piksel süsüne o sayfaya özgü tek `seed` ver (mevcut seed'ler: `grep -rho "seed={[0-9]*}" src | sort -u`).
- Risk rengi kullanıyorsan metin etiketini `RISK_ETIKET` / `DILIM_ETIKET`'ten (`src/lib/risk.ts`) al.
- Türkçe tanımlayıcı, çevredeki kodla aynı yorum yoğunluğu (AGENTS.md).
## 3. Bitirmeden önce
```bash
pnpm tasarim:denetim # sıfır hata olmadan iş bitmiş sayılmaz
```
Uyarıları raporda listele; hataları düzelt. Sonra 375 px kontrol notu yaz: taşma, üst üste binme,
dokunma hedefi < 44 px, uzun üniversite adı. (Tarayıcı açma kuralı AGENTS.md'de; koddan çıkarım yazıyorsan "koddan çıkarım" de.)
## 4. Örnekler
**İstek:** "Bölüm sayfasına 'Bu bölümü okuyanlar ne diyor' başlığı ekle."
**Çıktı:**
```tsx
<div className="flex flex-col items-center gap-4 text-center">
<SectionEyebrow>Öğrenci görüşleri</SectionEyebrow>
<h2 className="font-heading text-3xl font-bold sm:text-4xl">Bu bölümü okuyanlar ne diyor?</h2>
<p className="max-w-xl text-lg leading-relaxed text-slate-600">…</p>
</div>
```
**İstek:** "Paket sayfasına satın alma butonu koy."
**Çıktı:**
```tsx
<Button size="lg" className="h-12 w-full cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600">
Paketi al
<ArrowRight className="size-4" aria-hidden />
</Button>
```
(Yanlış: başta `<Sparkles />`, `bg-gradient-to-r`, `rounded-xl`, "Hemen Al ✨".)
**İstek:** "Üniversite sayfasına URAP kartı ekle."
**Çıktı:**
```tsx
<div className="relative overflow-hidden rounded-2xl border border-slate-200 bg-white p-5">
<PixelCorner className="absolute right-4 top-4" />
<p className="text-xs font-semibold text-slate-500">URAP Türkiye</p>
<p className="mt-1 font-heading text-2xl font-bold text-slate-900">18 <span className="text-base font-medium text-slate-400">/ 198</span></p>
</div>
```
**İstek:** "Modal ekle, çarpı sağ üstte olsun."
**Çıktı:** `DialogContent` varsayılanını kullan; hiçbir sınıf verme. Özel yerleşim şartsa `showCloseButton={false}` + DESIGN.md §4.3'teki sınıf dizisi aynen.
## 5. Kısa yasak listesi
`Badge` import'u · elle pill span · CTA'da `Sparkles`/emoji · `indigo/purple/gray/zinc` · gradyan metin ·
parlama gölgesi · `whileHover scale` · her elemanda fade-up · `dark:` · Inter/Roboto/serif · `text-[#hex]` · renk tek başına anlam.
## 6. Rapor
İş sonunda: kullanılan kanonik parçalar (liste) · `pnpm tasarim:denetim` çıktısı (hata/uyarı sayısı) · 375 px notu · DESIGN.md ile çelişen bir şey bulduysan dosya:satır ile yaz, kendi başına kuralı değiştirme.

View File

@@ -1,6 +1,8 @@
--- ---
name: tasarimci name: tasarimci
description: Ürün tasarımcısı (UX/UI). Yeni ekran ve akışların tasarım tanımını yazar; mevcut arayüzü tutarlılık (tek kaynak bileşenler), mobil kullanım, erişilebilirlik ve arayüz metni açısından koddan denetler. Yeni bir UI işi başlamadan önce ya da bir UI dalı merge edilmeden önce kullan. description: Ürün tasarımcısı (UX/UI). Yeni ekran ve akışların tasarım tanımını yazar; mevcut arayüzü tutarlılık (tek kaynak bileşenler), mobil kullanım, erişilebilirlik ve arayüz metni açısından koddan denetler. Yeni bir UI işi başlamadan önce ya da bir UI dalı merge edilmeden önce kullan.
skills:
- kolaytercih-tasarim
tools: Read, Grep, Glob, Bash, Write, Edit, Skill, mcp__Claude_Browser__preview_start, mcp__Claude_Browser__preview_stop, mcp__Claude_Browser__navigate, mcp__Claude_Browser__computer, mcp__Claude_Browser__read_page, mcp__Claude_Browser__find, mcp__Claude_Browser__resize_window, mcp__Claude_Browser__tabs_context, mcp__Claude_Browser__tabs_close, mcp__Claude_Browser__browser_batch tools: Read, Grep, Glob, Bash, Write, Edit, Skill, mcp__Claude_Browser__preview_start, mcp__Claude_Browser__preview_stop, mcp__Claude_Browser__navigate, mcp__Claude_Browser__computer, mcp__Claude_Browser__read_page, mcp__Claude_Browser__find, mcp__Claude_Browser__resize_window, mcp__Claude_Browser__tabs_context, mcp__Claude_Browser__tabs_close, mcp__Claude_Browser__browser_batch
--- ---
@@ -14,17 +16,18 @@ Sen KolayTercih ekibinin **ürün tasarımcısısın**. İşe başlamadan önce
- Risk dili: kırmızı/sarı/yeşil **her zaman metin etiketiyle**; renk tek başına anlam taşımaz. - Risk dili: kırmızı/sarı/yeşil **her zaman metin etiketiyle**; renk tek başına anlam taşımaz.
- Bileşen tabanı shadcn (`src/components/ui`), tipografi ve renk token'ları `src/app/globals.css`. - Bileşen tabanı shadcn (`src/components/ui`), tipografi ve renk token'ları `src/app/globals.css`.
## Beceriler (işe başlarken yükle) ## Beceriler
Projede kurulu tasarım becerileri senin referans kitaplığındır; kendi zevkinle değil bunlarla ve yukarıdaki tek kaynaklarla gerekçelendir. Çakışmada öncelik: `AGENTS.md` / tek kaynak bileşenler > beceri > kendi yorumun. Görsel dilin tek kaynağı `docs/tasarim/DESIGN.md`; giriş kapısı olan `kolaytercih-tasarim` becerisi sana başlangıçta yüklü gelir. Genel tasarım katalogları (`ui-ux-pro-max` vb.) **kullanılmaz**: yön zaten belli, katalog "ortalama site" görünümüne iter. Çakışmada öncelik: `AGENTS.md` / DESIGN.md tek kaynakları > beceri > kendi yorumun.
- **Her işte:** `Skill` aracıyla `shadcn` (bileşen kompozisyonu, form/ikon/overlay kuralları) ve `ui-ux-pro-max` (tipografi, boşluk, erişilebilirlik kontrol listesi) yükle; `.agents/skills/frontend-ui-engineering/SKILL.md` dosyasını `Read` ile oku (Skill listesinde görünmez). Yalnızca ihtiyaç doğduğunda, `Skill` aracıyla:
- **Etkileşim/dokunma/çekmece/sheet işlerinde:** `apple-design` ve `emil-design-eng`. - Yeni bir shadcn bileşeni eklenecekse: `shadcn`.
- **Hareket denetiminde:** `find-animation-opportunities` (eksik hareket) ve `.agents/skills/review-animations/SKILL.md` + `STANDARDS.md` dosyalarını `Read` ile oku (bu beceri Skill aracıyla çağrılamaz); değerleri STANDARDS.md'den birebir al, yaklaşık yazma. - Etkileşim/dokunma/çekmece/sheet işinde: `apple-design`, `emil-design-eng`.
- **Bileşen mimarisi sorusu çıkarsa:** `vercel-react-best-practices` ve `nextjs-app-architecture` — yalnızca tasarım tanımının hangi bileşenin client/server olacağını etkilediği yerde. - Hareket denetiminde: `find-animation-opportunities`; `.agents/skills/review-animations/SKILL.md` + `STANDARDS.md`'yi `Read` ile oku, değerleri STANDARDS.md'den birebir al.
- Bileşen mimarisi sorusu çıkarsa: `vercel-react-best-practices`, `nextjs-app-architecture`.
- `prototype` ve `pick-ui-library` sana kapalı; ihtiyaç görürsen `[BİLAL]` olarak öner. - `prototype` ve `pick-ui-library` sana kapalı; ihtiyaç görürsen `[BİLAL]` olarak öner.
Beceri okumak `src/` altına dokunma yetkisi vermez; sınırlar aynen geçerli. Her denetimde `pnpm tasarim:denetim` çıktısını rapora koy; makinenin yakaladığını elle tekrar arama, gözünü makinenin göremediğine (hiyerarşi, taşma, metin) ayır.
## Görev ## Görev

View File

@@ -2,6 +2,8 @@
name: yazilimci name: yazilimci
description: Yazılım geliştirici. Tanımı net bir işi ya da CTO şartnamesindeki tek bir paketi uygular; yalnızca sahibi olduğu dosyalara dokunur, typegen+tsc+lint ile doğrular, raporunu yazar. Paralel çalıştırılacaksa her kopyaya ayrı worktree + dal ve ayrık dosya kümesi verilmelidir. Push/merge yapmaz. description: Yazılım geliştirici. Tanımı net bir işi ya da CTO şartnamesindeki tek bir paketi uygular; yalnızca sahibi olduğu dosyalara dokunur, typegen+tsc+lint ile doğrular, raporunu yazar. Paralel çalıştırılacaksa her kopyaya ayrı worktree + dal ve ayrık dosya kümesi verilmelidir. Push/merge yapmaz.
disallowedTools: mcp__rybbit disallowedTools: mcp__rybbit
skills:
- kolaytercih-tasarim
--- ---
Sen KolayTercih ekibinin **yazılım geliştiricilerinden birisin**. İşe başlamadan önce `docs/ekip/PROTOKOL.md` dosyasını ve repo kökündeki `AGENTS.md`'yi oku; kurallar istisnasız bağlayıcıdır. Sen KolayTercih ekibinin **yazılım geliştiricilerinden birisin**. İşe başlamadan önce `docs/ekip/PROTOKOL.md` dosyasını ve repo kökündeki `AGENTS.md`'yi oku; kurallar istisnasız bağlayıcıdır.
@@ -12,8 +14,8 @@ Orkestratör sana şunları verir: iş tanımı ya da paket adı + şartnamenin
1. Şartnamendeki bölümü ve "dosya sahipliği" tablosunu oku. **Listede olmayan dosyaya dokunma.** Başka bir paketin dosyasında değişiklik şart görünüyorsa yapma; raporuna "sapma/engel" olarak yaz. Şartname yoksa: değişikliği en küçük dosya kümesiyle yap ve dokunduğun her dosyayı raporla. 1. Şartnamendeki bölümü ve "dosya sahipliği" tablosunu oku. **Listede olmayan dosyaya dokunma.** Başka bir paketin dosyasında değişiklik şart görünüyorsa yapma; raporuna "sapma/engel" olarak yaz. Şartname yoksa: değişikliği en küçük dosya kümesiyle yap ve dokunduğun her dosyayı raporla.
2. Next'e özgü bir API'ye dokunmadan önce `node_modules/next/dist/docs/` içindeki ilgili sayfayı oku — bu sürüm eğitim verindeki Next değil. Kütüphane davranışını varsayma; kaynağını oku. 2. Next'e özgü bir API'ye dokunmadan önce `node_modules/next/dist/docs/` içindeki ilgili sayfayı oku — bu sürüm eğitim verindeki Next değil. Kütüphane davranışını varsayma; kaynağını oku.
3. Kodu çevresindeki kod gibi yaz: aynı adlandırma (Türkçe tanımlayıcılar), aynı yorum yoğunluğu, aynı desenler. AGENTS.md UI kuralları geçerli (SectionEyebrow, CTA'da metin sonunda `ArrowRight`, kanonik kapatma çarpısı). Sihirbaz/listeleme davranışını değiştiriyorsan `/meraklisina` mermaid şemasını ve çevresini aynı commit'te güncelle. 3. Kodu çevresindeki kod gibi yaz: aynı adlandırma (Türkçe tanımlayıcılar), aynı yorum yoğunluğu, aynı desenler. UI'a dokunan işte `docs/tasarim/DESIGN.md` tek kaynaktır (başlangıçta yüklü `kolaytercih-tasarim` becerisi oraya götürür); sınıf dizilerini oradan birebir al. Sihirbaz/listeleme davranışını değiştiriyorsan `/meraklisina` mermaid şemasını ve çevresini aynı commit'te güncelle.
4. Doğrulama: `pnpm exec next typegen && pnpm exec tsc --noEmit && pnpm lint`. `pnpm build`'i yalnızca iş tanımı senden istiyorsa koş (paralel build RAM'i tüketir). Dev sunucusu/tarayıcı açma. 4. Doğrulama: `pnpm exec next typegen && pnpm exec tsc --noEmit && pnpm lint` (`lint` tasarım denetimini de koşar; sıfır hata şart). `pnpm build`'i yalnızca iş tanımı senden istiyorsa koş (paralel build RAM'i tüketir). Dev sunucusu/tarayıcı açma.
5. Commit (istendiyse): küçük, tek amaçlı, repo üslubunda (`feat(alan): …`, `fix(alan): …`). `git add -A` / `git add .` **yasak** — dosyaları yol vererek ekle. Bilal kararına bağlı değişiklikleri ayrı commit yap, mesaja `[Bilal]` koy. Kısmi commit'te bağımlılık kapanışını kontrol et: bir dosyanın kullandığı prop/export başka bir commit'lenmemiş dosyadan geliyorsa aynı commit'e al. 5. Commit (istendiyse): küçük, tek amaçlı, repo üslubunda (`feat(alan): …`, `fix(alan): …`). `git add -A` / `git add .` **yasak** — dosyaları yol vererek ekle. Bilal kararına bağlı değişiklikleri ayrı commit yap, mesaja `[Bilal]` koy. Kısmi commit'te bağımlılık kapanışını kontrol et: bir dosyanın kullandığı prop/export başka bir commit'lenmemiş dosyadan geliyorsa aynı commit'e al.
6. `main`'e commit yok, push yok, merge yok, başka worktree'de komut yok. 6. `main`'e commit yok, push yok, merge yok, başka worktree'de komut yok.

View File

@@ -0,0 +1 @@
../../.agents/skills/kolaytercih-tasarim

View File

@@ -20,9 +20,13 @@ Açtığında geçerli olanlar:
Push emri geldiğinde önce lokalde `pnpm build` başarıyla geçmeli; geçmeden asla push etme. Kritik detay: çalışma ağacı kirliyken build, commit'lenmemiş dosyaları da gördüğü için CI'da kırılacak bir commit'i yakalayamayabilir. Bu yüzden build'i **push edilecek commit'in kendisi** üzerinde doğrula (ör. geçici `git worktree add <dir> HEAD` + `node_modules` symlink'i ile). Ayrıca kısmi commit atarken değişikliğin bağımlılık kapanışını kontrol et — bir dosyanın kullandığı prop/export başka bir commit'lenmemiş dosyadan geliyorsa onları da aynı commit'e dahil et. Push emri geldiğinde önce lokalde `pnpm build` başarıyla geçmeli; geçmeden asla push etme. Kritik detay: çalışma ağacı kirliyken build, commit'lenmemiş dosyaları da gördüğü için CI'da kırılacak bir commit'i yakalayamayabilir. Bu yüzden build'i **push edilecek commit'in kendisi** üzerinde doğrula (ör. geçici `git worktree add <dir> HEAD` + `node_modules` symlink'i ile). Ayrıca kısmi commit atarken değişikliğin bağımlılık kapanışını kontrol et — bir dosyanın kullandığı prop/export başka bir commit'lenmemiş dosyadan geliyorsa onları da aynı commit'e dahil et.
# Görsel dilin tek kaynağı: docs/tasarim/DESIGN.md
UI'a dokunan her iş (yeni ekran, bileşen, kart, modal, buton, form; mevcut arayüz değişikliği) önce `docs/tasarim/DESIGN.md` okunarak yapılır; renk, yazı tipi, köşe, gölge, boşluk ve tek kaynak bileşenlerin sınıf dizileri oradan **birebir** alınır, "yaklaşık aynısı" yazılmaz. Aşağıdaki rozet/çarpı/CTA kuralları o belgenin özetidir. Genel tasarım katalogları (`ui-ux-pro-max` gibi "50 stil, 97 palet" kütüphaneleri) bu projede kullanılmaz; yön zaten belli. İş bitmeden `pnpm tasarim:denetim` sıfır hata vermeli (`pnpm lint` bunu da koşar); betik `scripts/tasarim-denetim.ts`, kural eklerken DESIGN.md'deki bölüm numarasına atıf yap.
# Badge/eyebrow rozetleri: tek kaynak SectionEyebrow # Badge/eyebrow rozetleri: tek kaynak SectionEyebrow
Site genelindeki tüm badge/rozet/eyebrow elemanları ana sayfadaki section başlıklarının üstünde duran rozetle birebir aynı olmalı. Kanonik bileşen `src/components/pixel-decor.tsx` içindeki `SectionEyebrow`dur (piksel imzalı `PixelMark` ikonu + `rounded-full border border-slate-200 bg-white px-3.5 py-1.5 font-bricolage text-xs font-semibold text-primary`; metin büyük-küçük harfli yazılır — uppercase yok). Yeni bir badge/eyebrow gerektiğinde `SectionEyebrow` kullan (`className` prop'u kenar boşluğu için var); `@/components/ui/badge`'deki `Badge` ile ya da elle yazılmış pill span'lerle yeni badge tasarlama. İnteraktif chip/buton pill'leri (filtre çipleri, nav butonları) bu kuralın dışındadır. Site genelindeki tüm badge/rozet/eyebrow elemanları ana sayfadaki section başlıklarının üstünde duran rozetle birebir aynı olmalı. Kanonik bileşen `src/components/pixel-decor.tsx` içindeki `SectionEyebrow`dur (piksel imzalı `PixelMark` ikonu + `inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-3.5 py-1.5 text-xs font-semibold text-primary`; metin büyük-küçük harfli yazılır — uppercase yok). Yeni bir badge/eyebrow gerektiğinde `SectionEyebrow` kullan (`className` prop'u kenar boşluğu için var); `@/components/ui/badge`'deki `Badge` ile ya da elle yazılmış pill span'lerle yeni badge tasarlama. İnteraktif chip/buton pill'leri (filtre çipleri, nav butonları) bu kuralın dışındadır.
# UI tutarlılığı: ortak elemanlar tek kaynaktan # UI tutarlılığı: ortak elemanlar tek kaynaktan

181
docs/tasarim/DESIGN.md Normal file
View File

@@ -0,0 +1,181 @@
# KolayTercih görsel dili (DESIGN.md)
Bu dosya sitenin görsel dilinin **tek yazılı kaynağıdır**. Yeni ekran, bileşen, modal, kart
ya da buton çizmeden önce okunur; mevcut arayüz değiştirilirken buna bakılır. Değerler koddan
çıkarılmıştır (`src/app/globals.css`, `src/app/layout.tsx`, `src/components/pixel-decor.tsx`,
`src/components/ui/*`, `src/app/page.tsx`); kodla çelişirse kod değil, bu dosya düzeltilir ve
çelişki raporlanır. Kural ihlallerini `pnpm tasarim:denetim` yakalar.
Biçim: Google Stitch'in DESIGN.md düzeni (9 bölüm). Sabit değerler Tailwind sınıfı olarak
verilir; kodda da sınıf kullanılır, hex/oklch elle yazılmaz.
---
## 1. Görsel tema ve atmosfer
**Bir cümle:** Beyaz, bol nefesli, mavi bilgi + turuncu aksiyon; her yüzeyde küçük bir
**piksel imzası** (hero'daki dot-matrix Türkiye haritasının dili) dolaşır.
- Zemin her zaman beyaz; koyu zemin yalnızca CTA bandı ve çekmece başlığı gibi tek bir
vurgu yüzeyinde (`PixelField` ile).
- İmza dekoratiftir ama rastgele değildir: deterministik ızgara (hücre 10, nokta 7.4, rx 2),
imleç yaklaşınca turuncuya ısınan pikseller (`usePixelHeat`), uzaklaşınca yavaş soğuma.
- Ton: öğrenciye "sen" diyen, sakin, vaatsiz. Süs yok, gösteriş yok; hareket yalnızca
bir şey söylüyorsa var.
- **Karanlık mod yoktur.** `dark:` sınıfı yazılmaz (`.dark` token'ları shadcn kalıntısıdır).
## 2. Renk paleti ve rolleri
| Rol | Sınıf | Nerede |
|---|---|---|
| Bilgi/vurgu mavisi (`primary`) | `text-primary`, `bg-primary/10`, `border-primary/30` | Rozet metni, ikon kutusu, hero'daki italik vurgu, linkler, kart hover kenarlığı |
| **Aksiyon turuncusu** | `bg-orange-500 hover:bg-orange-600 text-white` | Funnel CTA butonları. Bir ekranda **tek** turuncu buton |
| Piksel vurgusu | `fill-orange-500` (opaklık 0.55–0.85) | Yalnızca piksel süslerinin "sıcak" noktaları |
| Başlık metni | `text-slate-900` | h1–h3, kart başlıkları |
| Gövde metni | `text-slate-600` | Paragraf, açıklama |
| İkincil metin | `text-slate-500` | Meta bilgi, yardımcı satır |
| Silik metin | `text-slate-400` | İpucu, devre dışı, placeholder |
| Kenarlık | `border-slate-200` (landing kartlarında `border-slate-200/70`) | Kart, rozet, çarpı, giriş alanı |
| İç zemin | `bg-slate-50` | Kartın içindeki ikincil kutu, tablo başlığı |
| Hover zemini | `hover:bg-slate-100` | Çarpı, ghost buton |
| Risk: güvenli | `emerald-500` / `text-emerald-600` | **Her zaman "Güvenli" metniyle** |
| Risk: az riskli | `amber-500` / `text-amber-600` | **Her zaman "Az riskli" metniyle** |
| Risk: riskli | `red-500` / `text-red-600` | **Her zaman "Riskli" metniyle** |
| Uyarı kutusu | `border-amber-200 bg-amber-50 text-amber-900` | Bilgilendirme notu |
| Hata | `text-destructive`, `text-red-600` | Form hatası |
Kurallar:
- Nötr yalnızca **slate**. `gray`, `zinc`, `neutral`, `stone` yazılmaz.
- `indigo`, `purple`, `violet`, `fuchsia`, `pink`, `cyan`, `teal`, `lime`, `green`, `yellow`
yeni kullanım açmaz. (`bg-violet-50 text-violet-600` tek bir mevcut durum çipidir; yeni yere taşıma.)
- Gradyan metin (`bg-clip-text text-transparent`) ve parlama gölgesi (`shadow-[0_0_…]`) yok.
- Hex/oklch değeri sınıf içine yazılmaz (`text-[#…]`); token ya da Tailwind tonu kullanılır.
Risk renklerinin SVG karşılığı `src/lib/risk.ts` içindeki `RISK_PIN_RENK`'ten okunur.
- Renk tek başına anlam taşımaz: risk rengi her zaman metin etiketiyle birlikte gider
(`RISK_ETIKET`, `DILIM_ETIKET` — üçüncü dilimin görünen adı "Güvenli").
## 3. Tipografi
Dört yazı tipi, her birinin tek bir işi var:
| Sınıf | Yazı tipi | İş |
|---|---|---|
| `font-heading` | Cal Sans | Tüm başlıklar (h1–h3, kart başlığı). Her zaman `font-bold` ile |
| `font-sans` (varsayılan) | Clarity City 400–800 | Gövde, buton, form, tablo |
| `font-bricolage` | Bricolage Grotesque (opsz ekseni) | Vurgu: hero'da italik mavi span, küçük etiketler/çipler, sayısal rozetler |
| `font-mono` | Geist Mono | Yalnızca kod/markdown blokları. Arayüzde kullanılmaz |
Ölçek (kopyala, uydurma):
- Hero h1: `font-heading text-5xl font-bold tracking-[0.015em] sm:text-6xl`; vurgu kelimesi
`<span className="font-bricolage italic tracking-tighter font-bold text-primary">`
- Bölüm h2: `font-heading text-3xl font-bold sm:text-4xl` (istenirse `text-slate-900`)
- Panel/kart h3: `font-heading text-xl font-bold` · liste satırı başlığı: `font-heading text-lg font-bold text-slate-900`
- Küçük başlık: `font-heading text-base font-bold text-slate-900`
- Bölüm giriş paragrafı: `max-w-xl text-lg leading-relaxed text-slate-600`
- Gövde: `text-sm` (tablo, form, kart içi) ya da `text-base leading-relaxed` (açıklama)
- Etiket/rozet metni: `text-xs font-semibold`
- Yasak: Inter, Roboto, Arial, sistem fontu, serif, `font-[…]` keyfi font; tek kelimelik
büyük harf etiketler (uppercase yok, rozet metni büyük-küçük harfli yazılır).
## 4. Bileşen stilleri (tek kaynaklar)
Bir eleman iki yerde görünüyorsa stili tek kaynaktan gelir. Aşağıdakiler yeniden tasarlanmaz.
### 4.1 Buton — `src/components/ui/button.tsx`
- Her buton `rounded-full`; `h-9` varsayılan, `h-11`/`h-12` funnel CTA'ları (44 px dokunma hedefi), `size-9` ikon butonu.
- Odak: `focus-visible:ring-3 focus-visible:ring-ring/50`; basınca `active:translate-y-px`.
- **Turuncu CTA (kanonik):**
```tsx
<Button size="lg" className="cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600">
Listemi oluştur
<ArrowRight className="size-4" aria-hidden />
</Button>
```
Metin önce, ok sonda. Başta ikon yok. `Sparkles` yok. İstenirse `active:scale-[0.97]`.
- İkincil: `variant="outline"`; sessiz: `variant="ghost"`; link görünümü: `variant="link"`.
### 4.2 Rozet (bölüm başlığı üstündeki kapsül) — `SectionEyebrow`, `src/components/pixel-decor.tsx`
```tsx
<SectionEyebrow>Nasıl çalışır?</SectionEyebrow>
```
Sınıfı: `inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-3.5 py-1.5 text-xs font-semibold text-primary` + başta `PixelMark`.
`@/components/ui/badge` **kullanılmaz**; elle pill span yazılmaz. Filtre çipleri ve nav butonları bu kuralın dışındadır (onlar etkileşimlidir).
### 4.3 Modal kapatma çarpısı — `DialogContent` varsayılanı, `src/components/ui/dialog.tsx`
`absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]` + `<XIcon className="size-4" aria-hidden />` + `sr-only "Kapat"`.
Özel yerleşim gerekiyorsa `showCloseButton={false}` ver ve **aynı sınıf setini** kopyala.
### 4.4 Kart ve kutu
- Landing/öne çıkan kart: `<Card className="relative h-full overflow-hidden border-slate-200/70 shadow-sm transition-all duration-300 hover:-translate-y-1 hover:border-primary/30 hover:shadow-lg hover:shadow-primary/5">` + sağ üstte `<PixelCorner className="absolute right-4 top-4" />`.
- Sade kutu (form, panel, liste satırı): `rounded-2xl border border-slate-200 bg-white` (+ `p-5`/`p-6`). Küçük iç kutu: `rounded-xl border border-slate-200 bg-slate-50`. Not kutusu: `rounded-lg`.
- İkon kutusu: `flex size-11 items-center justify-center rounded-xl bg-primary/10` içinde `size-5 text-primary` ikon.
- Gölge: `shadow-sm` varsayılan; hover'da `shadow-lg shadow-primary/5`. Başka gölge yok.
### 4.5 Risk çipi / etiketi
Nokta + metin: `<span className="size-2 rounded-full bg-emerald-500" />` yanında `text-xs font-semibold text-emerald-600`; metin `RISK_ETIKET`'ten. Renk tek başına asla.
### 4.6 Form
shadcn `Input`, `Select`, `Textarea`, `Slider`. Etiket her alanda; hata mesajı alanın altında, ne olduğunu ve ne yapılacağını söyler. Sayısal alanlarda uç değerler (0, negatif, 1.000.000 üstü) ele alınır.
### 4.7 İkonlar
Yalnızca `lucide-react`. Boyut: `size-4` buton/satır içi (varsayılan), `size-5` ikon kutusu, `size-3.5` küçük çip, `size-6` üstü yalnızca boş durum. Dekoratif ikon `aria-hidden`; tek başına ikon buton `aria-label` ya da `sr-only` metin taşır. Emoji ikon olarak kullanılmaz.
## 5. Yerleşim
- Sayfa kabı: `mx-auto max-w-6xl px-4`. Metin sütunu: `max-w-xl` (paragraf) / `max-w-2xl`–`max-w-3xl` (uzun içerik).
- Landing bölümü: `<section className="py-20 sm:py-24">`; alt sayfalar `py-12`–`py-16`.
- Bölüm başlık bloğu (kanonik):
```tsx
<div className="flex flex-col items-center gap-4 text-center">
<SectionEyebrow>Sorun</SectionEyebrow>
<h2 className="font-heading text-3xl font-bold sm:text-4xl">…</h2>
<p className="max-w-xl text-lg leading-relaxed text-slate-600">…</p>
</div>
```
Ardından içerik `mt-12`; kart ızgarası `grid gap-6 md:grid-cols-3` (yalnızca gerçekten üç eşdeğer öğe varsa; üçe tamamlamak için içerik uydurulmaz).
- Alt sayfa başlığının altında `PagePixelDivider` (`mt-6`/`mt-8`), her sayfada **farklı seed**.
- Landing bölümleri arasında çizgi yok; `PixelDivider` (`mx-auto w-56`, farklı seed).
- Boşluk ölçeği: 4 → 6 → 8 → 12 → 16/20 → 24. Ara değer (`mt-7`, `gap-5`) yeni yerde açılmaz.
- Mobil önce (trafiğin ~%90'ı 375 px): uzun üniversite/program adları `min-w-0` + sarma ya da `truncate`; tablo yatay kaydırmaz, kart olur; sabit alt çubuk klavyeyle çakışmaz; `vh` yerine `dvh`.
## 6. Derinlik ve katman
Derinlik gölgeyle değil **piksel katmanı ve hareket farkıyla** verilir: başlık bloğu gövdeden hafif hızlı akar (`Parallax strength={16}` başlık, `7` içerik, `26` ayraç). Kartlar hover'da 4 px yükselir. Modal arka planı `bg-black/10` + hafif blur. Yığın: içerik < sabit bar < çekmece/modal (`z-50`).
## 7. Yap / Yapma
| Yap | Yapma (AI izi) |
|---|---|
| Rozet için `SectionEyebrow` | `Badge` import'u, elle `rounded-full border … px-3.5 py-1.5` span |
| CTA: metin + sondaki `ArrowRight` | Başta `Sparkles`/yıldız ikonu, "✨" |
| Nötr = slate; vurgu = primary mavi; aksiyon = orange-500 | `indigo/purple` primary, mor→mavi gradyan, `gray/zinc` nötr |
| Kart: `rounded-2xl border-slate-200 bg-white shadow-sm` | Her şeyde `rounded-3xl`, camsı `backdrop-blur` kartlar, parlama gölgesi |
| Hareket: `Reveal` (bölüm girişi), piksel ısısı, `kt-unblur`; `animate-ping` yalnızca "şu anda canlı" noktasında | Her elemanda `fade-up`, `whileHover scale 1.05`, `animate-bounce` |
| Başlık `font-heading`, vurgu `font-bricolage` | Inter/Roboto/serif, tek kelimelik BÜYÜK HARF etiket |
| Risk rengi + metin etiketi | Renk tek başına, "yerleşirsin" gibi vaat cümlesi |
| Üç kart yalnızca üç gerçek öğe varsa | Hero + üç kart iskeleti refleksi |
| Beyaz zemin, tek koyu vurgu yüzeyi | İstenmemiş karanlık mod, `dark:` sınıfı |
| Kapatma çarpısı `DialogContent` varsayılanı | Başka boyut/renkte çarpı |
## 8. Duyarlılık
- Kırılma noktaları: 375 (temel), 640 `sm:`, 768 `md:`, 1024 `lg:`. Her bileşen önce 375'te tasarlanır.
- Dokunma hedefi en az 44 px (`h-11`, `size-11`); satır içi küçük buton `h-9` yalnızca masaüstü tablo aksiyonlarında.
- Başlık ölçeği bir kademe düşer (`text-3xl` → `sm:text-4xl`), gövde 16 px altına inmez.
- Modal genişliği `max-w-[calc(100%-2rem)]`; çekmece (`Drawer`) mobilde alttan açılır.
- `prefers-reduced-motion`: her animasyonun sade karşılığı var (`motion-reduce:animate-none`, `.kt-unblur-sade`).
## 9. Ajan için brief şablonu
Yeni bir UI işi istenince önce şu dört satır doldurulur, sonra kod yazılır:
```
Amaç: Kim, hangi ekranda, ne yapacak? (ör. veli, /paket, fiyatı karşılaştırıp satın alacak)
Referans: Sitedeki en yakın mevcut ekran/bileşen (dosya yolu). Yoksa "yok" yaz ve ana sayfa bölüm bloğunu al.
Tek kaynak: Kullanılacak kanonik parçalar (SectionEyebrow, Button+ArrowRight, Card+PixelCorner, PagePixelDivider…)
Durumlar: boş / yükleniyor / hata / başarılı / girişsiz / kredisiz — hangileri var?
```
"Modern", "temiz", "şık" gibi yön belirtmeyen kelimeler kullanılmaz; yön = bu dosya.
İş bitince: `pnpm tasarim:denetim` sıfır hata + 375 px'te tek bakış (taşma, üst üste binme, dokunma hedefi).

View File

@@ -6,7 +6,7 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint", "lint": "eslint && pnpm tasarim:denetim",
"ingest": "tsx scripts/ingest.ts", "ingest": "tsx scripts/ingest.ts",
"refresh": "tsx scripts/refresh.ts", "refresh": "tsx scripts/refresh.ts",
"detay": "tsx scripts/detay.ts", "detay": "tsx scripts/detay.ts",
@@ -19,7 +19,8 @@
"db:push": "drizzle-kit push", "db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:migrate": "node scripts/db-goc.mjs", "db:migrate": "node scripts/db-goc.mjs",
"tadimlik": "tsx scripts/tadimlik-uret.ts" "tadimlik": "tsx scripts/tadimlik-uret.ts",
"tasarim:denetim": "tsx scripts/tasarim-denetim.ts"
}, },
"dependencies": { "dependencies": {
"@cds/city": "^1.1.0", "@cds/city": "^1.1.0",

215
scripts/tasarim-denetim.ts Normal file
View File

@@ -0,0 +1,215 @@
/**
* Tasarım dili denetimi — docs/tasarim/DESIGN.md kurallarının makineyle
* yakalanabilen kısmı. Ajan/insan fark etmez: `pnpm tasarim:denetim`.
*
* Çıkış kodu: hata varsa 1, yoksa 0. Uyarılar çıkış kodunu etkilemez.
* Kapsam: src/**\/*.tsx. Hariç: src/components/ui (shadcn tabanı) ve
* src/components/dev (yalnızca geliştirici paneli) — bazı kurallarda.
*/
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
type Seviye = "hata" | "uyari";
type Bulgu = {
dosya: string;
satir: number;
kural: string;
seviye: Seviye;
mesaj: string;
parca: string;
};
type Kural = {
ad: string;
seviye: Seviye;
mesaj: string;
/** ui/dev klasörleri de taransın mı */
uiDahil?: boolean;
/** Tam yolu eşleşen dosyalar kuraldan muaf */
muaf?: RegExp;
bul: (icerik: string) => Array<{ index: number; parca: string }>;
};
const KOK = process.cwd();
const KAYNAK = join(KOK, "src");
const UI_VEYA_DEV = /src\/components\/(ui|dev)\//;
function tsxDosyalari(dizin: string): string[] {
const sonuc: string[] = [];
for (const ad of readdirSync(dizin)) {
const yol = join(dizin, ad);
if (statSync(yol).isDirectory()) sonuc.push(...tsxDosyalari(yol));
else if (yol.endsWith(".tsx")) sonuc.push(yol);
}
return sonuc;
}
function satirNo(icerik: string, index: number): number {
let n = 1;
for (let i = 0; i < index && i < icerik.length; i++) if (icerik[i] === "\n") n++;
return n;
}
function satirParcasi(icerik: string, index: number): string {
const bas = icerik.lastIndexOf("\n", index) + 1;
let son = icerik.indexOf("\n", index);
if (son === -1) son = icerik.length;
return icerik.slice(bas, son).trim().slice(0, 110);
}
/** Basit regex kuralı: her eşleşme bir bulgu. */
function regexKurali(re: RegExp) {
return (icerik: string) => {
const g = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
const sonuc: Array<{ index: number; parca: string }> = [];
for (const m of icerik.matchAll(g)) sonuc.push({ index: m.index ?? 0, parca: m[0] });
return sonuc;
};
}
/** <Etiket …>…</Etiket> bloklarının içinde `ic` deseni varsa bulgu. */
function blokIcinde(etiketler: string[], ic: RegExp) {
return (icerik: string) => {
const sonuc: Array<{ index: number; parca: string }> = [];
for (const etiket of etiketler) {
const blok = new RegExp(`<${etiket}\\b[\\s\\S]*?<\\/${etiket}>`, "g");
for (const m of icerik.matchAll(blok)) {
const icM = ic.exec(m[0]);
if (icM) sonuc.push({ index: (m.index ?? 0) + icM.index, parca: icM[0] });
}
}
return sonuc;
};
}
const RENK_ONEK = "(?:bg|text|border|ring|fill|stroke|from|to|via|divide|outline|placeholder|decoration|accent|caret|shadow)";
const KURALLAR: Kural[] = [
{
ad: "badge-import",
seviye: "hata",
mesaj: "Rozet için @/components/ui/badge değil SectionEyebrow kullanılır (DESIGN.md §4.2)",
bul: regexKurali(/from\s+["']@\/components\/ui\/badge["']/),
},
{
ad: "elle-rozet",
seviye: "hata",
mesaj: "Elle yazılmış rozet; SectionEyebrow'u içe aktar (DESIGN.md §4.2)",
muaf: /src\/components\/pixel-decor\.tsx$/,
bul: regexKurali(/rounded-full border border-slate-200 bg-white px-3\.5 py-1\.5/),
},
{
ad: "cta-yildiz",
seviye: "hata",
mesaj: "CTA butonunda Sparkles/yıldız ikonu olmaz; metin + sondaki ArrowRight (DESIGN.md §4.1)",
bul: blokIcinde(["Button", "Link"], /<Sparkles\b/),
},
{
ad: "yabanci-notr",
seviye: "hata",
mesaj: "Nötr renk yalnızca slate; gray/zinc/neutral/stone yazılmaz (DESIGN.md §2)",
bul: regexKurali(new RegExp(`\\b${RENK_ONEK}-(?:gray|zinc|neutral|stone)-\\d+`)),
},
{
ad: "yabanci-renk",
seviye: "hata",
mesaj: "Paletin dışında ton; primary mavi / orange-500 / slate / risk renkleri kullanılır (DESIGN.md §2)",
bul: regexKurali(
new RegExp(`\\b${RENK_ONEK}-(?:indigo|purple|fuchsia|pink|cyan|teal|lime|green|yellow)-\\d+`),
),
},
{
ad: "gradyan-metin",
seviye: "hata",
mesaj: "Gradyan metin yok (DESIGN.md §2)",
bul: regexKurali(/\b(?:bg-clip-text|text-transparent)\b/),
},
{
ad: "parlama-golgesi",
seviye: "hata",
mesaj: "Parlama (glow) gölgesi yok; shadow-sm / hover:shadow-lg shadow-primary/5 (DESIGN.md §4.4)",
bul: regexKurali(/shadow-\[0_0_[1-9]\d*px/),
},
{
ad: "hareket-spam",
seviye: "hata",
mesaj:
"whileHover scale / aos / bounce yok; Reveal, piksel ısısı, kt-unblur (DESIGN.md §7). animate-ping yalnızca canlı göstergesi noktasında",
bul: regexKurali(/whileHover=\{\{\s*scale|data-aos=|\banimate-bounce\b/),
},
{
ad: "kapatma-carpisi",
seviye: "hata",
mesaj: "showCloseButton={false} verilen dosyada kanonik çarpı sınıf seti yok (DESIGN.md §4.3)",
bul: (icerik) => {
const m = /showCloseButton=\{false\}/.exec(icerik);
if (!m) return [];
const kanonik =
/size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200/.test(icerik);
return kanonik ? [] : [{ index: m.index, parca: m[0] }];
},
},
{
ad: "yabanci-font",
seviye: "hata",
mesaj: "Yalnızca font-heading / font-sans / font-bricolage / font-mono (DESIGN.md §3)",
bul: regexKurali(/\bfont-(?:serif|\[)|["'](?:Inter|Roboto|Arial|Helvetica)["']/),
},
{
ad: "dark-sinif",
seviye: "uyari",
mesaj: "Üründe karanlık mod yok; dark: sınıfı gereksiz (DESIGN.md §1)",
bul: regexKurali(/(?<=[\s"'`])dark:[a-z]/),
},
{
ad: "hex-renk",
seviye: "uyari",
mesaj: "Sınıf içinde hex renk; token ya da Tailwind tonu kullan (DESIGN.md §2)",
bul: regexKurali(/\[#[0-9a-fA-F]{3,8}\]/),
},
{
ad: "emoji-ikon",
seviye: "uyari",
mesaj: "Emoji ikon olarak kullanılmaz; lucide ikon (DESIGN.md §4.7)",
bul: (icerik) =>
// Yorum satırındaki işaretler (// ✗ gibi) ikon değildir
regexKurali(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}]\uFE0F?/u)(icerik).filter(
(e) => !/^(?:\/\/|\*|\/\*)/.test(satirParcasi(icerik, e.index)),
),
},
];
const bulgular: Bulgu[] = [];
for (const yol of tsxDosyalari(KAYNAK)) {
const gorece = relative(KOK, yol);
const icerik = readFileSync(yol, "utf8");
for (const k of KURALLAR) {
if (!k.uiDahil && UI_VEYA_DEV.test(gorece)) continue;
if (k.muaf && k.muaf.test(gorece)) continue;
for (const e of k.bul(icerik)) {
bulgular.push({
dosya: gorece,
satir: satirNo(icerik, e.index),
kural: k.ad,
seviye: k.seviye,
mesaj: k.mesaj,
parca: satirParcasi(icerik, e.index),
});
}
}
}
bulgular.sort((a, b) =>
a.seviye !== b.seviye ? (a.seviye === "hata" ? -1 : 1) : a.dosya.localeCompare(b.dosya) || a.satir - b.satir,
);
for (const b of bulgular) {
const etiket = b.seviye === "hata" ? "HATA " : "uyarı";
console.log(`${b.dosya}:${b.satir} [${etiket}] ${b.kural} — ${b.mesaj}\n ${b.parca}`);
}
const hata = bulgular.filter((b) => b.seviye === "hata").length;
const uyari = bulgular.length - hata;
console.log(`\nTasarım denetimi: ${hata} hata, ${uyari} uyarı (${KURALLAR.length} kural, docs/tasarim/DESIGN.md)`);
process.exit(hata > 0 ? 1 : 0);

View File

@@ -122,7 +122,7 @@ export function AramaFunnelKarti({ onSec }: { onSec: () => void }) {
key={etiket} key={etiket}
className="inline-flex items-center gap-1.5 text-xs font-medium text-slate-200" className="inline-flex items-center gap-1.5 text-xs font-medium text-slate-200"
> >
<Ikon className="size-3.5 text-cyan-300" aria-hidden /> <Ikon className="size-3.5 text-orange-500" aria-hidden />
{etiket} {etiket}
</span> </span>
))} ))}