refactor(tema): logo maskesi kaldırıldı, tema geçişi sade crossfade (640 ms)

Bilal'in kararı (9 Eki 2026): SVG maskeli yayılma şimdilik yok; tüm sayfa
tek parça crossfade ile geçer, mursela-nextjs'teki tema geçişiyle aynı
süre ve eğri (640 ms, cubic-bezier(0.6, 0, 0.2, 1)). motion bağımlılığı
ve maske değişkenleri tema-gecisi.ts'ten çıktı; DESIGN §2.3 notu güncel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-09 00:15:34 +03:00
parent 4c84a1a974
commit a51724fdcc
6 changed files with 44 additions and 141 deletions

View File

@@ -1,33 +1,20 @@
// Tema geçişi (DESIGN.md §2.3): açık ↔ koyu değişimi ekranı bir anda
// çevirmez; yeni tema, KolayTercih işaretinin biçiminde önce ekranın altından
// ortaya süzülür, sonra merkezden büyüyerek ekranı kaplar. View Transitions
// API + CSS maskesi (globals.css `.kt-tema-gecis`) eski/yeni görüntü çiftini
// kurar; maskenin konumu, boyu ve son dolgu motion ile sürülür. Tarayıcı View Transitions bilmiyorsa ya da kullanıcı hareketi
// azaltmışsa tema düz değişir.
// çevirmez; tüm sayfa tek parça, yumuşak bir crossfade ile yeni temaya geçer
// (View Transitions API; süre ve eğri globals.css `.kt-tema-gecis`
// bloğunda, mursela-nextjs'teki tema geçişiyle aynı: 640 ms ease-in-out).
// Tarayıcı View Transitions bilmiyorsa ya da kullanıcı hareketi azaltmışsa
// tema düz değişir.
import { animate, type AnimationPlaybackControls } from "motion";
/** Geçiş sırasında <html>'de duran sınıf (globals.css). */
/** Geçiş sırasında <html>'de duran sınıf: crossfade stili yalnız tema
* değişimine uygulanır, başka view transition'lara karışmaz. */
const GECIS_SINIFI = "kt-tema-gecis";
// Saniye. Faz 1 (uçuş): işaret ekranın altından ortaya gelir. Faz 2
// (kaplama): merkezden büyüyerek ekranı aşar; DOLGU_BASI'ndan sonra tam ekran
// dolgu alfası yükselir ve kareler arasında / kenarlarda kalan eski görüntü
// yeni temaya solar. Ekranda tek silüet vardır, ikinci bir şekil büyümez.
const UCUS_SURE = 0.45;
const KAPLAMA_SURE = 0.6;
const DOLGU_BASI = 0.5;
const EASE = [0.65, 0, 0.35, 1] as const;
// CSS'teki kt-tema-omur'un süresi: motion bitmeden pseudo eleman kalkmasın
// diye toplamın biraz üstü (globals.css'te --kt-tema-sure ile okunur).
const GECIS_SURESI = UCUS_SURE + KAPLAMA_SURE + 0.1;
let suruyor = false;
/**
* `uygula` temayı değiştiren çağrıdır (ör. `setTheme("dark")`). Geçiş
* destekleniyorsa işaret ekranın altından ortaya süzülür ve oradan büyüyerek
* yeni temayı açar.
* 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") {
@@ -39,66 +26,12 @@ export function temayiGecisle(uygula: () => void) {
uygula();
return;
}
// Geçiş sürerken gelen istek yok sayılır (uygulanmaz): ara anahtarlamalar
// hem koreografiyi bozar hem de ekrandaki görüntü ile tema durumu birbirine
// girer; D'ye art arda basınca görünen "sıçrama" budur.
if (suruyor) return;
const en = window.innerWidth;
const boy = window.innerHeight;
const kok = document.documentElement;
// Uçuş boyunca işaretin maskeden çıkan kenarı: ekranı kaplamayacak ama
// belirgin bir işaret olacak kadar (min ekran kenarının %22'si; işaretin
// görünür karesi bunun ~%67'si).
const ucusKenar = Math.round(Math.min(en, boy) * 0.22);
// Uçuş ve kaplamanın ortak ekseni ekranın ortası; başlangıçta işaret
// tamamen ekranın altında durur (alttan gelir).
const merkezX = en / 2;
const merkezY = boy / 2;
const baslangicY = boy + ucusKenar / 2;
// Kaplama merkezden ekranın en uzak köşesine kadar büyümeli. İşaretin
// görünür karesi viewBox'ın ~%67'si; kenar bu oranla çarpılır ki kareler
// ekranı geniş ölçüde kaplasın — kalan boşlukları dolgu solması kapatır.
const sonKenar = Math.ceil(Math.hypot(merkezX, merkezY) * 3);
kok.style.setProperty("--kt-tema-x", `${merkezX}px`);
kok.style.setProperty("--kt-tema-y", `${baslangicY}px`);
kok.style.setProperty("--kt-tema-kenar", `${ucusKenar}px`);
kok.style.setProperty("--kt-tema-dolgu", "0");
kok.style.setProperty("--kt-tema-sure", `${GECIS_SURESI}s`);
kok.classList.add(GECIS_SINIFI);
suruyor = true;
const kontroller: AnimationPlaybackControls[] = [
// Faz 1 — uçuş: alttan ortaya, x sabit.
animate(0, 1, {
duration: UCUS_SURE,
ease: EASE,
onUpdate: (p) => {
kok.style.setProperty(
"--kt-tema-y",
`${baslangicY + (merkezY - baslangicY) * p}px`,
);
},
}),
// Faz 2 — kaplama: merkezden ekranı aşana kadar.
animate(ucusKenar, sonKenar, {
duration: KAPLAMA_SURE,
delay: UCUS_SURE,
ease: EASE,
onUpdate: (kenar) => kok.style.setProperty("--kt-tema-kenar", `${kenar}px`),
}),
// Dolgu: büyümenin ikinci yarısında kareler arasında ve ekran
// kenarlarında kalan eski görüntü yeni temaya solar.
animate(0, 1, {
duration: KAPLAMA_SURE * (1 - DOLGU_BASI),
delay: UCUS_SURE + KAPLAMA_SURE * DOLGU_BASI,
ease: EASE,
onUpdate: (dolgu) => kok.style.setProperty("--kt-tema-dolgu", `${dolgu}`),
}),
];
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.
@@ -106,7 +39,6 @@ export function temayiGecisle(uygula: () => void) {
gecis.finished
.catch(() => {})
.finally(() => {
kontroller.forEach((k) => k.stop());
kok.classList.remove(GECIS_SINIFI);
suruyor = false;
});