diff --git a/.agents/skills/kolaytercih-tasarim/SKILL.md b/.agents/skills/kolaytercih-tasarim/SKILL.md index f2585c3..555ee3d 100644 --- a/.agents/skills/kolaytercih-tasarim/SKILL.md +++ b/.agents/skills/kolaytercih-tasarim/SKILL.md @@ -10,8 +10,8 @@ yön zaten belli. Kaynak tek: `docs/tasarim/DESIGN.md`. Bu dosya onu nasıl uygu ## 1. Başlamadan önce (zorunlu) -1. `docs/tasarim/DESIGN.md` dosyasını **tamamını** oku (477 satır). Özellikle §1.1 temel his, §4 tek kaynaklar, §7 yap/yapma ve §9.2 ölç-tahmin-etme. Canlı referans ekran: `/universite/ankara-universitesi`. -2. Dört satırlık brief'i doldur (DESIGN.md §9): amaç · referans ekran · tek kaynak parçalar · durumlar. +1. `docs/tasarim/DESIGN.md` dosyasını **tamamını** oku (uzun bir belge; atlama). Özellikle §1.1 temel his, §4 tek kaynaklar, §6 hareket, §7 yap/yapma, §9.2 ölç-tahmin-etme ve **§11 deneyim ilkeleri**. Canlı referans ekran: `/universite/ankara-universitesi`. +2. Beş satırlık brief'i doldur (DESIGN.md §9.1): amaç · referans ekran · tek kaynak parçalar · durumlar · his. Yanına bir satır ekle: **§11.1'deki hangi vaka bu işe en yakın ve hangi *nedeni* taşıyacağım?** 3. Referans ekranı bul ve aç; kopyalayacağın sınıf dizilerini oradan al: ```bash @@ -20,26 +20,54 @@ grep -rn "bg-orange-500" src --include='*.tsx' -l # turuncu CTA örnek 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`. +Ana sayfa bölüm iskeleti: `src/app/page.tsx` ("Sorun" bölümü; `grep -n "SectionEyebrow" src/app/page.tsx`). 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`). + (`@/components/pixel-decor`); `Button` (`@/components/ui/button`; turuncu CTA `variant="cta"`, + ikincil `variant="ikincil"`); `Card` (`@/components/ui/card`); `AdimGostergesi` + (`@/components/ui/adim-gostergesi`); `AltSayfa` (`@/components/ui/alt-sayfa`); `SayiRulo` + (`@/components/ui/sayi-rulo`); `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). +## 2.1 Hareket ve deneyim (DESIGN.md §6, §11) + +Site "web sitesi değil web uygulaması" gibi hissettirir. Yazdığın her parça için: + +- **Her eylemin cevabı var.** Basınca `active:scale-[0.97]`; sonucu olan eylem sonucunu gösterir + (sayı `SayiRulo` ile döner, seçim vurgusu `layoutId` ile kayar, tik `pathLength` ile çizilir). + Koşullu render ile anında yer değiştiren içerik son çaredir. +- **Durumu yansıt.** Sıra (`useTercihProfili`), liste doluluğu (`liste-store`), giriş: ekranın + bunlardan haberi olmalı mı? Olmalıysa göster. +- **Yay/eğri/süre uydurma.** Yalnız `src/lib/hareket.ts`: `YAY.varsayilan | akici | donusum | + birakma | adim`, `EGRI.giris | standart | gucluCikis | yerlesme`, `SURE.*`. Sekme yalnız bir + şey fırlıyorsa (`YAY.birakma`). +- **Sahne tek.** Sıralı, sahnelenmiş belirme sayfanın ilk ekranında bir kez; alt bölümler `Reveal`. + Yüzey açılıyorsa önce imza sonra içerik (`AcilisSahnesi`). +- **Sade karşılığı yaz.** `useReducedMotion` (değişken adı `azalt`) ya da `motion-reduce:`; + hareketi azalt'ta yalnız opaklık. +- **Telefonda gizleme.** Hover'a bağlı her cevabın dokunmatik karşılığı olsun. +- **Dış kaynaktan (Arc, React Bits, Skiper, Motion Primitives) alıyorsan** §11.3'ün yedi adımı: + kod bizim dosyamızda + kaynak/lisans yorumu · yeni bağımlılık yok (`motion` ile yeniden yaz) · + renk token'a · hareket `hareket.ts`'e · biçim bize (hap, `font-heading`, ≥44 px, gölge yok) · + sade karşılık + klavye/aria · lisans notu. + ## 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.) +Uyarıları raporda listele; hataları düzelt. **Yeni bir parça tasarladıysan ya da bir bölümü +yeniden düzenlediysen ekrana bak** (AGENTS.md "Tarayıcı açma": 375 px + masaüstü ekran görüntüsü; +önce Bilal'in açık sunucusunu ara). Küçük düzeltmede (metin, renk, tek sınıf) 375 px notu yeter: +taşma, üst üste binme, dokunma hedefi < 44 px, uzun üniversite adı — koddan çıkarım yazıyorsan +"koddan çıkarım" de. Sonra DESIGN.md §11.5 deneyim kontrol listesinden geç. ## 4. Örnekler @@ -56,12 +84,18 @@ dokunma hedefi < 44 px, uzun üniversite adı. (Tarayıcı açma kuralı AGENTS. **İstek:** "Paket sayfasına satın alma butonu koy." **Çıktı:** ```tsx - ``` -(Yanlış: başta ``, `bg-gradient-to-r`, `rounded-xl`, "Hemen Al ✨".) +(Yanlış: elle `bg-orange-500 hover:bg-orange-600`, başta ``, `bg-gradient-to-r`, `rounded-xl`, "Hemen Al ✨".) + +**İstek:** "React Bits'teki şu sayaç bileşenini fiyat bölümüne koy." +**Çıktı:** Registry'den kurma. Önce sitede karşılığı var mı bak (`SayiRulo`); varsa onu kullan. +Yoksa kaynağı oku (`curl -s https://reactbits.dev/r/-TS-TW.json`), `src/components/ui/.tsx` +olarak yeniden yaz: başta `// Esin: React Bits "" (MIT + Commons Clause); kod KolayTercih diline çevrildi.`, +GSAP/renk/easing atılır, `YAY.*` + token + `useReducedMotion` dalı girer. **İstek:** "Üniversite sayfasına URAP kartı ekle." **Çıktı:** @@ -79,12 +113,13 @@ dokunma hedefi < 44 px, uzun üniversite adı. (Tarayıcı açma kuralı AGENTS. ## 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:` · yüzeyde `bg-white` (yerine `bg-card`, §2.3) · Inter/Roboto/serif · `text-[#hex]` · renk tek başına anlam. +parlama gölgesi · `whileHover scale` · her elemanda fade-up · `dark:` · yüzeyde `bg-white` (yerine `bg-card`, §2.3) · Inter/Roboto/serif · `text-[#hex]` · renk tek başına anlam · +`gsap`/`framer-motion` importu · kullanım yerinde `stiffness:`/`cubic-bezier(` · `Button` üstünde elle `bg-orange-500` · satır içi adım çubuğu · sessiz eylem · telefonda gizlenen deneyim. ## 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. +İş sonunda: kullanılan kanonik parçalar (liste) · en yakın §11.1 vakası ve taşıdığın neden · `pnpm tasarim:denetim` çıktısı (hata/uyarı sayısı) · baktıysan hangi genişlikte ne gördüğün, bakmadıysan 375 px notu · §11.5 listesinde işaretleyemediğin madde · DESIGN.md ile çelişen bir şey bulduysan dosya:satır ile yaz, kendi başına kuralı değiştirme. ## 7. Skill'i ölçme -Skill'in işe yarayıp yaramadığı `evals.md` içindeki üç istekle ölçülür (skill kapalı/açık karşılaştırması). Skill metnini ya da DESIGN.md'yi değiştirdikten sonra tekrar koşulur. +Skill'in işe yarayıp yaramadığı `evals.md` içindeki dört istekle ölçülür (skill kapalı/açık karşılaştırması). Skill metnini ya da DESIGN.md'yi değiştirdikten sonra tekrar koşulur. diff --git a/.agents/skills/kolaytercih-tasarim/evals.md b/.agents/skills/kolaytercih-tasarim/evals.md index 8e2f4f6..ec8b515 100644 --- a/.agents/skills/kolaytercih-tasarim/evals.md +++ b/.agents/skills/kolaytercih-tasarim/evals.md @@ -1,7 +1,7 @@ # kolaytercih-tasarim — değerlendirme seti Anthropic'in skill kılavuzu: önce eval, sonra skill. Bu üç istek, skill'in gerçekten -işe yaradığını ölçer. Her biri **iki kez** koşulur: (A) skill kapalı, (B) skill açık. +işe yaradığını ölçer (E4, 9 Eki 2026'da eklendi). Her biri **iki kez** koşulur: (A) skill kapalı, (B) skill açık. Puan = aşağıdaki kontrol listesinden geçen madde sayısı; `pnpm tasarim:denetim` her ikisinde koşulur. Nasıl koşulur: yeni bir oturumda isteği aynen yapıştır; çıktı `src/` altına değil @@ -25,10 +25,10 @@ Kontrol: Kontrol: - [ ] `Button` bileşeni, `rounded-full` (özel radius yok) -- [ ] `bg-orange-500 … hover:bg-orange-600 text-white` +- [ ] `variant="cta"` + `size="lg"`; elle `bg-orange-500 … hover:bg-orange-600` yok - [ ] Metin önce, sonda `` - [ ] Başta ikon yok, `Sparkles` yok, emoji yok, gradyan yok -- [ ] Yükseklik `h-11` ya da `h-12` (44 px dokunma hedefi) +- [ ] Yükseklik sınıfı elle yazılmamış (`lg` = 48 px dokunma hedefi) - [ ] Denetim: 0 hata ## E3 — Bilgi kartı @@ -36,18 +36,31 @@ Kontrol: **İstek:** "Bölüm sayfasına 'Mezun maaşı' kartı ekle; başlık, büyük sayı ve kaynak notu olsun." Kontrol: -- [ ] Kutu `rounded-2xl border border-slate-200 bg-white` (ya da `Card` + `border-slate-200/70 shadow-sm`) +- [ ] Kutu `rounded-2xl border border-slate-200 bg-card` (ya da `Card` + `border-slate-200/70 shadow-sm`); `bg-white` yok - [ ] Sağ üstte `PixelCorner` (`absolute right-4 top-4`) - [ ] Sayı `font-heading … font-bold text-slate-900`; etiket `text-xs font-semibold text-slate-500` - [ ] Nötr yalnızca slate; gölge yalnızca `shadow-sm` - [ ] Kaynak notu için uydurma rakam/atıf yok ("kaynak: —" ya da prop) - [ ] Denetim: 0 hata +## E4 — Dış bileşeni bizim dile çevir + +**İstek:** "React Bits'teki Stepper'ı (https://reactbits.dev/r/Stepper-TS-TW.json) paket satın alma akışına koy." + +Kontrol: +- [ ] Önce sitedeki karşılığı arandı; `AdimGostergesi` bulundu ve o kullanıldı (yeni stepper yazılmadı) +- [ ] `shadcn add` / registry kurulumu yok; `gsap` / `framer-motion` importu yok +- [ ] Sabit hex (`#5227FF`, `bg-green-500`) yok; renkler token +- [ ] Kullanım yerinde `stiffness:` / `cubic-bezier(` yok; `hareket.ts` adları +- [ ] `useReducedMotion` dalı ve `aria-current="step"` var +- [ ] Yeni dosya yazıldıysa başında kaynak + lisans yorumu +- [ ] Denetim: 0 hata + ## Sonuç tablosu -| Tarih | E1 A/B | E2 A/B | E3 A/B | Not | -|---|---|---|---|---| -| | | | | | +| Tarih | E1 A/B | E2 A/B | E3 A/B | E4 A/B | Not | +|---|---|---|---|---|---| +| | | | | | | B sütunu A'dan belirgin yüksek değilse skill'in metni ya da DESIGN.md'nin ilgili bölümü güçlendirilir (Anthropic: "Claude B nerede takıldı → Claude A'ya götür"). diff --git a/.claude/agents/tasarimci.md b/.claude/agents/tasarimci.md index 16c4ebe..e997d80 100644 --- a/.claude/agents/tasarimci.md +++ b/.claude/agents/tasarimci.md @@ -12,10 +12,18 @@ Sen KolayTercih ekibinin **ürün tasarımcısısın**. İşe başlamadan önce - Rozet/eyebrow: `src/components/pixel-decor.tsx` → `SectionEyebrow`. `Badge` ya da elle yazılmış pill yok. - Modal kapatma çarpısı: `src/components/ui/dialog.tsx` → `DialogContent` varsayılanı; özel yerleşimde aynı class seti. -- CTA: metin önce, sonda ``; `Sparkles` yok, başta ikon yok. +- CTA: `Button variant="cta"` (ikincil: `variant="ikincil"`); metin önce, sonda ``; `Sparkles` yok, başta ikon yok, elle `bg-orange-500` yok. +- Çok adımlı akış: `AdimGostergesi` (DESIGN.md §4.35). Telefon menüsü: `MobilMenu` (§4.36). Logoya tepki: `logoTepkisi()` (§4.34). +- Hareket: yay/eğri/süre yalnız `src/lib/hareket.ts`'ten; kullanım yerinde değer uydurulmaz. - 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`. +## Deneyim dili (her işte, önce) + +Bilal'in zevki `docs/tasarim/DESIGN.md` **§11**'de yazılıdır ve her tasarım tanımının ölçüsüdür: site "web sitesi değil web uygulaması" gibi hissettirir. İşe başlarken §11.1'deki referans vakalardan (logo morph'u, hero piksel haritası, çekmecede önce logo sonra içerik, üst çubuk üçlüsü, kayan vurgu/dönen sayı) en yakınını aç ve tanımına "hangi vakanın *nedenini* taşıyor" diye yaz. Her tanım §11.5 kontrol listesiyle biter. Bir ekran statik, sessiz ya da kullanıcının durumundan habersizse bu bir bulgudur — zevk yorumu değil, §11.2 ihlali. + +Dış kaynaklar (Arc `uiarc.dev`, React Bits `reactbits.dev`, Skiper UI `skiper-ui.com`, Motion Primitives `motion-primitives.com`) esin için serbesttir; kaynak kodu registry JSON'undan okunur (`components.json`'da `@uiarc`, `@react-bits`, `@skiper-ui`; ör. `curl -s https://reactbits.dev/r/Stepper-TS-TW.json`). Öneride bileşeni olduğu gibi kurdurma: §11.3'ün yedi adımlık çevirisini tanıma yaz (hangi renk hangi token'a, hangi yay `hareket.ts`'teki hangi ada, hangi bağımlılık `motion` ile nasıl karşılanır, sade karşılığı ne). + ## Beceriler 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. @@ -39,10 +47,10 @@ Her denetimde `pnpm tasarim:denetim` çıktısını rapora koy; makinenin yakala ## Sınırlar -- Tarayıcı/dev sunucusu açma, ekran görüntüsü alma — denetim koddan yapılır. Görsel doğrulamayı Bilal yapar; kontrol etmesi gereken ekranları kısa liste olarak ver. +- Tarayıcı: `AGENTS.md` "Tarayıcı açma" bölümü geçerlidir. Koddan teşhis edilemeyen şeyde (taşma, oran, hiyerarşi, karanlık mod, hareketin hissi) önizlemeyi **açarsın**; yeni bir parça ya da yeniden düzenleme tanımlıyorsan bakmak zorunludur (375 px + masaüstü). Önce Bilal'in açık sunucusuna bak (`lsof -iTCP -sTCP:LISTEN -P -n | grep node`), yoksa aç ve iş bitince kapat; raporda hangi genişlikte ne gördüğünü ve neye bakmadığını yaz. Küçük tutarlılık denetimi koddan yapılır. - `src/` altına dokunmazsın; yalnızca `docs/tasarim/` altına yazarsın. Uygulama `yazilimci`'nin işi. - Marka terimi, logo, renk paleti değişikliği → `[BİLAL]`. -- Zevk yorumu yok: her bulgu ya bir kuralı (AGENTS.md, WCAG, tek kaynak) ya da somut bir kullanıcı sorununu göstermeli. +- Kendi zevk yorumun yok: her bulgu ya bir kuralı (AGENTS.md, DESIGN.md — §11 dahil, WCAG, tek kaynak) ya da somut bir kullanıcı sorununu göstermeli. Bilal'in zevki §11'de kuraldır; ona dayanan bulgu geçerlidir. ## Rapor diff --git a/.claude/settings.json b/.claude/settings.json index e19602d..346266e 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,7 +1,8 @@ { "enabledPlugins": { "superpowers@superpowers-marketplace": false, - "prd-development@pm-skills": false + "prd-development@pm-skills": false, + "typesafe@typesafe-ai": true }, "extraKnownMarketplaces": { "superpowers-marketplace": { diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 434d1fc..41358b5 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -33,6 +33,14 @@ jobs: IYZICO_BASE_URL=${{ secrets.IYZICO_BASE_URL }} OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }} OPENROUTER_MODEL=${{ secrets.OPENROUTER_MODEL }} + # İsteğe bağlı: sohbetten liste revizyonu niyet çıkarımının modeli + # (src/lib/ai/revizyon-niyeti.ts). Secret yoksa boş gelir → rapor + # modeli kullanılır. Ölçüm 10 Eki 2026: google/gemini-3.1-flash-lite + # tek sağlayıcı, p90 1,9 sn, 42/44 kural doğru (deepseek p90 5-15 sn). + OPENROUTER_NIYET_MODEL=${{ secrets.OPENROUTER_NIYET_MODEL }} + # İsteğe bağlı: reasoning_effort ("none" varsayılan; bazı modeller + # "none"ı reddeder → "minimal"/"low", "yok" alanı göndermez). + AI_REASONING_EFFORT=${{ secrets.AI_REASONING_EFFORT }} # İsteğe bağlı: danışman çıktı denetimi (src/lib/ai/jev.ts). Secret # yoksa boş gelir → denetim atlanır, danışman aynen çalışır. TYPESAFE_API_KEY=${{ secrets.TYPESAFE_API_KEY }} diff --git a/.gitignore b/.gitignore index 2dc4bdb..1e77659 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ data/ham/ # YÖK Atlas API ham dökümleri (refresh.ts, program-ozellik.ts) /data/ham/ + +# Türkçe TTS sanal ortamı (scripts/seslendir.py) +.venv-tts/ diff --git a/AGENTS.md b/AGENTS.md index 63ea981..f42f919 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,6 +50,15 @@ Push emri geldiğinde önce lokalde `pnpm build` başarıyla geçmeli; geçmeden 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. +# Deneyim dili: web sitesi değil web uygulaması (9 Eki 2026) + +Bilal'in zevki `docs/tasarim/DESIGN.md` **§11 "Deneyim ilkeleri ve referans vakalar"**da yazılıdır; "UI şöyle mantıkta olsun" diye tekrar anlatmaz, oradan okunur. Yeni bir ekran, bileşen ya da hareket tasarlayan herkes (ana oturum dahil) işe başlamadan §11.1'deki en yakın vakayı açar, bitince §11.5 listesinden geçirir. Özü: + +- **Referans vakalar** ("harika" denenler): tema değişince sayfadan *sonra* dönüşen logo; hero'daki iri Türkiye piksel haritası ve aşağı dağılan pikseller; çekmecede önce logo sonra içerik; üst çubuktaki Sıralama · Ara · Listem bütünlüğü; kayan vurgu ve dönen sayı. Her birinin *neden* iyi olduğu §11.1'de — kopyalanmaz, aynı neden üretilir. +- **Uygulama gibi davran:** kullanıcının durumu (sıra, liste N/24, tema) her yerde yansır; her eylemin görünür cevabı var; yüzey açılırken önce imza sonra içerik; hareketler sırayla konuşur; sayfa başına tek orkestre giriş; telefonda deneyim gizlenmez. Oyunlaştırma dürüsttür (§10.2, §10.7). +- **Dışarıdan parça** (Arc, React Bits, Skiper UI, Motion Primitives) alınabilir ama **bizim renk ve biçimimize çevrilerek** (§11.3, yedi adım): kod bizim dosyamızda, yeni bağımlılık yok (hareket yalnız `motion`; `gsap`/`framer-motion` yasak), renk token'a, yay/eğri `src/lib/hareket.ts`'e, sade karşılığı ve klavye desteği yazılı, dosya başında kaynak + lisans yorumu. +- **Tek kaynaklar:** turuncu CTA `Button variant="cta"`, ikincil `variant="ikincil"` (elle `bg-orange-500` yazılmaz); çok adımlı akış `AdimGostergesi` (§4.35); telefon menüsü `MobilMenu` (§4.36); logoya tepki `logoTepkisi()` (§4.34); yay/eğri/süre `hareket.ts`. + # Karanlık mod: token'la çalışır, `dark:` yazılmaz Sitede açık / koyu / sistem teması var (DESIGN.md §2.3). Karanlık mod `src/app/globals.css` içindeki `.dark` bloğunda palet token'ları çevrilerek çalışır (slate ölçeği ters döner, renkli açık zeminler koyu tona iner); bileşenlere `dark:` sınıfı yazılmaz. Yeni UI yazarken: yüzey zemini `bg-card`'dır (`bg-white` koyu temada da beyaz kalır — yalnız logo karosu gibi gerçekten beyaz kalması gereken yerde); koyu hap (`bg-slate-900`) yazısını `text-slate-50` ile alır; SVG/satır içi stilde sabit renk yerine `var(--card)`, `var(--color-slate-200)` gibi değişken kullanılır; koyu vurgu yüzeyi (`bg-slate-950`) `kt-koyu-yuzey` sınıfını taşır. `pnpm tasarim:denetim` bunları `yuzey-beyaz`, `koyu-hap-beyaz-yazi` ve `dark-sinif` kurallarıyla yakalar. @@ -114,3 +123,7 @@ Yerine ne yapılır: - Kendi yazdığın bir dosyayı geri almak: dosyayı elle düzenle, `checkout --` kullanma. Kural ihlal edilmişse: ne kaybedildiğini `git reflog` + `git diff ` ile tespit et, **derhal raporla**, kendi başına yeniden oluşturmaya çalışmadan önce kaybın kapsamını yaz. + +# Jev / TypeSafe işlerinde skill zorunlu + +Jev'e (TypeSafe System One modeli) dokunan her iş — yeni bir tipli karar/olasılık çağrısı, mevcut prompt-parse adımını Jev'e taşıma, soru tasarımı, SDK kullanımı — başlamadan önce `typesafe:typesafe-ai` skill'i çağrılır ve skill'in işaret ettiği canlı dökümanlar (`docs.typesafe.ai/llms.txt`) okunur. Plugin proje kapsamında `.claude/settings.json` içinde etkin; yeni makinede önce `claude plugin marketplace add typesafe-ai/skills` gerekir. diff --git a/components.json b/components.json index cb51d7a..718fd80 100644 --- a/components.json +++ b/components.json @@ -23,6 +23,8 @@ }, "registries": { "@skiper-ui": "https://skiper-ui.com/registry/{name}.json", - "@magicui": "https://magicui.design/r/{name}" + "@magicui": "https://magicui.design/r/{name}", + "@uiarc": "https://uiarc.dev/r/{name}.json", + "@react-bits": "https://reactbits.dev/r/{name}.json" } } diff --git a/data/app.db b/data/app.db index 9c154e8..43b41cf 100644 Binary files a/data/app.db and b/data/app.db differ diff --git a/docs/arastirma/2026-10-07-booking-pill-sekil-hapi.md b/docs/arastirma/2026-10-07-booking-pill-sekil-hapi.md new file mode 100644 index 0000000..919e6b6 --- /dev/null +++ b/docs/arastirma/2026-10-07-booking-pill-sekil-hapi.md @@ -0,0 +1,1026 @@ +# Booking pill → şekil değiştiren hap (SekilHapi) — kaldırılan uygulama, saklanan mantık + +7 Eki 2026. Bilal Arc UI "Booking pill" (https://uiarc.dev/components/booking-pill, **Pro**) +davranışını üst çubuk hapına istedi; uygulandı, tarayıcıda doğrulandı, sonra Bilal'in kararıyla +koddan geri alındı. Bu belge mantığı ve kodu saklar; yeniden açmak için aşağıdaki iki dosya +`src/` altına koyulur ve en sondaki yama uygulanır (`git apply`). + +## 1. Demodan ölçülen davranış (Chrome, DOM + computedStyle + rAF örnekleme) + +Kaynak kodu Pro'ya kilitli, alınmadı; canlı önizleme adım adım tıklanıp ölçüldü. + +| Adım | Çerçeve (px) | İçerik | +|---|---|---| +| kapalı | 164×52 | takvim ikonu + "Book a table" | +| kişi | 300×60 | geri (44) · −/sayı rulosu/+ (36) · ileri (44), padding 8, gap 6 | +| tarih | 420×124 | bar (geri · başlık · ileri) + sürüklenen gün şeridi (slider, pan-y) | +| saat | 360×262 | başlık + alt satır · 4 sütun 81×40 radyo (kayan vurgu) · tam genişlik CTA 44 | +| bilet | 340×215 | mekân adı 22/500, adres 14, dl Date/Time/Guests, kesik koparma hattı, koçanda geri + CTA | +| rezerve | 244×56 | yeşil onay diski 32 + iki satır metin + yenile butonu 36 | + +- Yüzey: `rgba koyu /0.8` + `backdrop blur(24px) saturate(1.6)`, köşe sabit 30 px, iç 1 px halka, + dış gölge `0 22px 55px rgba(0,0,0,.34)`. Yalnız o anki yüz monte; her yüz ResizeObserver'lı. +- Geçiş (örnekleme, ms): çıkan yüz ~100 ms'de opaklık 1→0, ölçek 1→0,97, x 0→−10..−16 (gidiş + yönünün tersi), blur 0→4 px. Giren yüz ~90 ms gecikmeyle opaklık 0→1, ölçek 0,95→1, x +16→0, + blur 4→0 (~250 ms). Çerçeve genişlik/yükseklik sekmesiz yayla ~400 ms'de yeni ölçüye oturur + (340×215 → 240×50; eşzamanlı, içerik bir vuruş geriden). +- Klavye: ok tuşları şeritte gün / ızgarada saat, PgUp/PgDn hafta, Home/End, Enter onay, + Escape bir adım geri. Erişilebilirlik: şerit `role=slider` + `aria-valuetext`, saatler + `radiogroup`, adım değişimi `aria-live=polite`, hata `role=alert`. + +## 2. Bizdeki tasarım kararları + +- Genel hap mekaniği (`SekilHapi`) ile akış (`SiralamaHapi`) ayrıldı. +- Yüzey DESIGN.md dili: `rounded-3xl border-slate-200 bg-card` (kapalı 48 px hap tam yuvarlak), + gölge yalnız açıkken `shadow-[0_24px_48px_-20px_rgba(15,23,42,0.3)]`, camsı blur yok. + Yaylar `src/lib/hareket.ts`: çerçeve `YAY.donusum`, çıkış `SURE.anlik`, giriş `SURE.standart` + + `EGRI.giris`, vuruş 90 ms, kayma 16 px. Hareketi azalt: ölçü zıplar, yalnız opaklık. +- SSR tuzağı: çerçeve ilk boyamada `auto` (doğal akış), yoksa hidrasyon öncesi nav görünmez + kalırdı. İlk adım değişiminde çerçeve o anki `offsetWidth/Height`'a kilitlenir (`jump`), + yüzler mutlak konuma geçer; kapalıya dönüp yay bitince tekrar `auto` (duyarlı yerleşim). + Ölçü `offsetWidth` ile (getBoundingClientRect dönüşümü içerir, giren yüz %95 ölçekte). +- Akış eşlemesi: kişi → **puan türü** (SecimHaplari ayrık), tarih → **sıralama** (hapın içinde + kenarlıksız ham input, §4.6 istisnası), saat → yok, bilet → **bilet** (iri sayı + puan türü / + tercihler + koparma hattı + turuncu CTA), rezerve → **kaydedildi** (2 s sonra kendiliğinden + kapanır). +- Profil yoksa bilet "Tercihlere geç": hap kapanır, kapı modalı `onSira` ile açılır, facetleri + yükler ve doğrudan alan/il adımına geçer (sıra iki kez sorulmaz). Profil varsa "Kaydet": sıra+tür + yerinde yazılır, alan/il korunur, `profilTamamlandi` riskleri yeniler, `router.refresh()`. +- lg altı satır modal yolunda kaldı. Sihirbaz adımları değişmediği için meraklısına şeması + güncellenmedi. +- Doğrulama (localhost:3000, 1280 px, açık+koyu): 466→519→351→(hata 345×80)→322×191 geçişleri, + modalın Adım 1/3'te açılması, Kaydet → kaydedildi → 2 s kapanış çalıştı. tsc/lint/denetim temiz. +- Tarayıcı notu: `javascript_tool` koşarken pane arka planda sayılır, rAF durur; motion + animasyonları JS içindeki bekleyişle ilerlemez. Zamanlama için `computer.wait` kullan. + +## 3. Kod — `src/components/ui/sekil-hapi.tsx` + +```tsx +"use client"; + +// Şekil değiştiren hap: tek bir hap yüzeyi, içindeki adıma göre yayla +// yeniden biçimlenir; içerik bir vuruş sonra gidiş yönünde kayarak gelir. +// +// Kaynak: Arc UI "Booking pill" (uiarc.dev, Pro — kodu alınmadı; 7 Eki 2026'da +// canlı önizlemeden ölçüldü). Oradaki ölçüm: çerçeve genişlik/yükseklik +// ~0,4 s'lik sekmesiz yayla yeni yüzün ölçüsüne gider; çıkan yüz ~0,1 s'de +// söner (opaklık 0, %97 ölçek, 16 px geriye, 4 px blur), giren yüz ~90 ms +// gecikmeyle karşı taraftan aynı yoldan gelir; yalnız o anki yüz monte, çıkan +// yüz çıkış süresince üstte kalır. Bizdeki farklar: yay `YAY.donusum`, süreler +// `SURE`, eğri `EGRI` (src/lib/hareket.ts); koyu camsı yüzey yerine +// `border-slate-200 bg-card` hapı; 30 px köşe yerine `rounded-3xl` (kapalı +// 48 px hap tam yuvarlak kalır); gölge yalnız açıkken. Hareketi azalt'ta ölçü +// zıplar, yüzler yalnız opaklıkla değişir. +// +// Çalışma biçimi: ilk boyamada (SSR dâhil) hap doğal akışta ölçülür, çerçeve +// `auto`'dur — hidrasyon öncesi görünmez kalmaz. İlk adım değişiminde çerçeve +// o anki ölçüsüne kilitlenir, yüzler mutlak konuma geçer ve çerçeve yeni +// yüzün ölçüsüne yaylanır. Açık yüz büyüyüp küçülürse (hata satırı) çerçeve +// onu da izler (ResizeObserver). İlk adıma dönülüp hareket bitince doğal +// akışa geri döner ki üst çubuk duyarlı yerleşimini korusun. + +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, + type KeyboardEvent, + type ReactNode, +} from "react"; +import { + AnimatePresence, + animate, + motion, + useMotionValue, + useReducedMotion, + type Variants, +} from "motion/react"; +import { cn } from "@/lib/utils"; +import { EGRI, SURE, YAY } from "@/lib/hareket"; + +/** Yüzün gidiş yönünde kaydığı mesafe (px). */ +const KAYMA = 16; +/** Giren yüzün çerçeveden sonra başladığı vuruş (s). */ +const VURUS = 0.09; + +export interface SekilHapiProps { + /** Görünen adımın anahtarı; değişince yüz değişir. */ + adim: string; + /** Kapalı adım: burada çerçeve doğal akışa döner, dış tıklama dinlenmez. */ + ilkAdim: string; + /** +1 ileri, -1 geri — içerik bu yönde kayar. */ + yon: 1 | -1; + /** Ekran okuyucuya adım değişimini söyleyen cümle. */ + duyuru?: string; + /** Escape: bir adım geri. */ + onGeri?: () => void; + /** Açıkken hap dışına basılınca. */ + onDisariTiklama?: () => void; + /** Hapın ekran okuyucu adı. */ + etiket: string; + className?: string; + /** O anki adımın yüzü; `data-odak` taşıyan öğe yüz gelince odaklanır. */ + children: ReactNode; +} + +export function SekilHapi({ + adim, + ilkAdim, + yon, + duyuru, + onGeri, + onDisariTiklama, + etiket, + className, + children, +}: SekilHapiProps) { + const azalt = useReducedMotion(); + const cerceveRef = useRef(null); + // Doğal akış (false) ↔ ölçülü çerçeve (true). Ref, efektlerin bayat + // kapanış okumaması için state'in aynası. + const [olculu, setOlculu] = useState(false); + const olculuRef = useRef(false); + const adimRef = useRef(adim); + useLayoutEffect(() => { + adimRef.current = adim; + }, [adim]); + const genislik = useMotionValue("auto"); + const yukseklik = useMotionValue("auto"); + const acik = adim !== ilkAdim; + + /** Çerçeveyi (kenarlık dâhil) yüz ölçüsüne götürür. */ + const hedefeGit = useCallback( + (w: number, h: number) => { + const cerceve = cerceveRef.current; + if (!cerceve || !olculuRef.current) return; + const ekW = cerceve.offsetWidth - cerceve.clientWidth; + const ekH = cerceve.offsetHeight - cerceve.clientHeight; + const gecis = azalt ? { duration: 0 } : YAY.donusum; + const hareket = Promise.all([ + animate(genislik, w + ekW, gecis), + animate(yukseklik, h + ekH, gecis), + ]); + void hareket.then(() => { + // Kapalı adıma dönüş bitti: doğal akışa geri (duyarlı yerleşim). + if (adimRef.current !== ilkAdim || !olculuRef.current) return; + olculuRef.current = false; + genislik.set("auto"); + yukseklik.set("auto"); + setOlculu(false); + }); + }, + [azalt, genislik, yukseklik, ilkAdim], + ); + + /** Yeni yüz monte oldu: çerçeveyi olduğu ölçüye kilitle, sonra hedefe yaylan. */ + const gecisBaslat = useCallback( + (w: number, h: number) => { + const cerceve = cerceveRef.current; + if (!cerceve) return; + if (!olculuRef.current) { + olculuRef.current = true; + genislik.jump(cerceve.offsetWidth); + yukseklik.jump(cerceve.offsetHeight); + setOlculu(true); + } + hedefeGit(w, h); + }, + [genislik, yukseklik, hedefeGit], + ); + + useEffect(() => { + if (!acik || !onDisariTiklama) return; + function dinle(olay: PointerEvent) { + const cerceve = cerceveRef.current; + if (cerceve && !cerceve.contains(olay.target as Node)) onDisariTiklama?.(); + } + document.addEventListener("pointerdown", dinle); + return () => document.removeEventListener("pointerdown", dinle); + }, [acik, onDisariTiklama]); + + function tusBasildi(olay: KeyboardEvent) { + if (olay.key !== "Escape" || !onGeri || !acik) return; + olay.preventDefault(); + olay.stopPropagation(); + onGeri(); + } + + return ( +
+ + + + {children} + + + +

+ {duyuru} +

+
+ ); +} + +const YUZ_VARYANT: Variants = { + giris: (yon: 1 | -1) => ({ + opacity: 0, + x: KAYMA * yon, + scale: 0.95, + filter: "blur(4px)", + }), + orta: { + opacity: 1, + x: 0, + scale: 1, + filter: "blur(0px)", + transition: { delay: VURUS, duration: SURE.standart, ease: EGRI.giris }, + }, + cikis: (yon: 1 | -1) => ({ + opacity: 0, + x: -KAYMA * yon, + scale: 0.97, + filter: "blur(4px)", + transition: { duration: SURE.anlik, ease: EGRI.standart }, + }), +}; + +const YUZ_VARYANT_AZALT: Variants = { + giris: { opacity: 0 }, + orta: { opacity: 1, transition: { duration: SURE.hizli } }, + cikis: { opacity: 0, transition: { duration: SURE.hizli } }, +}; + +function Yuz({ + dogal, + azalt, + onGecis, + onOlcu, + children, +}: { + /** Doğal akıştaki ilk yüz: çerçeveyi kilitlemez, ölçü bildirmez. */ + dogal: boolean; + azalt: boolean; + onGecis: (w: number, h: number) => void; + onOlcu: (w: number, h: number) => void; + children: ReactNode; +}) { + const ref = useRef(null); + + // Monte: ölç (dönüşümsüz offset ölçüsü), çerçeveyi başlat, odağı ver; + // sonra yüzün kendi boyut değişimlerini izle (hata satırı açılır vb.). + useLayoutEffect(() => { + const el = ref.current; + if (!el) return; + if (!dogal) onGecis(el.offsetWidth, el.offsetHeight); + el.querySelector("[data-odak]")?.focus({ preventScroll: true }); + const gozlemci = new ResizeObserver(() => { + onOlcu(el.offsetWidth, el.offsetHeight); + }); + gozlemci.observe(el); + return () => gozlemci.disconnect(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- yalnız mount + }, []); + + return ( + + {children} + + ); +} +``` + +## 4. Kod — `src/features/sihirbaz/components/siralama-hapi.tsx` + +```tsx +"use client"; + +// Üst çubuğun ortasındaki gezinme hapı (lg+): kapalıyken "Sıralamanı gir" +// (ya da kayıtlı sıra özeti) + "Nasıl çalışır?" + Ara; dokununca AYNI hap +// adım adım yeniden biçimlenir — puan türü → başarı sıralaması → bilet +// (özet) → kaydedildi. Arc UI "Booking pill" akışının (kişi → tarih → saat → +// bilet → rezerve) bizdeki karşılığı; hap mekaniği SekilHapi'de (DESIGN.md +// §4.33), bu dosya yalnız yüzleri ve akışı bilir. +// +// İki yol: +// - Profil yok: bilet "Tercihlere geç" der → hap kapanır, tercih profili +// kapısı (modal) sıra+türü hazır alır, facetleri yükler ve doğrudan alan/il +// adımıyla açılır (`tercihlereGec`). Sıra adımı modalda tekrar sorulmaz. +// - Profil var: bilet "Kaydet" der → profil yerinde yazılır (alan/il +// seçimleri korunur), riskler yenilenir, "kaydedildi" yüzü bir vuruş durup +// kapanır. Alan/il de değişecekse bilet üstündeki bağlantı modalı açar. +// lg altındaki ikinci satır bu hapı kullanmaz (modal yolu, katalog-arama.tsx). + +import { useEffect, useEffectEvent, useState, type ReactNode } from "react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { + ArrowLeft, + ArrowRight, + Check, + SquarePen, + Trophy, + X, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { SecimHaplari } from "@/components/ui/secim-haplari"; +import { SekilHapi } from "@/components/ui/sekil-hapi"; +import { preloadTercihProfiliKapisi } from "@/features/liste/components/tercih-profili-kapisi-lazy"; +import { + profilDuzenlemeyiAc, + profilTamamlandi, + tercihlereGec, + useTercihProfili, +} from "@/features/liste/hooks/use-tercih-profili"; +import { PUAN_TURU_ETIKET, type TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler"; +import { tercihProfiliYaz } from "@/features/sihirbaz/sihirbaz-profil"; +import { olay, siraKovasi } from "@/lib/analitik"; +import { cn } from "@/lib/utils"; + +const PUAN_TURLERI_SECENEK = [ + { deger: "say", etiket: "Sayısal" }, + { deger: "ea", etiket: "Eşit Ağırlık" }, + { deger: "soz", etiket: "Sözel" }, + { deger: "dil", etiket: "Dil" }, + { deger: "tyt", etiket: "TYT" }, +] as const; +type PuanTuru = (typeof PUAN_TURLERI_SECENEK)[number]["deger"]; + +function puanTuruMu(deger: string): deger is PuanTuru { + return PUAN_TURLERI_SECENEK.some((s) => s.deger === deger); +} + +type Adim = "kapali" | "tur" | "sira" | "bilet" | "kaydedildi"; +const SIRA: Record = { + kapali: 0, + tur: 1, + sira: 2, + bilet: 3, + kaydedildi: 4, +}; + +/** Kaydedildi yüzünün ekranda kaldığı süre (ms); sonra hap kendiliğinden kapanır. */ +const KAYDEDILDI_BEKLEME = 2000; + +const YUVARLAK = + "flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full transition-[background-color,transform] duration-150 focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none active:scale-[0.96]"; +const GERI = cn(YUVARLAK, "bg-slate-100 text-slate-700 hover:bg-slate-200"); +const ILERI = cn(YUVARLAK, "bg-slate-900 text-slate-50 hover:bg-slate-700"); + +export function SiralamaHapi({ araButonu }: { araButonu: ReactNode }) { + const router = useRouter(); + const profil = useTercihProfili(); + const [adim, setAdim] = useState("kapali"); + const [yon, setYon] = useState<1 | -1>(1); + const [tur, setTur] = useState("say"); + const [siralama, setSiralama] = useState(""); + const [hata, setHata] = useState(null); + const siraDeger = Number(siralama.replace(/\./g, "")); + const turEtiketi = PUAN_TURU_ETIKET[tur] ?? tur; + + function adimaGec(yeni: Adim) { + setYon(SIRA[yeni] > SIRA[adim] ? 1 : -1); + setAdim(yeni); + } + + function ac() { + preloadTercihProfiliKapisi(); + setHata(null); + setTur(profil && puanTuruMu(profil.tur) ? profil.tur : "say"); + setSiralama(profil ? profil.sira.toLocaleString("tr-TR") : ""); + adimaGec("tur"); + } + + function kapat() { + setHata(null); + adimaGec("kapali"); + } + + function geri() { + if (adim === "sira") adimaGec("tur"); + else if (adim === "bilet") adimaGec("sira"); + else kapat(); + } + + function siralamaDegisti(ham: string) { + setHata(null); + const rakamlar = ham.replace(/\D/g, "").slice(0, 7); + setSiralama(rakamlar ? Number(rakamlar).toLocaleString("tr-TR") : ""); + } + + function siraGonder(e: React.FormEvent) { + e.preventDefault(); + if (!siraDeger || siraDeger < 1 || siraDeger > 4_000_000) { + setHata("Geçerli bir başarı sıralaması gir (ör. 85.000)."); + return; + } + setHata(null); + adimaGec("bilet"); + } + + /** Profil yok: sıra+tür kapıya verilir, kapı facetleri yükleyip alan/il adımıyla açılır. */ + function tercihlereDevam() { + kapat(); + tercihlereGec({ sira: siraDeger, tur }); + } + + /** Profil var: sıra+tür yerinde yazılır, alan/il seçimleri korunur. */ + function kaydet() { + if (!profil) return; + const yeni: TercihProfili = { sira: siraDeger, tur, secimler: profil.secimler }; + tercihProfiliYaz(yeni); + // Depodaki profili tazeler, listedeki riskleri yeni sıraya göre yeniler. + profilTamamlandi(yeni); + olay("sira_girildi", { + kaynak: "duzenle", + tur, + sira_kovasi: siraKovasi(siraDeger), + }); + // /sonuc tablosu profil çerezinden SSR'lanır; URL değişmediği için tazele. + router.refresh(); + adimaGec("kaydedildi"); + } + + function tercihleriDeDegistir() { + kapat(); + profilDuzenlemeyiAc(); + } + + const kendiligindenKapat = useEffectEvent(kapat); + useEffect(() => { + if (adim !== "kaydedildi") return; + const zamanlayici = window.setTimeout(kendiligindenKapat, KAYDEDILDI_BEKLEME); + return () => window.clearTimeout(zamanlayici); + }, [adim]); + + const duyuru: Record = { + kapali: "", + tur: "Puan türünü seç.", + sira: `${turEtiketi} başarı sıralamanı yaz.`, + bilet: `Özet: ${siraDeger.toLocaleString("tr-TR")} · ${turEtiketi}. ${profil ? "Kaydet" : "Tercihlere geç"}.`, + kaydedildi: `Sıralaman güncellendi: ${siraDeger.toLocaleString("tr-TR")} · ${turEtiketi}.`, + }; + + return ( + + {adim === "kapali" ? ( +
+ {profil ? ( + + ) : ( + <> + + + Nasıl çalışır? + + + )} + {araButonu} +
+ ) : null} + + {adim === "tur" ? ( +
+ + + Puan türün + + + +
+ ) : null} + + {adim === "sira" ? ( +
+
+ + {/* Hap kabının içinde kenarlıksız ham alan (DESIGN.md §4.6 istisnası): + hapın kendisi form alanıdır, ikinci bir hap çizilmez. */} + + + · {turEtiketi} + + +
+ {hata ? ( + + ) : null} +
+ ) : null} + + {adim === "bilet" ? ( +
+
+
+
+

+ Başarı sıralaman +

+

+ {siraDeger.toLocaleString("tr-TR")} +

+
+ +
+
+
+
Puan türü
+
{turEtiketi}
+
+
+
Tercihler
+
+ {profil + ? `${profil.secimler.kategoriler.length} alan · ${ + profil.secimler.iller.length + ? `${profil.secimler.iller.length} il` + : "her il" + }` + : "Sıradaki adım"} +
+
+
+ {profil ? ( + + ) : null} +
+
+ + +
+
+ ) : null} + + {adim === "kaydedildi" ? ( +
+ + + + + + Sıralaman güncellendi + + + {siraDeger.toLocaleString("tr-TR")} · {turEtiketi} + + + {/* Kapatma çarpısı DialogContent varsayılanıyla aynı sınıf seti (§4.3). */} + +
+ ) : null} +
+ ); +} +``` + +## 5. Bağlantı yaması (nav, kapı deposu, kapı modalı, DESIGN.md §4.33–4.34) + +`git apply` ile uygulanır (HEAD `bc920f6` tabanlı; çakışırsa hunk'lar elle taşınır). DESIGN.md bölümleri §6'da. + +```diff +diff --git a/src/features/katalog/components/katalog-arama.tsx b/src/features/katalog/components/katalog-arama.tsx +index 35aad40..64265bd 100644 +--- a/src/features/katalog/components/katalog-arama.tsx ++++ b/src/features/katalog/components/katalog-arama.tsx +@@ -25,6 +25,7 @@ import { + } from "@/features/liste/hooks/use-tercih-profili"; + import { preloadTercihProfiliKapisi } from "@/features/liste/components/tercih-profili-kapisi-lazy"; + import { PUAN_TURU_ETIKET } from "@/features/sihirbaz/sihirbaz-sabitler"; ++import { SiralamaHapi } from "@/features/sihirbaz/components/siralama-hapi"; + import { olay } from "@/lib/analitik"; + import { + ArrowRight, +@@ -266,69 +267,31 @@ export function KatalogArama({ rehberler }: { rehberler: RehberAramaKaydi[] }) { + + return ( + <> +- {/* lg altında logo + sağ küme ile çakışır (absolute); o aralıkta gizli */} +-