From dc7861737b388ab3cf74f74798a372ac9c994e31 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Wed, 23 Sep 2026 14:44:35 +0300 Subject: [PATCH] =?UTF-8?q?feat(tasarim):=20g=C3=B6rsel=20dilin=20tek=20ka?= =?UTF-8?q?yna=C4=9F=C4=B1=20DESIGN.md,=20proje=20skill'i=20ve=20tasar?= =?UTF-8?q?=C4=B1m=20denetim=20beti=C4=9Fi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../skills-devre-disi}/ui-ux-pro-max/SKILL.md | 0 .../ui-ux-pro-max/data/charts.csv | 0 .../ui-ux-pro-max/data/colors.csv | 0 .../ui-ux-pro-max/data/icons.csv | 0 .../ui-ux-pro-max/data/landing.csv | 0 .../ui-ux-pro-max/data/products.csv | 0 .../ui-ux-pro-max/data/prompts.csv | 0 .../ui-ux-pro-max/data/react-performance.csv | 0 .../data/stacks/html-tailwind.csv | 0 .../ui-ux-pro-max/data/stacks/nextjs.csv | 0 .../ui-ux-pro-max/data/stacks/react.csv | 0 .../ui-ux-pro-max/data/stacks/shadcn.csv | 0 .../ui-ux-pro-max/data/styles.csv | 0 .../ui-ux-pro-max/data/typography.csv | 0 .../ui-ux-pro-max/data/ui-reasoning.csv | 0 .../ui-ux-pro-max/data/ux-guidelines.csv | 0 .../ui-ux-pro-max/data/web-interface.csv | 0 .../ui-ux-pro-max/scripts/core.py | 0 .../ui-ux-pro-max/scripts/design_system.py | 0 .../ui-ux-pro-max/scripts/search.py | 0 .agents/skills/kolaytercih-tasarim/SKILL.md | 86 +++++++ .claude/agents/tasarimci.md | 17 +- .claude/agents/yazilimci.md | 6 +- .claude/skills/kolaytercih-tasarim | 1 + AGENTS.md | 6 +- docs/tasarim/DESIGN.md | 181 +++++++++++++++ package.json | 5 +- scripts/tasarim-denetim.ts | 215 ++++++++++++++++++ src/components/arama-funnel-karti.tsx | 2 +- 29 files changed, 506 insertions(+), 13 deletions(-) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/SKILL.md (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/charts.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/colors.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/icons.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/landing.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/products.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/prompts.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/react-performance.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/stacks/html-tailwind.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/stacks/nextjs.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/stacks/react.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/stacks/shadcn.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/styles.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/typography.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/ui-reasoning.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/ux-guidelines.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/data/web-interface.csv (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/scripts/core.py (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/scripts/design_system.py (100%) rename {.claude/skills => .agents/skills-devre-disi}/ui-ux-pro-max/scripts/search.py (100%) create mode 100644 .agents/skills/kolaytercih-tasarim/SKILL.md create mode 120000 .claude/skills/kolaytercih-tasarim create mode 100644 docs/tasarim/DESIGN.md create mode 100644 scripts/tasarim-denetim.ts diff --git a/.claude/skills/ui-ux-pro-max/SKILL.md b/.agents/skills-devre-disi/ui-ux-pro-max/SKILL.md similarity index 100% rename from .claude/skills/ui-ux-pro-max/SKILL.md rename to .agents/skills-devre-disi/ui-ux-pro-max/SKILL.md diff --git a/.claude/skills/ui-ux-pro-max/data/charts.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/charts.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/charts.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/charts.csv diff --git a/.claude/skills/ui-ux-pro-max/data/colors.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/colors.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/colors.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/colors.csv diff --git a/.claude/skills/ui-ux-pro-max/data/icons.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/icons.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/icons.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/icons.csv diff --git a/.claude/skills/ui-ux-pro-max/data/landing.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/landing.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/landing.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/landing.csv diff --git a/.claude/skills/ui-ux-pro-max/data/products.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/products.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/products.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/products.csv diff --git a/.claude/skills/ui-ux-pro-max/data/prompts.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/prompts.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/prompts.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/prompts.csv diff --git a/.claude/skills/ui-ux-pro-max/data/react-performance.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/react-performance.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/react-performance.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/react-performance.csv diff --git a/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/stacks/html-tailwind.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/stacks/html-tailwind.csv diff --git a/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/stacks/nextjs.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/stacks/nextjs.csv diff --git a/.claude/skills/ui-ux-pro-max/data/stacks/react.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/stacks/react.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/stacks/react.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/stacks/react.csv diff --git a/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/stacks/shadcn.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/stacks/shadcn.csv diff --git a/.claude/skills/ui-ux-pro-max/data/styles.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/styles.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/styles.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/styles.csv diff --git a/.claude/skills/ui-ux-pro-max/data/typography.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/typography.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/typography.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/typography.csv diff --git a/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/ui-reasoning.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/ui-reasoning.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/ui-reasoning.csv diff --git a/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/ux-guidelines.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/ux-guidelines.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/ux-guidelines.csv diff --git a/.claude/skills/ui-ux-pro-max/data/web-interface.csv b/.agents/skills-devre-disi/ui-ux-pro-max/data/web-interface.csv similarity index 100% rename from .claude/skills/ui-ux-pro-max/data/web-interface.csv rename to .agents/skills-devre-disi/ui-ux-pro-max/data/web-interface.csv diff --git a/.claude/skills/ui-ux-pro-max/scripts/core.py b/.agents/skills-devre-disi/ui-ux-pro-max/scripts/core.py similarity index 100% rename from .claude/skills/ui-ux-pro-max/scripts/core.py rename to .agents/skills-devre-disi/ui-ux-pro-max/scripts/core.py diff --git a/.claude/skills/ui-ux-pro-max/scripts/design_system.py b/.agents/skills-devre-disi/ui-ux-pro-max/scripts/design_system.py similarity index 100% rename from .claude/skills/ui-ux-pro-max/scripts/design_system.py rename to .agents/skills-devre-disi/ui-ux-pro-max/scripts/design_system.py diff --git a/.claude/skills/ui-ux-pro-max/scripts/search.py b/.agents/skills-devre-disi/ui-ux-pro-max/scripts/search.py similarity index 100% rename from .claude/skills/ui-ux-pro-max/scripts/search.py rename to .agents/skills-devre-disi/ui-ux-pro-max/scripts/search.py diff --git a/.agents/skills/kolaytercih-tasarim/SKILL.md b/.agents/skills/kolaytercih-tasarim/SKILL.md new file mode 100644 index 0000000..2381d32 --- /dev/null +++ b/.agents/skills/kolaytercih-tasarim/SKILL.md @@ -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 +
+ Öğrenci görüşleri +

Bu bölümü okuyanlar ne diyor?

+

…

+
+``` + +**İstek:** "Paket sayfasına satın alma butonu koy." +**Çıktı:** +```tsx + +``` +(Yanlış: başta ``, `bg-gradient-to-r`, `rounded-xl`, "Hemen Al ✨".) + +**İstek:** "Üniversite sayfasına URAP kartı ekle." +**Çıktı:** +```tsx +
+ +

URAP Türkiye

+

18 / 198

+
+``` + +**İ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. diff --git a/.claude/agents/tasarimci.md b/.claude/agents/tasarimci.md index 8c229a3..16c4ebe 100644 --- a/.claude/agents/tasarimci.md +++ b/.claude/agents/tasarimci.md @@ -1,6 +1,8 @@ --- 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. +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 --- @@ -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. - 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). -- **Etkileşim/dokunma/çekmece/sheet işlerinde:** `apple-design` ve `emil-design-eng`. -- **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. -- **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. +Yalnızca ihtiyaç doğduğunda, `Skill` aracıyla: +- Yeni bir shadcn bileşeni eklenecekse: `shadcn`. +- Etkileşim/dokunma/çekmece/sheet işinde: `apple-design`, `emil-design-eng`. +- 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. -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 diff --git a/.claude/agents/yazilimci.md b/.claude/agents/yazilimci.md index 954a953..0626014 100644 --- a/.claude/agents/yazilimci.md +++ b/.claude/agents/yazilimci.md @@ -2,6 +2,8 @@ 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. 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. @@ -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. 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. -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. +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` (`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. 6. `main`'e commit yok, push yok, merge yok, başka worktree'de komut yok. diff --git a/.claude/skills/kolaytercih-tasarim b/.claude/skills/kolaytercih-tasarim new file mode 120000 index 0000000..c69f397 --- /dev/null +++ b/.claude/skills/kolaytercih-tasarim @@ -0,0 +1 @@ +../../.agents/skills/kolaytercih-tasarim \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 02b6842..47da23b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 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 -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 diff --git a/docs/tasarim/DESIGN.md b/docs/tasarim/DESIGN.md new file mode 100644 index 0000000..60aa7bc --- /dev/null +++ b/docs/tasarim/DESIGN.md @@ -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 + `` +- 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 + + ``` + 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 +Nasıl çalışır? +``` +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]` + `` + `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: `` + sağ üstte ``. +- 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: `` 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ü: `
`; alt sayfalar `py-12`–`py-16`. +- Bölüm başlık bloğu (kanonik): + ```tsx +
+ Sorun +

…

+

…

+
+ ``` + 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). diff --git a/package.json b/package.json index a015fa9..e6c1739 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint", + "lint": "eslint && pnpm tasarim:denetim", "ingest": "tsx scripts/ingest.ts", "refresh": "tsx scripts/refresh.ts", "detay": "tsx scripts/detay.ts", @@ -19,7 +19,8 @@ "db:push": "drizzle-kit push", "db:generate": "drizzle-kit generate", "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": { "@cds/city": "^1.1.0", diff --git a/scripts/tasarim-denetim.ts b/scripts/tasarim-denetim.ts new file mode 100644 index 0000000..27a0e25 --- /dev/null +++ b/scripts/tasarim-denetim.ts @@ -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; + }; +} + +/** … 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"], / { + 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); diff --git a/src/components/arama-funnel-karti.tsx b/src/components/arama-funnel-karti.tsx index 6c3b9b7..9979195 100644 --- a/src/components/arama-funnel-karti.tsx +++ b/src/components/arama-funnel-karti.tsx @@ -122,7 +122,7 @@ export function AramaFunnelKarti({ onSec }: { onSec: () => void }) { key={etiket} className="inline-flex items-center gap-1.5 text-xs font-medium text-slate-200" > - + {etiket} ))}