v1-beta-tema #9
@@ -133,14 +133,11 @@ Seçici `src/components/tema-secici.tsx` (footer'da), sağlayıcı `src/componen
|
||||
(`next-themes`, `<html class="dark">`). Renklerin tek kaynağı `src/app/globals.css` içindeki
|
||||
`.dark` bloğu.
|
||||
|
||||
**Geçiş ve kısayol (7 Eki 2026):** tema değişimi anlık değildir; yeni tema
|
||||
KolayTercih işaretinin biçiminde önce ekranın altından ortaya süzülür, sonra
|
||||
merkezden büyüyerek ekranı kaplar — büyüme boyunca logo normal, boşluklu
|
||||
hâliyle görünür; büyümenin ikinci yarısında kareler arasında kalan eski
|
||||
görüntü tam ekran dolguyla yeni temaya solar. Ekranda tek silüet vardır,
|
||||
ikinci bir şekil büyümez ("logo içinde logo" olmaz). (View Transitions + CSS
|
||||
maskesi, maskeyi motion sürer: `src/lib/tema-gecisi.ts`, `globals.css`
|
||||
`.kt-tema-gecis`; hareket azaltılmışsa düz değişir.)
|
||||
**Geçiş ve kısayol (7 Eki 2026, 9 Eki'de sadeleşti):** tema değişimi anlık değildir;
|
||||
tüm sayfa tek parça, 640 ms ease-in-out crossfade ile yeni temaya geçer (View
|
||||
Transitions: `src/lib/tema-gecisi.ts`, `globals.css` `.kt-tema-gecis`; hareket
|
||||
azaltılmışsa düz değişir). Logo maskeli yayılma denendi ve kaldırıldı (Bilal'in
|
||||
kararı, 9 Eki 2026): mursela-nextjs'teki sade crossfade yeter.
|
||||
`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.
|
||||
|
||||
@@ -477,73 +477,47 @@
|
||||
background: var(--color-slate-700);
|
||||
}
|
||||
|
||||
/* Tema geçişi (DESIGN.md §2.3, lib/tema-gecisi.ts): tema değişirken yeni
|
||||
görüntü, KolayTercih işaretinin biçiminde önce ekranın altından ortaya
|
||||
süzülür, sonra merkezden büyüyerek ekranı kaplar. Eski görüntü yerinde
|
||||
durur; yeni görüntünün maskesi iki katman (mask-composite varsayılanı
|
||||
add, toplanır):
|
||||
(1) işaretin çizimi (kolay-tercih-mark.svg'nin gömülü kopyası — üç kare,
|
||||
aralarındaki boşlukla, logo logonun kendisi gibi durur), merkezi
|
||||
--kt-tema-x/y'de, boyu --kt-tema-kenar;
|
||||
(2) tam ekran dolgu, alfası --kt-tema-dolgu (0 → 1): büyümenin ikinci
|
||||
yarısında kareler arasında ve ekran kenarlarında kalan eski görüntü
|
||||
yumuşakça yeni temaya solar. İkinci bir şekil yok — "logo içinde logo"
|
||||
görünmesin (8 Eki 2026, Bilal).
|
||||
SVG data URI olarak gömülü, dosyadan url() ile DEĞİL: Chrome, view
|
||||
transition pseudo elemanında dosyadan yüklenen birden çok maske
|
||||
katmanını tümden şeffaf çiziyordu. Çizim public/kolay-tercih-mark.svg
|
||||
ile aynı geometridir; o değişirse burası da güncellenir.
|
||||
Konum, boy ve dolgu motion ile sürülür (lib/tema-gecisi.ts); buradaki
|
||||
kt-tema-omur yalnızca geçişin pseudo elemanı motion bitene kadar yaşasın
|
||||
diye durur (süre --kt-tema-sure, JS'den gelir). Hareket azaltılmışsa JS
|
||||
geçişi hiç başlatmaz, tema düz değişir. */
|
||||
@property --kt-tema-kenar {
|
||||
syntax: "<length>";
|
||||
inherits: true;
|
||||
initial-value: 0px;
|
||||
}
|
||||
@property --kt-tema-dolgu {
|
||||
syntax: "<number>";
|
||||
inherits: true;
|
||||
initial-value: 0;
|
||||
}
|
||||
|
||||
/* Tema geçişi (DESIGN.md §2.3, lib/tema-gecisi.ts): tema değişirken tüm
|
||||
sayfa tek parça crossfade yapar — eski görüntü solar, yeni belirir.
|
||||
mursela-nextjs'teki tema geçişiyle aynı: 640 ms, ease-in-out. Sınıf
|
||||
(`kt-tema-gecis`) geçiş süresince <html>'de durur; stil yalnız tema
|
||||
değişimine uygulanır. Hareket azaltılmışsa JS geçişi hiç başlatmaz,
|
||||
tema düz değişir. */
|
||||
.kt-tema-gecis {
|
||||
view-transition-name: root;
|
||||
--kt-tema-sure: 640ms;
|
||||
--kt-tema-egri: cubic-bezier(0.6, 0, 0.2, 1);
|
||||
}
|
||||
.kt-tema-gecis::view-transition-old(root),
|
||||
.kt-tema-gecis::view-transition-new(root) {
|
||||
animation: none;
|
||||
mix-blend-mode: normal;
|
||||
will-change: opacity;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
.kt-tema-gecis::view-transition-group(root) {
|
||||
animation-duration: var(--kt-tema-sure);
|
||||
animation-timing-function: var(--kt-tema-egri);
|
||||
}
|
||||
.kt-tema-gecis::view-transition-old(root) {
|
||||
z-index: 1;
|
||||
animation: kt-tema-cikis var(--kt-tema-sure) var(--kt-tema-egri) both;
|
||||
}
|
||||
.kt-tema-gecis::view-transition-new(root) {
|
||||
z-index: 2;
|
||||
mask-image:
|
||||
url("data:image/svg+xml,<svg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20180%20180%22><rect%20x=%2227%22%20y=%2256%22%20width=%2257%22%20height=%2257%22%20rx=%2213%22/><rect%20x=%2291%22%20y=%2229%22%20width=%2256%22%20height=%2256%22%20rx=%2213%22/><rect%20x=%2291%22%20y=%2295%22%20width=%2256%22%20height=%2256%22%20rx=%2213%22/></svg>"),
|
||||
linear-gradient(
|
||||
rgb(0 0 0 / var(--kt-tema-dolgu)),
|
||||
rgb(0 0 0 / var(--kt-tema-dolgu))
|
||||
);
|
||||
mask-repeat: no-repeat;
|
||||
mask-size:
|
||||
var(--kt-tema-kenar) var(--kt-tema-kenar),
|
||||
100% 100%;
|
||||
mask-position:
|
||||
calc(var(--kt-tema-x) - var(--kt-tema-kenar) / 2)
|
||||
calc(var(--kt-tema-y) - var(--kt-tema-kenar) / 2),
|
||||
0 0;
|
||||
/* Değeri değişmeyen ama gerçek bir animasyon: view transition, üzerindeki
|
||||
tüm animasyonlar bitene kadar kalkmaz; maskeyi JS (motion) sürerken
|
||||
ömrü bu tutar. */
|
||||
animation: kt-tema-omur var(--kt-tema-sure, 1.05s) linear both;
|
||||
z-index: 0;
|
||||
animation: kt-tema-giris var(--kt-tema-sure) var(--kt-tema-egri) both;
|
||||
}
|
||||
@keyframes kt-tema-omur {
|
||||
@keyframes kt-tema-cikis {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes kt-tema-giris {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ function yaziAlanindaMi(hedef: EventTarget | null) {
|
||||
* Klavye kısayolu: `D` açık ↔ koyu temayı değiştirir (ipucu footer'daki
|
||||
* TemaSecici'nin altında, Kbd ile). Değiştirici tuşla (⌘/Ctrl/Alt) ya da yazı
|
||||
* alanındayken çalışmaz; "sistem" seçiliyken o anki çözümlenmiş temanın
|
||||
* tersine, açık bir seçime geçer. Geçiş logodan yayılan maskeyle oynar.
|
||||
* tersine, açık bir seçime geçer. Geçiş sayfa geneli crossfade ile oynar.
|
||||
*/
|
||||
export function TemaKisayolu() {
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
|
||||
@@ -27,7 +27,7 @@ function AdresCubuguRengi() {
|
||||
* globals.css'teki `.dark` token eşlemesinden gelir. Varsayılan açık tema;
|
||||
* kullanıcı footer'daki TemaSecici ile koyu ya da "sistem"e geçer, seçim
|
||||
* tarayıcıda saklanır; `D` tuşu açık ↔ koyu geçişi yapar (TemaKisayolu).
|
||||
* Tema değişimi logodan yayılan maskeyle oynar (lib/tema-gecisi.ts).
|
||||
* Tema değişimi sayfa geneli crossfade ile oynar (lib/tema-gecisi.ts).
|
||||
*/
|
||||
export function TemaSaglayici({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
|
||||
@@ -18,8 +18,8 @@ const aboneOlma = () => () => {};
|
||||
/**
|
||||
* Tema seçici: açık / koyu / sistem (DESIGN.md §2.3). Seçili tema yalnızca
|
||||
* tarayıcıda bilinir; sunucu HTML'inde hiçbir seçenek işaretli gelmez,
|
||||
* hydration sonrası işaretlenir (yerleşim oynamaz). Seçim logodan yayılan
|
||||
* maskeyle uygulanır (lib/tema-gecisi.ts); altında `D` kısayolu ipucu durur.
|
||||
* hydration sonrası işaretlenir (yerleşim oynamaz). Seçim sayfa geneli crossfade
|
||||
* ile uygulanır (lib/tema-gecisi.ts); altında `D` kısayolu ipucu durur.
|
||||
*/
|
||||
export function TemaSecici({ className }: { className?: string }) {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user