Ders 11 / 19
Tema Yönetimi
Temaların kaynak tablodan tek kuralla türetilmesi, aynalamanın koruduğu ve kaçırdığı sözleşmeler, düzeltmenin istisna listesi olarak tutulması, marka temasının tek aileyle sınanması ve rol–tema kapsamının ölçülmesi.
İçindekiler
Temel katman önceki derste tamamlandı: renk, tipografi, aralık, boyut ve düzen kararları adlandırılmış ve türetilebilir durumda. Geriye Renk Belirteçleri dersinden kalan boşluk kalıyor. Orada yirmi rolün yalnız sekizinin koyu tema karşılığı vardı ve eksik hücreler elle doldurulmayı bekliyordu.
Elle doldurma iki nedenle işe yaramaz. Birincisi, her yeni rol her temada bir kez daha yazılmak zorundadır ve bu iş rol sayısıyla tema sayısının çarpımı kadar büyür. İkincisi, elle yazılan değer bir gerekçe taşımaz: bir rolün koyu temada niçin o basamağa düştüğü kayıtlı değildir. Bu ders temayı bir değer listesi olarak değil, kaynak temadan türetilen bir tablo olarak kurar ve türetmenin nerede yetmediğini ölçer.
Tema Bir Türetme Kuralıdır
Bir tema, anlamsal katmanın her rolüne bir ilkel değer bağlayan tablodur. İki tema arasındaki
fark, bu bağlamanın kuralıdır. Koyu tema için en yalın kural basamak aynalamasıdır:
açıklık rampasının iki ucu yer değiştirir, 000 ile 900, 050 ile 800 eşleşir. Yüzey
en açıktan en koyuya, metin en koyudan en açığa geçer; rol adları ve sözleşme değişmez.
Kuralın değeri, tek bir satırın bütün rolleri taşımasıdır. Yeni bir rol eklendiğinde koyu karşılığı ayrıca yazılmaz; kural onu da kapsar. Kuralın sınırı ise ölçülerek bulunur: aynalama kontrast oranını korumaz, çünkü açıklık rampası algılanan parlaklık ekseninde bakışımlı değildir.
Aşağıdaki betik iki sözleşmeyi de her temada çalıştırır: eşik sözleşmesi (ön plan–zemin çiftlerinin en az kaç kontrast oranı taşıyacağı) ve durum ayrımı sözleşmesi (bir durum rolünün taban rolünden ayırt edilebilmesi için gereken en küçük oran, Etkileşim Durumları dersinde 1.2 olarak benimsenmişti).
// tema.mjs — temalarin tek kuraldan turetilmesi ve sozlesmenin her temada denetlenmesi function hslRgb(h, s, l) { s /= 100; l /= 100; const k = (n) => (n + h / 30) % 12; const a = s * Math.min(l, 1 - l); const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); return [f(0), f(8), f(4)].map((v) => Math.round(v * 255)); } const onaltilik = (rgb) => "#" + rgb.map((v) => v.toString(16).padStart(2, "0")).join(""); function kanal(v) { const s = v / 255; return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4); } const parlaklik = ([r, g, b]) => 0.2126 * kanal(r) + 0.7152 * kanal(g) + 0.0722 * kanal(b); function kontrast(a, b) { const [x, y] = [parlaklik(a), parlaklik(b)].sort((p, q) => q - p); return (x + 0.05) / (y + 0.05); } // Ilkel katman (Renk Belirtecleri dersindeki rampa). const ACIKLIK = { "000": 100, "050": 97, 100: 92, 200: 84, 300: 74, 400: 62, 500: 50, 600: 40, 700: 31, 800: 22, 900: 14 }; const AILE = { notr: [214, 8], ana: [214, 62], basari: [145, 55], uyari: [38, 85], hata: [8, 68] }; // Anlamsal katman: rol -> [aile, basamak]. Acik tema kaynagidir. const ACIK = { yuzey: ["notr", "000"], "yuzey-ikincil": ["notr", "050"], "yuzey-secili": ["ana", "100"], kenarlik: ["notr", "500"], "metin-birincil": ["notr", "900"], "metin-ikincil": ["notr", "600"], "eylem-ana": ["ana", "600"], "eylem-ana-uzeri": ["notr", "000"], "eylem-ana-uzerine": ["ana", "700"], "eylem-ana-basili": ["ana", "800"], "eylem-devre-disi": ["notr", "200"], "eylem-devre-disi-uzeri": ["notr", "500"], "odak-halkasi": ["ana", "600"], "basari-zemin": ["basari", "100"], "basari-metin": ["basari", "800"], "uyari-zemin": ["uyari", "100"], "uyari-metin": ["uyari", "800"], "hata-zemin": ["hata", "100"], "hata-metin": ["hata", "700"], }; // Sozlesme: on plan, zemin, esik. const SOZLESME = [ ["metin-birincil", "yuzey", 4.5], ["metin-birincil", "yuzey-ikincil", 4.5], ["metin-ikincil", "yuzey", 4.5], ["metin-birincil", "yuzey-secili", 4.5], ["kenarlik", "yuzey", 3.0], ["eylem-ana", "yuzey", 3.0], ["eylem-ana-uzeri", "eylem-ana", 4.5], ["eylem-ana-uzeri", "eylem-ana-uzerine", 4.5], ["eylem-ana-uzeri", "eylem-ana-basili", 4.5], ["odak-halkasi", "yuzey", 3.0], ["basari-metin", "basari-zemin", 4.5], ["uyari-metin", "uyari-zemin", 4.5], ["hata-metin", "hata-zemin", 4.5], ]; // Turetme kurali 1: basamak aynalamasi. Aciklik rampasi ters cevrilir. const AYNA = { "000": "900", "050": "800", 100: "700", 200: "600", 300: "500", 400: "400", 500: "300", 600: "200", 700: "100", 800: "050", 900: "000" }; const aynala = (kaynak) => Object.fromEntries(Object.entries(kaynak).map(([rol, [aile, b]]) => [rol, [aile, AYNA[b]]])); // Turetme kurali 2: marka temasi. Yalniz "ana" ailesinin ton ve doygunlugu degisir. const markaAilesi = (ton, doygunluk) => ({ ...AILE, ana: [ton, doygunluk] }); function denetle(rolTablosu, aileTablosu) { const renk = (rol) => { const [aile, b] = rolTablosu[rol]; return hslRgb(aileTablosu[aile][0], aileTablosu[aile][1], ACIKLIK[b]); }; return SOZLESME.map(([on, zemin, esik]) => { const k = kontrast(renk(on), renk(zemin)); return { on, zemin, esik, k, gecti: k >= esik }; }); } const kalanlar = (sonuc) => sonuc.filter((s) => !s.gecti); const yaz = (baslik, sonuc) => { console.log(`\n${baslik}`); console.log("on plan zemin oran esik sonuc"); for (const s of sonuc) { console.log( `${s.on.padEnd(24)} ${s.zemin.padEnd(22)} ${s.k.toFixed(2).padStart(5)} ${s.esik.toFixed(1).padStart(6)} ${s.gecti ? "gecti" : "KALDI"}` ); } console.log(`esigi gecemeyen: ${kalanlar(sonuc).length} / ${sonuc.length}`); }; // Ikinci sozlesme: durum turevleri tabandan ayirt edilebilmeli (Etkilesim Durumlari: 1.2). const AYRIM = [ ["eylem-ana-uzerine", "eylem-ana"], ["eylem-ana-basili", "eylem-ana-uzerine"], ["yuzey-ikincil", "yuzey"], ["eylem-devre-disi", "yuzey"], ]; const AYRIM_ESIGI = 1.2; function ayrimDenetle(rolTablosu, aileTablosu) { const renk = (rol) => { const [aile, b] = rolTablosu[rol]; return hslRgb(aileTablosu[aile][0], aileTablosu[aile][1], ACIKLIK[b]); }; return AYRIM.map(([durum, taban]) => { const k = kontrast(renk(durum), renk(taban)); return { durum, taban, k, gecti: k >= AYRIM_ESIGI }; }); } const ayrimYaz = (baslik, sonuc) => { console.log(`\n${baslik}`); console.log("durum rolu taban rol oran esik sonuc"); for (const s of sonuc) { console.log( `${s.durum.padEnd(24)} ${s.taban.padEnd(22)} ${s.k.toFixed(3).padStart(5)} ${AYRIM_ESIGI.toFixed(1).padStart(6)} ${s.gecti ? "gecti" : "KALDI"}` ); } console.log(`ayirt edilemeyen: ${sonuc.filter((s) => !s.gecti).length} / ${sonuc.length}`); }; console.log("--- 1. acik tema (kaynak) ---"); yaz("acik tema sozlesmesi", denetle(ACIK, AILE)); ayrimYaz("acik tema durum ayrimi", ayrimDenetle(ACIK, AILE)); console.log("\n--- 2. aynalamayla turetilen koyu tema ---"); const KOYU = aynala(ACIK); yaz("koyu tema sozlesmesi (ham turetme)", denetle(KOYU, AILE)); ayrimYaz("koyu tema durum ayrimi (ham turetme)", ayrimDenetle(KOYU, AILE)); console.log("\n--- 3. koyu temada duzeltme ---"); // Aynalama, yalniz kalan eslesmelerin basamagini kaydirir; kural degil istisna listesidir. const KOYU_DUZELTME = { kenarlik: ["notr", "400"], "eylem-ana": ["ana", "300"], "eylem-ana-uzeri": ["notr", "900"], "eylem-ana-uzerine": ["ana", "200"], "eylem-ana-basili": ["ana", "100"], "odak-halkasi": ["ana", "300"], }; const KOYU2 = { ...KOYU, ...KOYU_DUZELTME }; yaz("koyu tema sozlesmesi (duzeltmeden sonra)", denetle(KOYU2, AILE)); ayrimYaz("koyu tema durum ayrimi (duzeltmeden sonra)", ayrimDenetle(KOYU2, AILE)); console.log(`duzeltme sayisi: ${Object.keys(KOYU_DUZELTME).length} rol / ${Object.keys(ACIK).length}`); console.log("\n--- 4. marka temasi: yalniz ana ailenin tonu degisir ---"); for (const [ad, ton, doygunluk] of [["mor", 276, 62], ["yesil", 145, 62], ["turuncu", 28, 62]]) { const aileler = markaAilesi(ton, doygunluk); const acik = kalanlar(denetle(ACIK, aileler)); const koyu = kalanlar(denetle(KOYU2, aileler)); console.log( `${ad.padEnd(8)} ton ${String(ton).padStart(3)} acik temada kalan: ${acik.length} koyu temada kalan: ${koyu.length}` + (acik.length ? " (" + acik.map((s) => s.on + "/" + s.zemin).join(", ") + ")" : "") ); } console.log("\n--- 5. kapsam: rol x tema hucreleri ---"); const TEMALAR = { acik: ACIK, koyu: KOYU2 }; let eksik = 0; for (const rol of Object.keys(ACIK)) { for (const [ad, tablo] of Object.entries(TEMALAR)) if (!(rol in tablo)) { eksik++; console.log(` eksik: ${rol} / ${ad}`); } } console.log(`tanimli hucre: ${Object.keys(ACIK).length * Object.keys(TEMALAR).length - eksik} / ${Object.keys(ACIK).length * Object.keys(TEMALAR).length}`); console.log("\n--- 6. bicem cikti (ilk dort rol) ---"); const bicem = (secici, tablo, aileler) => [`${secici} {`] .concat(Object.entries(tablo).slice(0, 4).map(([rol, [aile, b]]) => ` --renk-${rol}: ${onaltilik(hslRgb(aileler[aile][0], aileler[aile][1], ACIKLIK[b]))};`)) .concat(["}"]).join("\n"); console.log(bicem(":root", ACIK, AILE)); console.log(bicem('[data-tema="koyu"]', KOYU2, AILE));
--- 1. acik tema (kaynak) ---
acik tema sozlesmesi
on plan zemin oran esik sonuc
metin-birincil yuzey 15.74 4.5 gecti
metin-birincil yuzey-ikincil 14.70 4.5 gecti
metin-ikincil yuzey 5.89 4.5 gecti
metin-birincil yuzey-secili 12.82 4.5 gecti
kenarlik yuzey 4.11 3.0 gecti
eylem-ana yuzey 6.50 3.0 gecti
eylem-ana-uzeri eylem-ana 6.50 4.5 gecti
eylem-ana-uzeri eylem-ana-uzerine 9.05 4.5 gecti
eylem-ana-uzeri eylem-ana-basili 12.68 4.5 gecti
odak-halkasi yuzey 6.50 3.0 gecti
basari-metin basari-zemin 7.54 4.5 gecti
uyari-metin uyari-zemin 7.57 4.5 gecti
hata-metin hata-zemin 7.21 4.5 gecti
esigi gecemeyen: 0 / 13
acik tema durum ayrimi
durum rolu taban rol oran esik sonuc
eylem-ana-uzerine eylem-ana 1.393 1.2 gecti
eylem-ana-basili eylem-ana-uzerine 1.401 1.2 gecti
yuzey-ikincil yuzey 1.071 1.2 KALDI
eylem-devre-disi yuzey 1.459 1.2 gecti
ayirt edilemeyen: 1 / 4
--- 2. aynalamayla turetilen koyu tema ---
koyu tema sozlesmesi (ham turetme)
on plan zemin oran esik sonuc
metin-birincil yuzey 15.74 4.5 gecti
metin-birincil yuzey-ikincil 11.80 4.5 gecti
metin-ikincil yuzey 10.78 4.5 gecti
metin-birincil yuzey-secili 9.05 4.5 gecti
kenarlik yuzey 8.23 3.0 gecti
eylem-ana yuzey 10.29 3.0 gecti
eylem-ana-uzeri eylem-ana 10.29 4.5 gecti
eylem-ana-uzeri eylem-ana-uzerine 12.82 4.5 gecti
eylem-ana-uzeri eylem-ana-basili 14.63 4.5 gecti
odak-halkasi yuzey 10.29 3.0 gecti
basari-metin basari-zemin 5.02 4.5 gecti
uyari-metin uyari-zemin 5.09 4.5 gecti
hata-metin hata-zemin 7.21 4.5 gecti
esigi gecemeyen: 0 / 13
koyu tema durum ayrimi (ham turetme)
durum rolu taban rol oran esik sonuc
eylem-ana-uzerine eylem-ana 1.246 1.2 gecti
eylem-ana-basili eylem-ana-uzerine 1.141 1.2 KALDI
yuzey-ikincil yuzey 1.333 1.2 gecti
eylem-devre-disi yuzey 2.670 1.2 gecti
ayirt edilemeyen: 1 / 4
--- 3. koyu temada duzeltme ---
koyu tema sozlesmesi (duzeltmeden sonra)
on plan zemin oran esik sonuc
metin-birincil yuzey 15.74 4.5 gecti
metin-birincil yuzey-ikincil 11.80 4.5 gecti
metin-ikincil yuzey 10.78 4.5 gecti
metin-birincil yuzey-secili 9.05 4.5 gecti
kenarlik yuzey 5.75 3.0 gecti
eylem-ana yuzey 7.63 3.0 gecti
eylem-ana-uzeri eylem-ana 7.63 4.5 gecti
eylem-ana-uzeri eylem-ana-uzerine 10.29 4.5 gecti
eylem-ana-uzeri eylem-ana-basili 12.82 4.5 gecti
odak-halkasi yuzey 7.63 3.0 gecti
basari-metin basari-zemin 5.02 4.5 gecti
uyari-metin uyari-zemin 5.09 4.5 gecti
hata-metin hata-zemin 7.21 4.5 gecti
esigi gecemeyen: 0 / 13
koyu tema durum ayrimi (duzeltmeden sonra)
durum rolu taban rol oran esik sonuc
eylem-ana-uzerine eylem-ana 1.349 1.2 gecti
eylem-ana-basili eylem-ana-uzerine 1.246 1.2 gecti
yuzey-ikincil yuzey 1.333 1.2 gecti
eylem-devre-disi yuzey 2.670 1.2 gecti
ayirt edilemeyen: 0 / 4
duzeltme sayisi: 6 rol / 19
--- 4. marka temasi: yalniz ana ailenin tonu degisir ---
mor ton 276 acik temada kalan: 0 koyu temada kalan: 0
yesil ton 145 acik temada kalan: 1 koyu temada kalan: 0 (eylem-ana-uzeri/eylem-ana)
turuncu ton 28 acik temada kalan: 0 koyu temada kalan: 0
--- 5. kapsam: rol x tema hucreleri ---
tanimli hucre: 38 / 38
--- 6. bicem cikti (ilk dort rol) ---
:root {
--renk-yuzey: #ffffff;
--renk-yuzey-ikincil: #f7f7f8;
--renk-yuzey-secili: #dee9f7;
--renk-kenarlik: #757e8a;
}
[data-tema="koyu"] {
--renk-yuzey: #212327;
--renk-yuzey-ikincil: #34383d;
--renk-yuzey-secili: #1e4980;
--renk-kenarlik: #969da6;
}
Aynalama Neyi Geçer, Neyi Kaçırır
Birinci bölüm açık temanın durumunu tekrarlar: eşik sözleşmesinin on üç eşleşmesi de geçiyor, durum ayrımında ise ikincil yüzey 1.071 oranla kalıyor. Bu, Renk Belirteçleri dersinde saptanan kusurdur ve burada bilinçli olarak korunmuştur — türetmenin kaynağı kusursuz bir tablo değil, gerçek tablodur.
İkinci bölüm ham aynalamanın sonucudur ve iki şey gösterir. Eşik sözleşmesinin tamamı koyu temada da geçiyor; dahası oranlar genellikle yükseliyor. İkincil yüzeyin açık temada geçemediği ayrım eşiği koyu temada 1.333 ile geçiyor: aynı kural, aynı roller, farklı sonuç. Bir temada doğrulanmış bir kararın diğerinde de doğru olduğunu varsaymanın neden yanlış olduğu buradan görünür.
Aynı bölüm kuralın kaçırdığı yeri de gösteriyor. Basılı durum ile üzerine gelme durumu
arasındaki oran koyu temada 1.141’e düşüyor ve eşiğin altında kalıyor. Nedeni rampanın
yapısıdır: aynalama 700 ve 800 basamaklarını 100 ve 050’ye taşır, bu iki basamak
arasındaki açıklık farkı ise diğer uçtakinden küçüktür. Kural bozuk değildir; kuralın
varsayımı — rampanın bakışımlı olması — yalnızca yaklaşık doğrudur.
Düzeltme Bir İstisna Listesidir
Üçüncü bölüm düzeltmeyi uygular. Düzeltme, kuralı değiştirmez; kuralın ürettiği tablonun üzerine yazılan altı satırlık bir istisna listesidir. Altı rol on dokuz rolün yaklaşık üçte biridir ve bu oran kuralın işe yaradığının kanıtıdır: on üç rol hiçbir müdahale olmadan doğru değeri aldı.
İstisnanın liste olarak tutulması iki şey sağlar. Denetim, istisnaların da sözleşmeden geçtiğini gösterir — düzeltmeden sonra iki sözleşme de sıfır ihlalle kapanıyor. Gözden geçirme ise listeye bakarak kuralın nerede yetmediğini görür; bu bilgi bir sonraki rampa düzenlemesinin girdisidir. Değerler tabloya tek tek yazılsaydı bu ayrım kaybolurdu: hangi değerin kuraldan, hangisinin karardan geldiği görünmez olurdu.
Marka Teması: Değişen Tek Ailenin Sınanması
Dördüncü bölüm ikinci türetme kuralını uygular. Marka teması, rol tablosunu hiç
değiştirmeden yalnız ana ailesinin tonunu ve doygunluğunu değiştirir. Rol adları, basamak
bağlantıları ve sözleşme aynı kalır; değişen tek şey ilkel katmandaki iki sayıdır.
Sonuç, marka renginin serbest bir seçim olmadığını gösteriyor. Mor ve turuncu tonlar iki
temada da sözleşmeyi geçiyor. Yeşil ton açık temada bir eşleşmeyi düşürüyor: birincil eylemin
üzerindeki metin, yeşil 600 basamağının üzerinde 4.5 eşiğini tutturamıyor. Bu bir tasarım
tercihi değil, ölçülebilir bir kısıttır ve iki yolu vardır — ya eylem rolü daha koyu bir
basamağa bağlanır ya da eylem üzeri metni ailenin kendi açık ucundan seçilir. Hangisi
seçilirse seçilsin, karar marka temasının belirtecine yazılır ve denetim bir sonraki koşumda
bunu doğrular.
Kapsam ve Çıktı
Beşinci bölüm kapsamı sayar: on dokuz rol ile iki temanın çarpımı otuz sekiz hücredir ve hepsi tanımlıdır. Renk Belirteçleri dersindeki on iki boş hücre, elle doldurularak değil kural yazılarak kapandı. Kapsam ölçümü bir tema eklendiğinde de aynı biçimde çalışır: yeni temanın kuralı yazıldığı anda bütün roller karşılığını alır.
Altıncı bölüm belirteç kaynağından biçem çıktısını üretir. Temalar arasındaki fark tek bir yerde toplanır: kök seçicide açık temanın değerleri, tema özniteliğini taşıyan seçicide koyu temanın değerleri. Bileşenler hiçbir tema bilgisi taşımaz; yalnız rol adını okurlar. Bu, Biçem Mimarisi dersinde kurulan özel özellik kalıbının sistem düzeyindeki karşılığıdır.
İşletim sistemi tercihine uyum bu yapının üzerine tek bir ortam sorgusuyla eklenir; tercihin kendisi Kullanıcı Tercihleri dersinde ele alınmıştı. Sistemin sorumluluğu tercihi okumak değil, tercihin karşılığı olan tablonun eksiksiz ve denetlenmiş olmasıdır.
Özet
- Tema, rol başına yazılmış bir değer listesi değil, kaynak tablodan türetilen bir eşlemedir; kural bir kez yazılır ve yeni roller onu kendiliğinden izler.
- Aynalama kuralı eşik sözleşmesini korur ama kontrast oranlarını korumaz: açık temada ayrım eşiğini geçemeyen ikincil yüzey koyu temada geçiyor, basılı durum ise tersine 1.141’e düşüp kalıyor. Her sözleşme her temada yeniden çalıştırılır.
- Düzeltme kuralı değiştirmez; kuralın üzerine yazılan bir istisna listesidir. On dokuz rolün altısı düzeltildi, on üçü kuraldan geldi ve hangisinin nereden geldiği kayıtlıdır.
- Marka teması yalnız bir ailenin ton ve doygunluğunu değiştirir; bu iki sayı sözleşmeyi düşürebilir — yeşil tonda birincil eylem üzerindeki metin açık temada eşiği tutturamıyor.
- Kapsam, rol ve tema kümelerinin çarpımı üzerinden ölçülür: otuz sekiz hücrenin tamamı tanımlı. Biçem çıktısında temalar arası fark tek seçicide toplanır, bileşenler yalnız rol adını okur.
Sonraki Adım
Temel katman bu dersle kapanıyor: değerler adlandırıldı, ölçeklere bağlandı ve temalar tek
kuraldan türetilip denetlendi. Ancak bir sistemin kullanıcısı belirteci değil bileşeni
kullanır. Ödünç alma düğmesini yazan kişi --renk-eylem-ana değerini değil, “birincil düğme”
bileşenini arar; bulamazsa kendi düğmesini yazar ve sistemin kapsamı sessizce daralır.
Sonraki konu bu katmanı ele alır ve ilk dersi kataloğun kendisiyle başlar: hangi bileşenler
sisteme girer, bir bileşenin olgunluk düzeyi nasıl ölçülür ve kataloğa girmeyen bir ihtiyaç
nasıl karşılanır?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.