diff --git a/AGENTS.md b/AGENTS.md index ecb0528..63ea981 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,11 @@ This version has breaking changes — APIs, conventions, and file structure may **İstisna: `tasarimci` ve UX işi.** Tasarımcı, gerekli gördüğünde önizlemeyi/dev sunucusunu **açabilir**. Bazı tasarım sorunları koddan teşhis edilemez — taşma, üst üste binme, kırılma noktaları, gerçek metin uzunluğuyla oluşan yerleşim, karanlık mod, odak halkası, dokunma hedefi. Bunlarda "koddan çıkarım" yetmez; bakmak gerekir. +**Zorunlu: yeni UI parçası tasarlayan herkes bakar (9 Eki 2026).** Yukarıdaki "açma" varsayılanı küçük düzeltmeler içindir (metin, renk, tek sınıf değişikliği). İş **yeni bir bileşen/bölüm tasarlamak ya da mevcut bir bölümü yeniden düzenlemek** ise — rol ne olursa olsun, ana oturum dahil — sonuç ekranda görülmeden teslim edilmez. 9 Ekim'de sürüm notlarındaki geri bildirim formu bakılmadan teslim edildi; DESIGN.md sınıfları birebir doğruydu ama sonuç kötüydü (kutu içinde kutu, sayfa başlıklarının yanında cılız başlık, soluk küçük buton) ve Bilal uyarmak zorunda kaldı. Sınıfların doğru olması kompozisyonun iyi olduğunu göstermez; oran ve hiyerarşi koddan görünmez. Yapılacaklar: +- Önce Bilal'in açık dev sunucusu var mı bak (`lsof -iTCP -sTCP:LISTEN -P -n | grep node`); varsa onu kullan, yeni sunucu açma ve onunkini kapatma. Yoksa aç, iş bitince kapat. +- **375 px ve masaüstünde** ekran görüntüsü al; ilk hâli beğenmediysen düzelt, tekrar bak. Raporda hangi genişlikte ne gördüğünü ve neye bakmadığını (ör. açık tema) yaz. +- Yazmadan önce parçayı **sayfanın geri kalanıyla** ölç: başlık o sayfadaki kardeş başlıklarla aynı ölçekte olur; kart içine kenarlıklı alan konmaz (kutu içinde kutu) — form için tek yüzey: kenarlıksız `Textarea` + buton aynı yüzeyin içinde (sohbet kutusu kalıbı, örnek `src/features/geri-bildirim/components/geri-bildirim-formu.tsx`); aksiyon butonu kanonik turuncu CTA'dır (`size="lg"`), soluk `h-9` değil. + Açtığında geçerli olanlar: - **Gerekçesini yaz:** neyi koddan göremediğin için açtın. - **Ne gördüğünü yaz**, hangi genişlikte (375 / 768 / 1024 / 1280 px) ne olduğunu tek tek. Ekran görüntüsü alabilirsin. diff --git a/docs/tasarim/DESIGN.md b/docs/tasarim/DESIGN.md index 5386898..c449702 100644 --- a/docs/tasarim/DESIGN.md +++ b/docs/tasarim/DESIGN.md @@ -133,6 +133,22 @@ Seçici `src/components/tema-secici.tsx` (footer'da), sağlayıcı `src/componen (`next-themes`, ``). Renklerin tek kaynağı `src/app/globals.css` içindeki `.dark` bloğu. +**Geçiş ve kısayol (7 Eki 2026; son hâli 9 Eki):** tema değişimi anlık değildir; yeni +tema header'daki logodan **yumuşak bir halka** gibi açılır: kenarı keskin değil, geniş ve +tüylü (ışık yayılır gibi), kısa ve hafif: 600 ms. **Geçiş bitince** logonun kareleri +yeni temanın biçimine dönüşür: koyuya geçişte şeftali kare hilale, mavi kare hilalin iki ucunun +ortasındaki yıldıza (turuncu söner); açığa geçişte turuncu kare güneşe, şeftali kare halesine. Kısa bir an durur, yeniden +logoya toplanır (`animasyonlu-mark.tsx` `temaSekli`, 1,7 sn; yalnız header'daki logo; hover döngüsünden +önceliklidir, biçim bitince imleç üzerindeyse döngü kaldığı yerden değil yerinden sürer). Tema +seçicinin ikonları aynı biçimlerin durağan hâlidir (ayrıntı ve kurallar §4.34) (`tema-ikonu.tsx`: güneş, ay, Sistem için +logonun üç karesi); burada lucide ikonu kullanılmaz. View Transitions + radyal degrade +maskesi: `src/lib/tema-gecisi.ts`, `globals.css` `.kt-tema-gecis`; hareket azaltılmışsa düz +değişir. Denenip bırakılanlar: logo biçimli maske ("logo içinde logo"), düz crossfade, piksel +ızgarası (sert bulundu). Kural: bu geçişte sert kenar ve ikinci bir şekil yok. +`D` tuşu açık ↔ koyu geçişi yapar (`src/components/tema-kisayolu.tsx`); ipucu seçicinin +altında `Kbd` ile durur, başka yere kopyalanmaz. Header'daki Beta rozeti `/surum-notlari`na +bağlantıdır. + **Nasıl çalışır:** bileşenlere `dark:` yazılmaz; palet token'ları çevrilir. | Açık temadaki sınıf | Koyu temada ne olur | @@ -698,6 +714,43 @@ genişletilebilir olmalı; UI için prompt-kit kullan." zaman yazılıdır (§2.2). Sohbet ilerlese de kalır. Yeni bir liste uyarısı gerekince `listeUyarilari`'na eklenir, sayfaya yeni kutu açılmaz. +### 4.34 Logo biçim dönüşümü (morph) — `AnimasyonluMark` `temaSekli`, `tema-ikonu.tsx` + +(Numara 4.34: `v2` dalındaki belge 4.21–4.33'ü kullanıyor; iki dal birleşince çakışmasın.) + +Bilal'in kararı (9 Eki 2026): logonun üç karesi yalnız süs değildir; **bir durumu anlatmak +için kısa bir an başka bir biçime dönüşüp geri toplanabilir.** İlk ve şimdilik tek kullanım +tema değişimidir (§2.3). Bu, markanın yumuşak ve oyunsu tarafının imza hareketidir; piksel +imzası (§1) durağan dokuysa bu onun canlı karşılığıdır. + +**Biçimler** (180'lik logo kutusunda; renkler logonun kendi renkleri, iki temada aynı): + +| Durum | Mavi kare | Şeftali kare | Turuncu kare | +|---|---|---|---| +| Açık tema: **güneş** | merkezde söner | hale: büyük, yarı saydam daire | güneş: küçük dolu daire | +| Koyu tema: **ay** | yıldız: hilalin iki sivri ucunun tam ortasında | hilal: sırtı solda | yıldızın yerinde söner | +| Sistem (yalnız ikon) | logo karesi | logo karesi | logo karesi | + +Simetri kasıtlıdır: güneşte mavi, ayda turuncu sahneden çekilir. Hilal, kareyi kesen ikinci +bir şekille değil karenin kendi iç gölgesiyle çizilir (zemin rengine bağlı kalmaz). + +**Koreografi:** toplam 1,7 sn — %30'da biçim tamam, %70'e kadar durur, sonra logoya döner. +Kareler yuvarlanırken yer değiştirir ve boy değiştirir; sekme yok (§6). Sayfa geçişiyle +**üst üste binmez**: önce sayfa geçişi biter (600 ms), kısa bir nefes, sonra logo dönüşür. +Hover döngüsünden önceliklidir; biçim bitince imleç hâlâ üzerindeyse hover yerinden sürer. +"Hareketi azalt" açıksa dönüşüm oynamaz. + +**Durağan hâli:** tema seçicinin ikonları aynı biçimlerdir (`tema-ikonu.tsx`). Logo ile ikon +aynı geometriyi kullanır; biri değişirse öbürü de aynı commit'te değişir. + +**Kurallar:** +- Dönüşüm yalnız header'daki logoda oynar; aynı ekranda iki logo birden dönüşmez. +- Bir **durum değişimini** anlatır (tema gibi), dekor olarak döngüye alınmaz, sayfa + açılışında oynatılmaz. +- Yeni bir biçim eklenecekse üç kareden ve logonun üç renginden çıkar; dördüncü şekil, + yeni renk, çizgi ikon eklenmez. Her biçimin durağan ikon karşılığı da yazılır. +- Biçim logonun kendi alanına sığar (x 27–151): sağındaki "KolayTercih" yazısına değmez. + ## 5. Yerleşim ### 5.1 Genel @@ -767,6 +820,8 @@ Hareket kuralları (Apple, `motion/react`): - Kaydırmaya bağlı tetik `IntersectionObserver` ile (kaydırma dinleyicisi değil). - "Hareketi azalt": kayma/yay yok, yalnız opaklık (`useReducedMotion`, `motion-reduce:transition-opacity`). +- Logo kareleri bir durumu anlatmak için kısa bir an başka biçime dönüşebilir (§4.34); sayfa + geçişiyle üst üste binmez, sırayla oynar. ## 7. Yap / Yapma @@ -784,6 +839,7 @@ Hareket kuralları (Apple, `motion/react`): | Gösterge kareleri masonry (kaymış sütun, farklı boy) | Hizalı eşit ızgara; 8 px'lik "neredeyse hizalı" kaymalar | | Liste satırında ad sarar, sayı alt satıra iner | `truncate` ile yarım ad; 32 px satır butonu | | Hareket: `Reveal`, piksel ısısı, `kt-unblur`, sekmesiz yay; `animate-ping` yalnızca "şu anda canlı" noktasında | Her elemanda `fade-up`, `whileHover scale 1.05`, `animate-bounce` | +| Tema ikonu ve logo dönüşümü: logonun üç karesinden güneş / ay (§4.34) | Tema seçicide lucide `Sun`/`Moon`; logoyu döngüde sürekli dönüştürmek | | Başlık `font-heading`, vurgu `font-bricolage` | Inter/Roboto/serif, tek kelimelik BÜYÜK HARF etiket, 10–11 px yazı | | Risk rengi + metin etiketi; tür ayrımı ikonla | Renk tek başına, "yerleşirsin" gibi vaat cümlesi | | Üç kart yalnızca üç gerçek öğe varsa | Hero + üç kart iskeleti refleksi | diff --git a/package.json b/package.json index dac4e01..612b001 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kolaytercih", - "version": "1.0.0", + "version": "1.1.0", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/globals.css b/src/app/globals.css index 12f3f2f..8a63b82 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -476,3 +476,47 @@ .toaster [data-sonner-toast][data-styled="true"] [data-button]:hover { background: var(--color-slate-700); } + +/* Tema geçişi (DESIGN.md §2.3, lib/tema-gecisi.ts): yeni tema header'daki + logodan yumuşak bir halka gibi açılır. Eski görüntü yerinde durur; yeni + görüntü radyal degrade maskesiyle üstünde belirir. Maskenin kenarı keskin + değil: yarıçapın %55'ine kadar tam opak, oradan kenara doğru tüy gibi + söner (--kt-tema-cekirdek; JS'teki CEKIRDEK ile aynı olmalı) — ışık + yayılır gibi, sert çizgi yok. Yarıçap (--kt-tema-r) 0 → --kt-tema-son + yumuşak kalkıp yumuşak durur; kısa ve hafif (600 ms) — asıl gösteri, + geçiş bitince oynayan logo dönüşümüdür. Merkez ve son yarıçap JS'ten gelir. + Hareket azaltılmışsa JS geçişi hiç başlatmaz, tema düz değişir. */ +@property --kt-tema-r { + syntax: ""; + inherits: true; + initial-value: 0px; +} +.kt-tema-gecis { + view-transition-name: root; + --kt-tema-cekirdek: 0.55; +} +.kt-tema-gecis::view-transition-old(root), +.kt-tema-gecis::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} +.kt-tema-gecis::view-transition-old(root) { + z-index: 1; +} +.kt-tema-gecis::view-transition-new(root) { + z-index: 2; + mask-image: radial-gradient( + circle at var(--kt-tema-x) var(--kt-tema-y), + #000 calc(var(--kt-tema-r) * var(--kt-tema-cekirdek)), + transparent var(--kt-tema-r) + ); + animation: kt-tema-halka 600ms cubic-bezier(0.4, 0, 0.2, 1) both; +} +@keyframes kt-tema-halka { + from { + --kt-tema-r: 0px; + } + to { + --kt-tema-r: var(--kt-tema-son); + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ed58074..dcf8564 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -117,7 +117,7 @@ export default function RootLayout({
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx index fd564b1..02e49de 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -58,11 +58,9 @@ import { import type { Metadata } from "next"; import { JsonLd, - KATALOG_VERI_TARIHI, faqPageJsonLd, softwareApplicationJsonLd, } from "@/lib/seo"; -import { KATALOG_TARIH_METNI } from "@/features/katalog/components/veri-kaynagi"; import { SiteFooter } from "@/components/site-footer"; export const metadata: Metadata = { @@ -257,27 +255,6 @@ export default function Home() { > -
- - - Veri: YÖK Atlas - {" "} - · son güncelleme{" "} - - -
- {/* Markayı tek cümlede tanımlayan görünür metin: sitedeki SSS ve - /kosullar ifadelerinin toplamı, yeni vaat yok. Meta/OG'de zaten - var; yapay zekâ cevap motorları gövde metnini önceliklendirdiği - için burada da durur (docs/arastirma/2026-09-24-geo-seo). */} -

- KolayTercih, YÖK Atlas'ın resmî yerleştirme verisiyle - sıralamana uygun 24 tercihlik dengeli liste kuran, yapay zekâ - destekli bir karar destek aracıdır. ÖSYM ya da YÖK ile bağlantılı - değildir; yerleşme garantisi vermez. -

diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index edb57ac..c0133dd 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -43,6 +43,7 @@ const staticEntries: MetadataRoute.Sitemap = [ { url: `${SITE_URL}/paket`, priority: 0.7, changeFrequency: "monthly" }, { url: `${SITE_URL}/meraklisina`, priority: 0.6, changeFrequency: "monthly" }, { url: `${SITE_URL}/iletisim`, priority: 0.3, changeFrequency: "yearly" }, + { url: `${SITE_URL}/surum-notlari`, priority: 0.3, changeFrequency: "monthly" }, { url: `${SITE_URL}/gizlilik`, priority: 0.2, changeFrequency: "yearly" }, { url: `${SITE_URL}/cerez-politikasi`, diff --git a/src/app/surum-notlari/page.tsx b/src/app/surum-notlari/page.tsx new file mode 100644 index 0000000..8b0282f --- /dev/null +++ b/src/app/surum-notlari/page.tsx @@ -0,0 +1,114 @@ +import type { Metadata } from "next"; +import { ArayuzBilgisi } from "@/components/arayuz-bilgisi"; +import { Parallax } from "@/components/parallax"; +import { GeriBildirimFormu } from "@/features/geri-bildirim/components/geri-bildirim-formu"; +import { SiteFooter } from "@/components/site-footer"; +import { SURUM } from "@/lib/surum"; +import { sayfaMetadata } from "@/lib/seo"; + +export const metadata: Metadata = sayfaMetadata({ + title: "Sürüm notları", + description: + "KolayTercih beta sürüm notları: neler değişti ve geri bildirimini nasıl iletirsin.", + path: "/surum-notlari", +}); + +/** + * Sürüm günlüğü. En yeni en üstte; her sürüm package.json'daki numarayla + * aynı adı taşır (AGENTS.md "Sürümleme"). Yeni bir sürüm çıkarken buraya bir + * kayıt eklenir. Yalnız görünüşü etkileyen değişiklikler `arayuz` alanına + * yazılır: listede tek satır ("Arayüz iyileştirmeleri") olur, ayrıntısı + * yanındaki bilgi düğmesinin balonunda durur. + */ +const SURUMLER: { + surum: string; + tarih: string; + maddeler: string[]; + arayuz?: string[]; +}[] = [ + { + surum: "1.1.0", + tarih: "7 Ekim 2026", + maddeler: [], + arayuz: [ + "D tuşu açık / koyu temayı değiştirir; geçiş logodan yayılarak yumuşak oynar.", + "Bu sayfa: beta durumu, sürüm günlüğü ve geri bildirim kutusu. Header'daki Beta rozeti buraya getirir.", + ], + }, + { + surum: "1.0.0", + tarih: "7 Ekim 2026", + maddeler: [ + "Sıralama, alan, il ve devlet/vakıf tercihine göre 24 tercihlik dengeli liste: hayal, dengeli ve güvenli dilimler.", + "Her program için YÖK Atlas verisinden taban sıralama, kontenjan ve yıllara göre eğilim.", + "Yapay zekâ destekli tercih raporu ve liste üzerinde soru sorabildiğin danışman sohbeti.", + "Üniversite ve bölüm kataloğu, üniversite sıralamaları, rehber yazıları.", + ], + arayuz: ["Açık, koyu ve sistem teması."], + }, +]; + +export default function SurumNotlariPage() { + return ( + <> +
+ +

Sürüm notları

+

+ KolayTercih şu an beta sürümünde. Ürün çalışıyor ve gerçek YÖK + Atlas verisiyle liste üretiyor; ama hâlâ her gün bir şeyler + düzeltiyor, eksik gördüğümüz yerleri tamamlıyoruz. Her sürümde + neyin değiştiği aşağıda. +

+
+ + {/* Zaman çizgisi: her sürümün piksel karesi solda durur, kareleri + ince bir çizgi birbirine bağlar (karenin ortasından bir sonraki + karenin ortasına). En eski sürümde çizgi biter. */} +
    + {SURUMLER.map((s, i) => ( +
  1. + {i < SURUMLER.length - 1 && ( + + )} + +
    +

    + v{s.surum} +

    + + {s.surum === SURUM && ( + + Şu anki sürüm + + )} +
    +
      + {s.maddeler.map((m) => ( +
    • {m}
    • + ))} + {s.arayuz && ( +
    • + + Arayüz iyileştirmeleri + + +
    • + )} +
    +
  2. + ))} +
+ + +
+ + + ); +} diff --git a/src/components/animasyonlu-mark.tsx b/src/components/animasyonlu-mark.tsx index 406e12a..4e40759 100644 --- a/src/components/animasyonlu-mark.tsx +++ b/src/components/animasyonlu-mark.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useRef } from "react"; +import { TEMA_GECIS_OLAYI } from "@/lib/tema-gecisi"; import { cn } from "@/lib/utils"; // public/kolay-tercih-mark.svg'nin inline, animasyonlu karşılığı. @@ -97,6 +98,51 @@ function yer(pozisyon: number, boyut: number): string { return `${(x / boyut) * 100}% ${(y / boyut) * 100}%`; } +/** Karenin merkezini 180'lik kutuda (cx, cy) noktasına taşıyan `translate`. */ +function merkezde(cx: number, cy: number, boyut: number): string { + return `${((cx - boyut / 2) / boyut) * 100}% ${((cy - boyut / 2) / boyut) * 100}%`; +} + +// Tema geçişi biçimi (DESIGN.md §2.3): sayfa geçişi bitince kareler +// neye geçildiğini gösterir, kısa bir an durur ve yeniden logoya toplanır. +// - Koyuya: şeftali kare hilale, mavi kare hilalin kucağındaki yıldıza; +// turuncu söner. +// - Açığa: turuncu kare güneşe, şeftali kare arkasındaki haleye; mavi söner. +// Toplam süre ve duraklar: %30'da biçim tamam, %70'e kadar durur, sonra döner. +const SEKIL_SURE = 1700; +const SEKIL_DURAKLAR = [0, 0.3, 0.7, 1]; +type SekilHedefi = { translate: string; scale: string; opacity: number }; + +// Ay kompozisyonu kutunun merkezine (90) göre küçültülür ve logonun alanına +// (x 27–151) oturacak kadar kaydırılır. +const AY_OLCEK = 0.82; +const AY_KAYMA_X = 8; +function ay(nokta: number, kayma = AY_KAYMA_X): number { + return 90 + (nokta - 90) * AY_OLCEK + kayma; +} + +/** Kare `i` için hedef biçim. Kareler sırayla mavi, şeftali, turuncu. */ +function sekilHedefi(i: number, koyu: boolean): SekilHedefi { + const b = KARELER[i].boyut; + if (koyu) { + return [ + // Biçim tema-ikonu.tsx'teki ay ikonuyla aynı: gövde r74 @ (76,92), + // tek yıldız r13 @ (108,79) — hilalin iki sivri ucunun tam ortası (iki + // dairenin kesişim kirişinin orta noktası). Turuncu kare yıldızın + // yerinde söner: güneşte mavi, ayda turuncu sahneden çekilir. `ay` + // kompozisyonu logonun kendi alanına sığdırır. + { translate: merkezde(ay(108), ay(79, 0), b), scale: String((26 * AY_OLCEK) / b), opacity: 1 }, // yıldız + { translate: merkezde(ay(76), ay(92, 0), b), scale: String((148 * AY_OLCEK) / b), opacity: 1 }, // hilal + { translate: merkezde(ay(108), ay(79, 0), b), scale: "0.2", opacity: 0 }, // söner + ][i]; + } + return [ + { translate: merkezde(90, 90, b), scale: "0.3", opacity: 0 }, // söner + { translate: merkezde(90, 90, b), scale: "2.15", opacity: 0.45 }, // hale + { translate: merkezde(90, 90, b), scale: "1.1", opacity: 1 }, // güneş + ][i]; +} + /** Kare için durak koordinatları: kendi yeri → sıradaki → ondan sonraki → * kendi yeri. Duraklarda bekleme için her hedef iki kez tekrarlanır * (KONUM_ZAMANLARI ile hizalı). */ @@ -184,6 +230,7 @@ export function AnimasyonluMark({ const kokRef = useRef(null); const kontrolRef = useRef<{ ayarla: (hedef: Varyant | null, cevik?: boolean) => void; + temaSekli: (koyu: boolean) => void; } | null>(null); // Animasyonların sahibi: bileşen yaşadıkça bir kez kurulur. Prop değişimi @@ -197,6 +244,8 @@ export function AnimasyonluMark({ let dongu: Animation[] = []; let donus: Animation[] = []; + /** Tema geçişi biçimi (güneş/ay) animasyonları; bkz. `temaSekli`. */ + let sekil: Animation[] = []; /** Şu an dönen döngü ve istenen döngü (`null`: durağan). */ let aktif: Varyant | null = null; let hedef: Varyant | null = null; @@ -233,14 +282,11 @@ export function AnimasyonluMark({ ); }; - const ayarla = (yeni: Varyant | null, cevik = false) => { - if (yeni === hedef) return; - hedef = yeni; - if (aktif !== null) yerles(cevik); - if (yeni === null || azHareket.matches) return; - // Kareler hâlâ yerine dönüyorsa dönüşün bitmesi beklenir; döngü hep - // yerinden başlar, sıçrama olmaz. - Promise.all(donus.map((a) => a.finished)) + /** `yeni` döngüsünü, süren dönüş ve tema biçimi bitince yerinden başlatır. */ + const donguyuSiraya = (yeni: Varyant) => { + // Kareler hâlâ yerine dönüyorsa (ya da güneş/ay oynuyorsa) bitmesi + // beklenir; döngü hep yerinden başlar, sıçrama olmaz. + Promise.all([...donus, ...sekil].map((a) => a.finished)) .then(() => { if (sokuldu || hedef !== yeni || aktif !== null) return; dongu = kareler.flatMap((el, i) => donguyuBaslat(el, i, yeni)); @@ -249,12 +295,94 @@ export function AnimasyonluMark({ .catch(() => {}); }; - const kontrol = { ayarla }; + const ayarla = (yeni: Varyant | null, cevik = false) => { + if (yeni === hedef) return; + hedef = yeni; + if (aktif !== null) yerles(cevik); + if (yeni === null || azHareket.matches) return; + donguyuSiraya(yeni); + }; + + /** + * Tema geçişi biçimi: kareler güneşe ya da aya dönüşür, durur, logoya + * döner. Hover döngüsünden önceliklidir: döngü oynuyorsa kesilir, kareler + * o an neredeyse oradan biçime geçer; biçim bitince imleç hâlâ + * üzerindeyse döngü yerinden yeniden başlar. Hareket azaltılmışsa çalışmaz. + * Hilal, kareyi kesen ikinci bir şekille değil karenin kendi iç gölgesiyle + * çizilir (zemin şeffaf, renk iç gölgede): arkadaki zemin ne olursa olsun + * doğru görünür. + */ + const temaSekli = (koyu: boolean) => { + if (azHareket.matches) return; + // Karelerin o anki hâli: döngü ya da önceki biçim yarıdaysa oradan çıkılır. + const anlik = kareler.map((el) => { + const cs = getComputedStyle(el); + return { + translate: cs.translate, + scale: cs.scale, + opacity: cs.opacity, + borderRadius: cs.borderRadius, + }; + }); + [...dongu, ...donus, ...sekil].forEach((a) => a.cancel()); + dongu = []; + donus = []; + aktif = null; + sekil = kareler.flatMap((el, i) => { + const k = KARELER[i]; + const ev = { + translate: yer(k.baslangic, k.boyut), + scale: "1", + opacity: 1, + borderRadius: `${(13 / k.boyut) * 100}%`, + }; + const bicim = { ...sekilHedefi(i, koyu), borderRadius: "50%" }; + const secenek = { duration: SEKIL_SURE }; + const animasyonlar = [ + el.animate( + [ + { ...anlik[i], easing: DONUS }, + { ...bicim, easing: "linear" }, + { ...bicim, easing: YERLES }, + ev, + ].map((kare, d) => ({ ...kare, offset: SEKIL_DURAKLAR[d] })), + secenek, + ), + ]; + // Hilal: yalnız şeftali kare, yalnız koyuya geçerken. + if (koyu && i === 1) { + const renk = getComputedStyle(el).backgroundColor; + const w = el.offsetWidth; + const dolu = `inset 0 0 0 ${w}px ${renk}`; + // tema-ikonu.tsx'teki hilalle aynı kesim: gövde r74'ten, (+40,-16) + // kaydırılmış r66 daire çıkar — sırtı sol altta. + // İç gölgede: kaydırma = kesim dairesinin kayması, yayılma = iki + // yarıçapın farkı (hepsi gövde çapı 148'e oranla). + const hilal = `inset ${(40 / 148) * w}px ${(-16 / 148) * w}px 0 ${(8 / 148) * w}px ${renk}`; + animasyonlar.push( + el.animate( + [ + { boxShadow: dolu, backgroundColor: "transparent", easing: DONUS }, + { boxShadow: hilal, backgroundColor: "transparent", easing: "linear" }, + { boxShadow: hilal, backgroundColor: "transparent", easing: YERLES }, + { boxShadow: dolu, backgroundColor: "transparent" }, + ].map((kare, d) => ({ ...kare, offset: SEKIL_DURAKLAR[d] })), + secenek, + ), + ); + } + return animasyonlar; + }); + // İmleç hâlâ üzerindeyse hover döngüsü biçimden sonra sürer. + if (hedef !== null) donguyuSiraya(hedef); + }; + + const kontrol = { ayarla, temaSekli }; kontrolRef.current = kontrol; return () => { sokuldu = true; if (kontrolRef.current === kontrol) kontrolRef.current = null; - [...dongu, ...donus].forEach((a) => a.cancel()); + [...dongu, ...donus, ...sekil].forEach((a) => a.cancel()); }; }, []); @@ -276,9 +404,18 @@ export function AnimasyonluMark({ const cik = () => kontrolRef.current?.ayarla(null, true); hoverHedefi.addEventListener("mouseenter", gir); hoverHedefi.addEventListener("mouseleave", cik); + // Tema geçişi bitince (lib/tema-gecisi.ts) kareler yeni temanın + // biçimine dönüşür: koyuya geçişte ay ve yıldızlar, açığa geçişte güneş. + // Olay anında yeni temanın sınıfını taşır. + const temaGecti = () => + kontrolRef.current?.temaSekli( + document.documentElement.classList.contains("dark"), + ); + window.addEventListener(TEMA_GECIS_OLAYI, temaGecti); return () => { hoverHedefi.removeEventListener("mouseenter", gir); hoverHedefi.removeEventListener("mouseleave", cik); + window.removeEventListener(TEMA_GECIS_OLAYI, temaGecti); }; }, [oynat, sadeceHover, varyant]); diff --git a/src/components/arayuz-bilgisi.tsx b/src/components/arayuz-bilgisi.tsx new file mode 100644 index 0000000..fc1c261 --- /dev/null +++ b/src/components/arayuz-bilgisi.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { useRef, useState } from "react"; +import { Info } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +/** + * Sürüm notlarında "Arayüz iyileştirmeleri" satırının yanındaki bilgi düğmesi. + * Düğme ve balon, danışman sohbetindeki "Danışmanın bildikleri" düğmesiyle + * birebir aynıdır (`sohbet-client.tsx`). Fark: dokunmatikte üzerine gelme + * olmadığı için dokununca da açılıp kapanır. + */ +export function ArayuzBilgisi({ maddeler }: { maddeler: string[] }) { + const [acik, setAcik] = useState(false); + // Basış anındaki durum: Radix basışta balonu kapatır, tık ona göre çevirir. + const basistaAcikti = useRef(false); + + return ( + + + + + + +

Bu sürümdeki arayüz iyileştirmeleri:

+
    + {maddeler.map((m) => ( +
  • {m}
  • + ))} +
+
+
+
+ ); +} diff --git a/src/components/site-footer.tsx b/src/components/site-footer.tsx index d06ea03..e9b945e 100644 --- a/src/components/site-footer.tsx +++ b/src/components/site-footer.tsx @@ -25,8 +25,10 @@ export function SiteFooter() { .sort((a, b) => b.programSayisi - a.programSayisi) .slice(0, 8); + // data-site-footer: layout'taki #icerik alt boşluğunu (pb-28) kapatır — + // dev yazı logosu ekranın altına yapışsın. return ( -