feat(tema): sayfa geçişi kısa ve hafif (600 ms), logo dönüşümü geçiş bitince oynar; DESIGN §4.22 logo morph

- Sıra: önce sayfa geçişi biter, kısa nefes (80 ms), sonra logo güneşe/aya
  dönüşür; üst üste binmez. Geçiş iptal olsa da logo doğru biçime dönüşür.
- View Transitions olmayan tarayıcıda da logo dönüşür.
- DESIGN.md §4.22: logo biçim dönüşümü tasarım diline eklendi (biçim tablosu,
  koreografi, kurallar); §6 hareket ve §7 Yap/Yapma satırları.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-09 00:48:41 +03:00
parent 49d88cd72f
commit 1b51903e71
4 changed files with 73 additions and 13 deletions

View File

@@ -135,12 +135,12 @@ Seçici `src/components/tema-secici.tsx` (footer'da), sağlayıcı `src/componen
**Geçiş ve kısayol (7 Eki 2026; son hâli 9 Eki):** tema değişimi anlık değildir; yeni **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 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), 1100 ms, yumuşak kalkıp yumuşak durur. Aynı anda logonun kareleri 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 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 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 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 ö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 (`tema-ikonu.tsx`: güneş, ay, Sistem için seçicinin ikonları aynı biçimlerin durağan hâlidir (ayrıntı ve kurallar §4.22) (`tema-ikonu.tsx`: güneş, ay, Sistem için
logonun üç karesi); burada lucide ikonu kullanılmaz. View Transitions + radyal degrade 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 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 değişir. Denenip bırakılanlar: logo biçimli maske ("logo içinde logo"), düz crossfade, piksel
@@ -714,6 +714,41 @@ 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 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. `listeUyarilari`'na eklenir, sayfaya yeni kutu açılmaz.
### 4.22 Logo biçim dönüşümü (morph) — `AnimasyonluMark` `temaSekli`, `tema-ikonu.tsx`
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. Yerleşim
### 5.1 Genel ### 5.1 Genel
@@ -783,6 +818,8 @@ Hareket kuralları (Apple, `motion/react`):
- Kaydırmaya bağlı tetik `IntersectionObserver` ile (kaydırma dinleyicisi değil). - Kaydırmaya bağlı tetik `IntersectionObserver` ile (kaydırma dinleyicisi değil).
- "Hareketi azalt": kayma/yay yok, yalnız opaklık (`useReducedMotion`, - "Hareketi azalt": kayma/yay yok, yalnız opaklık (`useReducedMotion`,
`motion-reduce:transition-opacity`). `motion-reduce:transition-opacity`).
- Logo kareleri bir durumu anlatmak için kısa bir an başka biçime dönüşebilir (§4.22); sayfa
geçişiyle üst üste binmez, sırayla oynar.
## 7. Yap / Yapma ## 7. Yap / Yapma
@@ -800,6 +837,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 | | 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 | | 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` | | 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.22) | 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ı | | 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 | | 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 | | Üç kart yalnızca üç gerçek öğe varsa | Hero + üç kart iskeleti refleksi |

View File

@@ -483,7 +483,8 @@
değil: yarıçapın %55'ine kadar tam opak, oradan kenara doğru tüy gibi 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 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 yayılır gibi, sert çizgi yok. Yarıçap (--kt-tema-r) 0 → --kt-tema-son
yumuşak kalkıp yumuşak durur. Merkez ve son yarıçap JS'ten gelir. 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. */ Hareket azaltılmışsa JS geçişi hiç başlatmaz, tema düz değişir. */
@property --kt-tema-r { @property --kt-tema-r {
syntax: "<length>"; syntax: "<length>";
@@ -509,7 +510,7 @@
#000 calc(var(--kt-tema-r) * var(--kt-tema-cekirdek)), #000 calc(var(--kt-tema-r) * var(--kt-tema-cekirdek)),
transparent var(--kt-tema-r) transparent var(--kt-tema-r)
); );
animation: kt-tema-halka 1100ms cubic-bezier(0.45, 0, 0.2, 1) both; animation: kt-tema-halka 600ms cubic-bezier(0.4, 0, 0.2, 1) both;
} }
@keyframes kt-tema-halka { @keyframes kt-tema-halka {
from { from {

View File

@@ -103,7 +103,7 @@ function merkezde(cx: number, cy: number, boyut: number): string {
return `${((cx - boyut / 2) / boyut) * 100}% ${((cy - boyut / 2) / boyut) * 100}%`; return `${((cx - boyut / 2) / boyut) * 100}% ${((cy - boyut / 2) / boyut) * 100}%`;
} }
// Tema geçişi biçimi (DESIGN.md §2.3): geçiş logodan yayılırken kareler // 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. // 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; // - Koyuya: şeftali kare hilale, mavi kare hilalin kucağındaki yıldıza;
// turuncu söner. // turuncu söner.
@@ -404,7 +404,7 @@ export function AnimasyonluMark({
const cik = () => kontrolRef.current?.ayarla(null, true); const cik = () => kontrolRef.current?.ayarla(null, true);
hoverHedefi.addEventListener("mouseenter", gir); hoverHedefi.addEventListener("mouseenter", gir);
hoverHedefi.addEventListener("mouseleave", cik); hoverHedefi.addEventListener("mouseleave", cik);
// Tema geçişi başlarken (lib/tema-gecisi.ts) kareler yeni temanın // 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ş. // biçimine dönüşür: koyuya geçişte ay ve yıldızlar, açığa geçişte güneş.
// Olay anında <html> yeni temanın sınıfını taşır. // Olay anında <html> yeni temanın sınıfını taşır.
const temaGecti = () => const temaGecti = () =>

View File

@@ -1,8 +1,9 @@
// Tema geçişi (DESIGN.md §2.3): açık ↔ koyu değişimi ekranı bir anda // Tema geçişi (DESIGN.md §2.3): açık ↔ koyu değişimi ekranı bir anda
// çevirmez; yeni tema header'daki KolayTercih işaretinden yumuşak bir halka // çevirmez; yeni tema header'daki KolayTercih işaretinden yumuşak bir halka
// gibi açılır — kenarı keskin değil, geniş ve tüylü; ışık yayılır gibi. // gibi açılır — kenarı keskin değil, geniş ve tüylü; ışık yayılır gibi.
// Aynı anda logonun kareleri yeni temanın biçimine dönüşür (koyuya: ay ve // Geçiş kısa ve hafiftir; BİTİNCE logonun kareleri yeni temanın biçimine
// yıldızlar; açığa: güneş), kısa bir an durur ve yeniden logoya toplanır. // dönüşür (koyuya: ay ve yıldız; açığa: güneş), kısa bir an durur ve
// yeniden logoya toplanır.
// Markanın yumuşak, oyunsu diline uysun diye sert kenar, piksel ya da ikinci // Markanın yumuşak, oyunsu diline uysun diye sert kenar, piksel ya da ikinci
// bir şekil yok (9 Eki 2026). // bir şekil yok (9 Eki 2026).
// //
@@ -14,7 +15,7 @@
/** Geçiş sırasında <html>'de duran sınıf (globals.css). */ /** Geçiş sırasında <html>'de duran sınıf (globals.css). */
const GECIS_SINIFI = "kt-tema-gecis"; const GECIS_SINIFI = "kt-tema-gecis";
/** Geçiş başlarken pencereye atılan olay; header'daki logo bunu dinleyip /** Geçiş bitince pencereye atılan olay; header'daki logo bunu dinleyip
* karelerini güneşe ya da aya dönüştürür (animasyonlu-mark.tsx). */ * karelerini güneşe ya da aya dönüştürür (animasyonlu-mark.tsx). */
export const TEMA_GECIS_OLAYI = "kt-tema-gecis"; export const TEMA_GECIS_OLAYI = "kt-tema-gecis";
@@ -23,6 +24,9 @@ export const TEMA_GECIS_OLAYI = "kt-tema-gecis";
// en uzak köşeyi de örtecek kadar büyük seçilir. // en uzak köşeyi de örtecek kadar büyük seçilir.
const CEKIRDEK = 0.55; const CEKIRDEK = 0.55;
/** Sayfa geçişi ile logo dönüşümü arasındaki kısa nefes (ms). */
const LOGO_NEFESI = 80;
let suruyor = false; let suruyor = false;
/** /**
@@ -36,10 +40,20 @@ export function temayiGecisle(uygula: () => void) {
return; return;
} }
const azHareket = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const azHareket = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!document.startViewTransition || azHareket) { if (azHareket) {
uygula(); uygula();
return; return;
} }
if (!document.startViewTransition) {
// Sayfa geçişi yok ama logo yine dönüşür; temanın sınıfı <html>'e
// yazılsın diye kısa bir an beklenir.
uygula();
window.setTimeout(
() => window.dispatchEvent(new Event(TEMA_GECIS_OLAYI)),
LOGO_NEFESI,
);
return;
}
if (suruyor) return; if (suruyor) return;
const kok = document.documentElement; const kok = document.documentElement;
@@ -64,9 +78,7 @@ export function temayiGecisle(uygula: () => void) {
const gecis = document.startViewTransition(uygula); const gecis = document.startViewTransition(uygula);
// Sekme gizliyken / yeniden boyutlanırken tarayıcı geçişi iptal eder: // Sekme gizliyken / yeniden boyutlanırken tarayıcı geçişi iptal eder:
// `ready` reddedilir, tema yine uygulanmıştır; konsola hata düşmesin. // `ready` reddedilir, tema yine uygulanmıştır; konsola hata düşmesin.
gecis.ready gecis.ready.catch(() => {});
.then(() => window.dispatchEvent(new Event(TEMA_GECIS_OLAYI)))
.catch(() => {});
gecis.finished gecis.finished
.catch(() => {}) .catch(() => {})
.finally(() => { .finally(() => {
@@ -75,5 +87,14 @@ export function temayiGecisle(uygula: () => void) {
kok.style.removeProperty("--kt-tema-y"); kok.style.removeProperty("--kt-tema-y");
kok.style.removeProperty("--kt-tema-son"); kok.style.removeProperty("--kt-tema-son");
suruyor = false; suruyor = false;
// Sıra: önce sayfa geçişi biter, sonra logo dönüşür — ikisi üst üste
// binmez, göz önce temaya sonra logoya gider. Kısa nefes (LOGO_NEFESI):
// geçiş iptal olduysa (sekme gizli vb.) `finished` hemen döner ve
// temanın sınıfı <html>'e henüz yazılmamış olabilir; logo yanlış
// biçime dönüşmesin.
window.setTimeout(
() => window.dispatchEvent(new Event(TEMA_GECIS_OLAYI)),
LOGO_NEFESI,
);
}); });
} }