Files
kolaytercih/src/lib/tema-gecisi.ts
bilalgursen aec8a2ad76 feat(v2): 9–10 Ekim çalışma ağacı — deneyim dili 1. dalga, PWA manifest, revizyon niyet modeli, listem/tadımlık bileşen denetimi
9–10 Ekim oturumlarının commit'lenmemiş işi tek parça alındı
(raporlar: docs/ekip-raporlari/2026-10-09, 2026-10-10):
- Landing deneyim dili 1. dalga: sayfa geçişi (ViewTransition), piksel morf/şerit,
  sorun sahneleri, adım akışı, sayan sayı, mobil menü, adım göstergesi, logoya tepki;
  DESIGN.md §11 ve AGENTS.md kuralları, tasarım denetimine yeni kurallar
- Tema: tema ikonu, D kısayolu, tema geçişi
- PWA: manifest.ts ve ikonlar
- Danışman: revizyon niyet modeli (OPENROUTER_NIYET_MODEL), sohbet geçmişi, soru API'si
- /listem ve /sonuc tadımlığı bileşen denetimi düzeltmeleri
- eslint: .venv-tts/ yok sayılır

tsc, eslint ve tasarım denetimi sıfır hata.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 00:34:29 +03:00

101 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
// gibi açılır — kenarı keskin değil, geniş ve tüylü; ışık yayılır gibi.
// Geçiş kısa ve hafiftir; BİTİNCE logonun kareleri yeni temanın biçimine
// 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
// bir şekil yok (9 Eki 2026).
//
// Nasıl: View Transitions API eski/yeni görüntü çiftini verir; yeni görüntü
// radyal degrade maskesiyle açılır. Yarıçapı CSS sürer (globals.css
// `.kt-tema-gecis`), buradan yalnız merkez ve son yarıçap verilir. Tarayıcı
// View Transitions bilmiyorsa ya da hareket azaltılmışsa tema düz değişir.
/** Geçiş sırasında <html>'de duran sınıf (globals.css). */
const GECIS_SINIFI = "kt-tema-gecis";
/** 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). */
export const TEMA_GECIS_OLAYI = "kt-tema-gecis";
// Maskenin tam opak çekirdeği yarıçapın bu oranında biter, kalanı tüydür
// (globals.css'teki --kt-tema-cekirdek ile aynı olmalı): son yarıçap, çekirdek
// en uzak köşeyi de örtecek kadar büyük seçilir.
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;
/**
* `uygula` temayı değiştiren çağrıdır (ör. `setTheme("dark")`). Geçiş
* sürerken gelen istek yok sayılır: art arda D'ye basınca ekrandaki görüntü
* ile tema durumu birbirine girmesin.
*/
export function temayiGecisle(uygula: () => void) {
if (typeof document === "undefined") {
uygula();
return;
}
const azHareket = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (azHareket) {
uygula();
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;
const kok = document.documentElement;
// Halkanın merkezi header'daki logo; krom gizli sayfalarda sol üst köşe.
const logo = document
.querySelector('header a[aria-label="KolayTercih"]')
?.getBoundingClientRect();
const gorunur = logo && logo.width > 0 && logo.bottom > 0;
const x = gorunur ? logo.left + logo.height / 2 : 24;
const y = gorunur ? logo.top + logo.height / 2 : 24;
const enUzak = Math.hypot(
Math.max(x, window.innerWidth - x),
Math.max(y, window.innerHeight - y),
);
kok.style.setProperty("--kt-tema-x", `${x}px`);
kok.style.setProperty("--kt-tema-y", `${y}px`);
kok.style.setProperty("--kt-tema-son", `${Math.ceil(enUzak / CEKIRDEK)}px`);
kok.classList.add(GECIS_SINIFI);
suruyor = true;
const gecis = document.startViewTransition(uygula);
// Sekme gizliyken / yeniden boyutlanırken tarayıcı geçişi iptal eder:
// `ready` reddedilir, tema yine uygulanmıştır; konsola hata düşmesin.
gecis.ready.catch(() => {});
gecis.finished
.catch(() => {})
.finally(() => {
kok.classList.remove(GECIS_SINIFI);
kok.style.removeProperty("--kt-tema-x");
kok.style.removeProperty("--kt-tema-y");
kok.style.removeProperty("--kt-tema-son");
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,
);
});
}