---
title: 'Arayüz Tasarımının Temelleri'
source: 'https://academia.sh/tr/kurslar/arayuz-temelleri'
language: tr
updated: '2026-08-17T04:41:14+00:00'
license: 'CC BY-SA 4.0'
lessons: 22
outcomes:
    - 'Görsel hiyerarşiyi tipografi, aralık ve karşıtlıkla kurar.'
    - 'Izgara ve düzen kararlarını içerik yapısına dayandırır.'
    - 'Bileşen durumlarını eksiksiz tasarlar.'
---

# Arayüz Tasarımının Temelleri

Görsel hiyerarşi, ızgara ve aralık, tipografi ve renk kararlarını algısal temellere ve ölçülebilir eşiklere bağlayan; bileşen durumlarını eksiksiz tanımlamayı öğreten giriş kursu.

## Görsel İlkeler

Hiyerarşi, gruplama, karşıtlık, hizalama ve yinelenmenin algısal temelleri; her görsel kararın hangi ayrımı hangi maliyetle ürettiği.

### Görsel Hiyerarşi

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/gorsel-hiyerarsi

Dikkatin sıralanması olarak hiyerarşi; boyut, ağırlık, kontrast ve konum kanallarının ölçülebilir katkısı ve düzeyler arası ayrımın denetlenmesi.

Arayüz tasarımı, dışarıdan bakıldığında beğeni işi gibi görünür. Bir ekran "iyi" ya da "kötü"
bulunur ve gerekçe sorulduğunda yanıt çoğu zaman kişisel bir tercihe dayanır. Bu kurs bu yaklaşımı
reddeder. Her görsel karar bir işlev üstlenir: bir ayrım üretir, bir ilişki kurar ya da bir sırayı
ilan eder. Karar gerekçelendirilebiliyorsa tasarımdır; gerekçelendirilemiyorsa süslemedir. Kurs
boyunca sorulacak soru "güzel mi" değil, **hangi ayrımı hangi maliyetle üretiyor** olacaktır.

Örnekler tek bir arayüz üzerinden ilerleyecek: bir kütüphanenin **katalog arayüzü**. Arama
alanı, arama sonuçları listesi, tek bir kaydın ayrıntı görünümü ve ödünç alma eylemi. Bu
arayüz her derste bir adım daha kararlı hâle gelecek. İlk soru en temel olanıdır: bir ekrana
bakan kişi neyi önce görür, bunu kim belirler?

#### Hiyerarşi Bir Okuma Sırası Sözüdür

**Görsel hiyerarşi (visual hierarchy)**, bir düzlemdeki ögelerin dikkat çekme gücüne göre
sıralanmasıdır. Tasarımcı bu sıralamayı doğrudan denetleyemez; ancak ögelerin görsel
özelliklerini değiştirerek sıralamayı yönlendirebilir.

Bu tanımın iki sonucu vardır. Birincisi, hiyerarşi bir özellik değil bir **ilişkidir**. Bir
başlığın kendi başına "güçlü" olması diye bir şey yoktur; ancak çevresindeki ögelere göre
güçlü olabilir. Bütün metinler 32 piksele çıkarılırsa hiyerarşi yükselmez, yok olur.

İkincisi, hiyerarşi bir **söz**tür ve tutulmadığında bedeli okurundur. Bir arayüz, kayıt
başlığını rafta bulunma durumundan daha belirgin gösteriyorsa, kataloğa "hangi kitaplar
şu an rafta" sorusuyla gelen kişi her kayıtta iki kez bakmak zorunda kalır. Yanlış hiyerarşi
bilgiyi silmez; onu bulmanın maliyetini artırır.

Hiyerarşi kurmadan önce yanıtlanması gereken soru şudur: bu ekranda **hangi görev** ilk
sırada? Katalog arayüzünde arama sonucu listesinin görevi "bu sonuç aradığım şey mi"
sorusunu ucuzlatmaktır. Öyleyse en güçlü öge kayıt başlığıdır, yazar adı ikinci sıradadır,
raf kodu ise ancak kayıt seçildikten sonra gerekir.

#### Dikkati Yönlendiren Kanallar

Bir ögeyi diğerinden ayırmak için birbirinden bağımsız birkaç **kanal** kullanılabilir. Her
kanalın kendi maliyeti ve kendi sınırı vardır.

- **Boyut.** En güçlü ve en pahalı kanal. Bir ögeyi büyütmek yalnızca onu öne çıkarmaz,
  aynı zamanda ekranda yer tüketir ve altındaki her şeyi aşağı iter.
- **Ağırlık.** Yazının kalınlığı. Yer tüketmez, bu yüzden yoğun listelerde boyuttan daha
  kullanışlıdır. Sınırı, kullanılan yazı tipinin sunduğu ağırlık sayısıdır.
- **Kontrast.** Metnin zeminden ayrışma derecesi. Ayrımı zayıflatarak da çalışır: ikincil
  bilgiyi soluklaştırmak, birincil bilgiyi büyütmeden öne çıkarır. Bedeli erişilebilirlik
  eşiğidir; bu eşik Karşıtlık ve Erişilebilirlik dersinde ölçülecektir.
- **Konum.** Okuma yönünün başlangıcına yakın olan öge daha erken taranır. Bedava değildir:
  konum, düzenin geri kalanını kısıtlar.
- **Boşluk.** Bir ögenin çevresindeki boşluk, o ögeyi yalıtır ve öne çıkarır. Sonraki
  derslerin konusu budur.

Kanalların bağımsız olması önemlidir. Aynı ayrımı iki kanalda birden ifade etmek, ayrımı
iki kat güçlendirmez ama **dayanıklı** kılar: kanallardan biri işlemez hâle geldiğinde
diğeri ayakta kalır. Renk körlüğü, düşük parlaklıkta okuma, küçük ekran ve yazı tipinin
yüklenememesi bu "işlemez hâle gelme" durumlarının hepsidir.

#### Ayrımı Ölçmek

Hiyerarşi tartışması, "yeterince belirgin mi" sorusu öznel kaldığı sürece sonuçsuz kalır.
Soruyu ölçülebilir kılmanın yolu, komşu iki düzey arasındaki farkı kanal kanal hesaplamak ve
bir ölçütle karşılaştırmaktır.

Aşağıdaki program katalog arayüzünün metin düzeylerini alır, her düzeyin zeminle
**kontrast oranı (contrast ratio)** değerini hesaplar ve komşu düzeyler arasındaki farkları
listeler. Kontrast oranı, CSS ile Görsel Sunum kursunda tanımlanan **bağıl parlaklık
(relative luminance)** üzerinden hesaplanır.

```js
// hiyerarsi.mjs — metin duzeyleri arasindaki ayrimi kanal kanal olcer

const duzeyler = [
  { ad: "sayfa basligi",   boyut: 32, agirlik: 700, renk: "#1a1a1a" },
  { ad: "bolum basligi",   boyut: 20, agirlik: 600, renk: "#1a1a1a" },
  { ad: "kayit basligi",   boyut: 18, agirlik: 600, renk: "#1a1a1a" },
  { ad: "yazar",           boyut: 16, agirlik: 400, renk: "#3d3d3d" },
  { ad: "ustveri",         boyut: 14, agirlik: 400, renk: "#5c5c5c" },
];

const ZEMIN = "#ffffff";

// WCAG bagil parlaklik ve kontrast orani
function kanal(v) {
  const s = v / 255;
  return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
}
function parlaklik(hex) {
  const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
  return 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);
}

// Kurs olcutu: iki komsu duzey en az bir kanalda su esikleri asmalidir.
const ESIK = { boyutOrani: 1.15, agirlikFarki: 200, kontrastOrani: 1.5 };

console.log("duzey            boyut  agirlik  kontrast(zemin)");
for (const d of duzeyler) {
  console.log(
    `${d.ad.padEnd(16)} ${String(d.boyut).padStart(4)}px ${String(d.agirlik).padStart(7)}  ` +
      `${kontrast(d.renk, ZEMIN).toFixed(2)}:1`
  );
}

console.log("");
console.log("komsu duzey cifti              boyut orani  agirlik farki  kontrast orani  ayrim");
for (let i = 0; i < duzeyler.length - 1; i++) {
  const ust = duzeyler[i];
  const alt = duzeyler[i + 1];
  const boyutOrani = ust.boyut / alt.boyut;
  const agirlikFarki = ust.agirlik - alt.agirlik;
  const kUst = kontrast(ust.renk, ZEMIN);
  const kAlt = kontrast(alt.renk, ZEMIN);
  const kontrastOrani = kUst / kAlt;

  const gecen = [];
  if (boyutOrani >= ESIK.boyutOrani) gecen.push("boyut");
  if (agirlikFarki >= ESIK.agirlikFarki) gecen.push("agirlik");
  if (kontrastOrani >= ESIK.kontrastOrani) gecen.push("kontrast");

  const etiket = `${ust.ad} > ${alt.ad}`;
  console.log(
    `${etiket.padEnd(30)} ${boyutOrani.toFixed(3).padStart(11)} ` +
      `${String(agirlikFarki).padStart(14)} ${kontrastOrani.toFixed(3).padStart(15)}  ` +
      (gecen.length ? gecen.join("+") : "ZAYIF")
  );
}
```

```
duzey            boyut  agirlik  kontrast(zemin)
sayfa basligi      32px     700  17.40:1
bolum basligi      20px     600  17.40:1
kayit basligi      18px     600  17.40:1
yazar              16px     400  10.86:1
ustveri            14px     400  6.69:1

komsu duzey cifti              boyut orani  agirlik farki  kontrast orani  ayrim
sayfa basligi > bolum basligi        1.600            100           1.000  boyut
bolum basligi > kayit basligi        1.111              0           1.000  ZAYIF
kayit basligi > yazar                1.125            200           1.602  agirlik+kontrast
yazar > ustveri                      1.143              0           1.624  kontrast
```

Eşik değerleri bir doğa yasası değildir; bu kursun benimsediği ve denetlenebilir olsun diye
sayısallaştırdığı ölçütlerdir. Önemli olan sayının kendisi değil, kararın bir sayıya
bağlanmış olmasıdır: "yeterince belirgin görünmüyor" cümlesi tartışılabilir, "boyut oranı
1.111, eşik 1.15" cümlesi denetlenebilir.

Çıktının ikinci satırı asıl bulgudur. Bölüm başlığı ile kayıt başlığı arasında hiçbir kanal
eşiği aşmıyor: boyutları neredeyse aynı, ağırlıkları eşit, renkleri özdeş. Bu iki düzey
tasarım belgesinde ayrı satırlarda yazılıdır, ekranda ise aynı düzeydir. Hiyerarşi
belgede vardır, arayüzde yoktur.

Bu bulgunun çözümü tek başına "bölüm başlığını büyütmek" değildir. Ayrım gerçekten gerekli
mi diye sorulur. Katalog arayüzünde arama sonuçları listesinin tek bir bölümü varsa, bölüm
başlığı bilgi taşımayan bir etikettir ve kaldırılabilir. Bir hiyerarşi sorununun en ucuz
çözümü, çoğu zaman düzey sayısını azaltmaktır.

#### Tek Kanala Yaslanmanın Maliyeti

Çıktının son satırı bir başka zayıflığı gösteriyor: yazar ile üstveri arasındaki ayrım
yalnızca kontrast kanalında duruyor. Boyut oranı 1.143, eşiğin altında; ağırlık farkı sıfır.
Ayrım tek bacaklıdır.

Tek bacaklı ayrımlar öngörülebilir biçimde çöker. Kontrasta yaslanan bir ayrım, güneş
altındaki bir ekranda ya da düşük parlaklığa ayarlanmış bir panelde ortadan kalkar. Renge
yaslanan bir ayrım, renk ayrımı sınırlı olan okurda kalkar. Ağırlığa yaslanan bir ayrım,
yazı tipinin yalnızca tek ağırlığı yüklenebildiğinde kalkar.

Kural şu biçimde yazılabilir: **taşıdığı bilgi kritikse, ayrım en az iki kanalda ifade
edilir.** Kayıt başlığı ile yazar arasındaki ayrım bu koşulu sağlıyor (ağırlık ve kontrast).
Yazar ile üstveri arasındaki ayrım sağlamıyor; üstverinin yazı boyutu 13 piksele indirilirse
boyut oranı 16/13, yani yaklaşık 1.231 olur ve eşiği geçer.

#### Hiyerarşi Kaç Düzey Taşır

Düzey eklemek ücretsiz değildir. Her yeni düzey, mevcut düzeylerin arasına sıkışır ve
komşularıyla arasındaki farkı daraltır. Beş düzeyli bir ölçekte boyut aralığı 14 ile 32
piksel arasındaysa, altıncı düzey ya alt ucu okunamayacak kadar küçültür ya da bir düzey
çiftinin ayrımını eşiğin altına düşürür.

Bu yüzden pratikte düzey sayısı azdır: bir birincil, bir ikincil, bir üçüncül ve gerekirse
bir de bastırılmış düzey. Aynı ekranda birbiriyle yarışan iki birincil öge varsa, ikisi de
birincil değildir.

Aynı sınır eylemler için de geçerlidir. Katalog kaydında "Ödünç al" birincil eylemse,
"Listeye ekle" ve "Paylaş" onunla aynı görsel güçte olamaz. Üç düğme de dolgulu ve renkli
çizildiğinde, kullanıcı hangisinin beklenen eylem olduğunu arayüzden okuyamaz; kararı
metinleri okuyarak verir. Bu, hiyerarşinin işini yapmadığı anlamına gelir.

#### Özet

- Görsel hiyerarşi, ögelerin dikkat çekme gücüne göre sıralanmasıdır; bir özellik değil
  ögeler arası bir ilişkidir ve her şeyi güçlendirmek hiyerarşiyi yok eder.
- Hiyerarşi, ekranın birincil görevinden türetilir: en güçlü öge, o görevi ucuzlatan ögedir.
- Ayrım boyut, ağırlık, kontrast, konum ve boşluk kanallarında ifade edilir; kanallar
  bağımsızdır ve her birinin ayrı bir maliyeti vardır.
- İki düzey arasındaki ayrım sayısal olarak denetlenebilir; ölçüt keyfî seçilse bile karar
  tartışmadan denetime taşınır.
- Kritik bilginin ayrımı en az iki kanalda ifade edilir; tek kanallı ayrımlar o kanalın
  işlemediği koşullarda tümüyle kaybolur.
- Düzey sayısı arttıkça komşu düzeyler arası fark daralır; hiyerarşi sorunlarının en ucuz
  çözümü düzey eklemek değil, düzey silmektir.

#### Sonraki Adım

Bu ders ögeleri birbirinden ayırmayı ele aldı. Arayüzün ikinci işi ise ögeleri birbirine
**bağlamaktır**: kayıt başlığıyla yazar adının aynı kayda ait olduğu, raf kodunun ise ayrı
bir bilgi kümesi olduğu ekranda nasıl anlaşılır? Sonraki ders, bu ilişkinin çizgi ve
çerçeveyle değil boşlukla kurulduğunu gösterir ve boşluk oranlarının algılanan gruplamayı
nasıl belirlediğini hesapla ortaya koyar.

### Yakınlık ve Gruplama

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/yakinlik-ve-gruplama

İlişkinin boşlukla ilan edilmesi; gruplamanın mutlak boşlukla değil boşluk oranıyla kurulması ve iç içe gruplama düzeylerinin sayısal denetimi.

Önceki ders ögeleri birbirinden ayırmayı ele aldı: hangi öge önce okunur ve bu sıra hangi
kanallarda ifade edilir. Arayüzün ikinci işi bunun tersidir. Katalog kaydında başlık, yazar adı ve
raf kodu ayrı ayrı çizilir ama aynı kitaba aittir. Bir sonraki kaydın başlığı yalnızca birkaç
piksel aşağıdadır ve başka bir kitaba aittir. Bu iki durumun farkı ekranda neyle anlaşılır?

Yanıt çizgi ya da çerçeve değil, boşluktur. Bu ders boşluğun ilişkiyi nasıl kurduğunu,
kurulan ilişkinin neden mutlak piksel değerine değil boşluklar arası **orana** bağlı
olduğunu ve bu oranın nasıl denetleneceğini gösterir.

#### Yakınlık İlişkiyi İlan Eder

**Yakınlık (proximity)**, birbirine yakın duran ögelerin tek bir birim olarak algılanması
eğilimidir. Bu eğilim, algı psikolojisinde **Gestalt ilkeleri (Gestalt principles)** adıyla
toplanan bir grubun üyesidir; aynı grupta benzer görünen ögelerin birlikte algılanması
(**benzerlik**, similarity) ve ortak bir zemin ya da çerçeve paylaşan ögelerin birlikte
algılanması (**ortak bölge**, common region) da bulunur.

Bu ilkelerin arayüz tasarımı için önemi kuramsal değil pratiktir: ilkeler bir seçenek
sunmaz, bir kısıt getirir. Ögeler bir düzleme yerleştirildiği anda gruplanmış olurlar.
Tasarımcının kararı "gruplayayım mı" değil, "algılanan gruplama amaçlanan gruplamayla
örtüşüyor mu" sorusudur. Boşluk verilmediğinde gruplama ortadan kalkmaz; rastgele bir
gruplama yerleşir.

Yakınlık ilkeleri arasında bir güç sırası vardır. İki öge birbirine yakın ama farklı renkteyse,
yakınlık benzerliği yener: ögeler tek grup olarak okunur. Katalog kaydında başlık koyu ve kalın,
yazar adı soluk ve normal ağırlıktaysa, aralarında 4 piksel varken bu ikisi hâlâ tek kayıt olarak
algılanır. Bu, hiyerarşi ile gruplamanın çakışmadığı anlamına gelir: bir grubun içindeki ögeler
birbirinden farklı ağırlıkta olabilir; grup kimliğini taşıyan şey boşluktur.

#### Gruplama Mutlak Boşlukla Değil Oranla Kurulur

Yakınlık göreli bir ölçüdür. "24 piksel boşluk kayıtları ayırır" cümlesi kendi başına
doğru ya da yanlış değildir; kaydın içindeki boşluğun kaç piksel olduğu bilinmeden
değerlendirilemez. İç boşluk 4 pikselse 24 piksel açık bir sınırdır; iç boşluk 20 pikselse
aynı 24 piksel hiçbir şey ayırmaz.

Bunu denetlenebilir kılmak için bir ölçüt gerekir. Bu kurs şu ölçütü benimser: bir boşluk,
aynı yığındaki en küçük boşluğun **en az iki katıysa** grup sınırı sayılır. Aşağıdaki
program katalog sonuç listesinin iki farklı aralık kararını bu ölçütle sınar.

```js
// 02-gruplama.mjs — dikey yiginda bosluk oranina gore gruplama denetimi

// Katalog sonuc listesinin iki satiri: her kayit baslik + yazar + ustveri tasir.
// Her oge (ad, yukseklik, ustBosluk) verilir; ustBosluk bir onceki ogeden uzakligidir.
const tasarimA = [
  { ad: "kayit-1 baslik", yukseklik: 24, ustBosluk: 0, grup: 1 },
  { ad: "kayit-1 yazar", yukseklik: 20, ustBosluk: 4, grup: 1 },
  { ad: "kayit-1 ustveri", yukseklik: 18, ustBosluk: 4, grup: 1 },
  { ad: "kayit-2 baslik", yukseklik: 24, ustBosluk: 24, grup: 2 },
  { ad: "kayit-2 yazar", yukseklik: 20, ustBosluk: 4, grup: 2 },
  { ad: "kayit-2 ustveri", yukseklik: 18, ustBosluk: 4, grup: 2 },
];

const tasarimB = [
  { ad: "kayit-1 baslik", yukseklik: 24, ustBosluk: 0, grup: 1 },
  { ad: "kayit-1 yazar", yukseklik: 20, ustBosluk: 12, grup: 1 },
  { ad: "kayit-1 ustveri", yukseklik: 18, ustBosluk: 12, grup: 1 },
  { ad: "kayit-2 baslik", yukseklik: 24, ustBosluk: 16, grup: 2 },
  { ad: "kayit-2 yazar", yukseklik: 20, ustBosluk: 12, grup: 2 },
  { ad: "kayit-2 ustveri", yukseklik: 18, ustBosluk: 12, grup: 2 },
];

// Kurs olcutu: bir bosluk, yigindaki en kucuk boslugun AYRIM_ORANI katiysa
// grup siniri sayilir. Oran 2'nin altindaysa sinir belirsizdir.
const AYRIM_ORANI = 2;

function gruplandir(oge_listesi) {
  const bosluklar = oge_listesi.slice(1).map((o) => o.ustBosluk);
  const enKucuk = Math.min(...bosluklar);
  const gruplar = [[oge_listesi[0].ad]];
  const oranlar = [];
  for (let i = 1; i < oge_listesi.length; i++) {
    const oran = oge_listesi[i].ustBosluk / enKucuk;
    oranlar.push({ ad: oge_listesi[i].ad, bosluk: oge_listesi[i].ustBosluk, oran });
    if (oran >= AYRIM_ORANI) gruplar.push([oge_listesi[i].ad]);
    else gruplar[gruplar.length - 1].push(oge_listesi[i].ad);
  }
  return { enKucuk, oranlar, gruplar };
}

function rapor(baslik, oge_listesi) {
  const { enKucuk, oranlar, gruplar } = gruplandir(oge_listesi);
  const amac = new Set(oge_listesi.map((o) => o.grup)).size;
  console.log(`${baslik}  (en kucuk bosluk: ${enKucuk}px, esik orani: ${AYRIM_ORANI})`);
  for (const o of oranlar) {
    const sinir = o.oran >= AYRIM_ORANI ? "SINIR" : "grup ici";
    console.log(
      `  ${o.ad.padEnd(18)} ustunde ${String(o.bosluk).padStart(2)}px  oran ${o.oran
        .toFixed(2)
        .padStart(5)}  ${sinir}`
    );
  }
  console.log(`  amaclanan grup sayisi: ${amac}   algilanan grup sayisi: ${gruplar.length}`);
  gruplar.forEach((g, i) => console.log(`    grup ${i + 1}: ${g.join(", ")}`));
  console.log("");
}

rapor("tasarim A  grup ici 4px / ayirici 24px", tasarimA);
rapor("tasarim B  grup ici 12px / ayirici 16px", tasarimB);
```

```
tasarim A  grup ici 4px / ayirici 24px  (en kucuk bosluk: 4px, esik orani: 2)
  kayit-1 yazar      ustunde  4px  oran  1.00  grup ici
  kayit-1 ustveri    ustunde  4px  oran  1.00  grup ici
  kayit-2 baslik     ustunde 24px  oran  6.00  SINIR
  kayit-2 yazar      ustunde  4px  oran  1.00  grup ici
  kayit-2 ustveri    ustunde  4px  oran  1.00  grup ici
  amaclanan grup sayisi: 2   algilanan grup sayisi: 2
    grup 1: kayit-1 baslik, kayit-1 yazar, kayit-1 ustveri
    grup 2: kayit-2 baslik, kayit-2 yazar, kayit-2 ustveri

tasarim B  grup ici 12px / ayirici 16px  (en kucuk bosluk: 12px, esik orani: 2)
  kayit-1 yazar      ustunde 12px  oran  1.00  grup ici
  kayit-1 ustveri    ustunde 12px  oran  1.00  grup ici
  kayit-2 baslik     ustunde 16px  oran  1.33  grup ici
  kayit-2 yazar      ustunde 12px  oran  1.00  grup ici
  kayit-2 ustveri    ustunde 12px  oran  1.00  grup ici
  amaclanan grup sayisi: 2   algilanan grup sayisi: 1
    grup 1: kayit-1 baslik, kayit-1 yazar, kayit-1 ustveri, kayit-2 baslik, kayit-2 yazar, kayit-2 ustveri
```

İki tasarımın da amacı aynı: iki kayıt, iki grup. Tasarım A bunu üretir, tasarım B üretmez.
Dikkat çekici olan, tasarım B'nin **daha ferah** olmasıdır. Toplam boşluğu daha fazladır,
kayıtlar arasında 16 piksel bırakır, buna rağmen kayıt sınırını yok eder. Boşluk eklemek
gruplamayı iyileştirmez; boşluğu **farklılaştırmak** iyileştirir.

Bu, aralık kararlarının neden tek tek verilemeyeceğini de açıklar. "Kayıtlar arasına 16
piksel koyalım" kararı yalnız başına değerlendirilemez, çünkü anlamı komşu kararlara
bağlıdır. Aralık kararları bir küme olarak alınır.

#### İç İçe Gruplama Düzeyleri

Katalog ekranında tek bir gruplama düzeyi yoktur. Başlık ile yazar adı bir düzeyde, kayıt
blokları bir üst düzeyde, kayıtlar bir üstte, ekranın bölümleri en üstte gruplanır. Her
düzeyin bir alt düzeyden ayrılabilmesi gerekir; aksi hâlde hiyerarşinin bir basamağı
görünmez olur.

```js
// 02-ic-ice.mjs — ic ice gecmis gruplama duzeylerinin bosluk oranini denetler

// Katalog ekranindaki dort gruplama duzeyi, en icten en disa.
const duzeyler = [
  { ad: "satir ici (baslik-yazar)", bosluk: 4 },
  { ad: "kayit ici bloklar", bosluk: 8 },
  { ad: "kayitlar arasi", bosluk: 24 },
  { ad: "bolumler arasi", bosluk: 48 },
];

const AYRIM_ORANI = 2;

console.log("duzey                      bosluk  bir alt duzeye orani  durum");
for (let i = 0; i < duzeyler.length; i++) {
  const d = duzeyler[i];
  if (i === 0) {
    console.log(`${d.ad.padEnd(26)} ${String(d.bosluk).padStart(4)}px  ${"-".padStart(20)}  taban`);
    continue;
  }
  const oran = d.bosluk / duzeyler[i - 1].bosluk;
  const durum = oran >= AYRIM_ORANI ? "ayirir" : "BELIRSIZ";
  console.log(
    `${d.ad.padEnd(26)} ${String(d.bosluk).padStart(4)}px  ${oran.toFixed(2).padStart(20)}  ${durum}`
  );
}
```

```
duzey                      bosluk  bir alt duzeye orani  durum
satir ici (baslik-yazar)      4px                     -  taban
kayit ici bloklar             8px                  2.00  ayirir
kayitlar arasi               24px                  3.00  ayirir
bolumler arasi               48px                  2.00  ayirir
```

Dört düzey, taban boşluk 4 piksel, en dış boşluk 48 piksel. Aradaki oranların hepsi en az iki
olduğu için her düzey bir alttakinden ayrılır. Buradaki asıl bulgu sayıların kendisi değil,
sayıların **çarpımsal** ilerlemesidir: 4, 8, 24, 48. Toplamalı bir dizi (4, 8, 12, 16) aynı işi
göremez, çünkü üst düzeylere doğru oranlar hızla bire yaklaşır.

Bu gözlem, gruplama düzeyi sayısına bir sınır da koyar. Taban boşluk 4 piksel ve her düzey
en az iki katı olacaksa, altı düzey en dışta $4 \cdot 2^{5} = 128$ piksel gerektirir. Bir ekranda
dikey olarak 128 pikselin ne kadar pahalı olduğu düşünülürse, uygulanabilir gruplama düzeyi
sayısının dört civarında kaldığı görülür. Hiyerarşi düzeylerinde olduğu gibi burada da
çözüm düzey eklemek değil, gerekmeyen düzeyi kaldırmaktır.

#### Boşluk mu, Çerçeve mi

Ortak bölge ilkesi gruplamanın ikinci yoludur: ögeleri bir çerçeve içine almak ya da ortak
bir zemin rengi vermek de onları tek birim yapar. Bu yol yakınlıktan güçlüdür — çerçeve,
yakınlığı yener: kutu içindeki iki uzak öge, kutu dışındaki yakın ögeden daha ilişkili
algılanır.

Güçlü olması onu varsayılan seçenek yapmaz. Çerçevenin bedeli görsel gürültüdür. Yirmi
kayıtlık bir sonuç listesinde her kayıt çerçevelenirse ekrana 20 dikdörtgen, yani 80 çizgi
eklenir. Bu çizgilerin hiçbiri bilgi taşımaz; hepsi kayıt sınırını ilan eder. Aynı sınır
boşlukla ilan edilseydi ekrana hiçbir çizgi eklenmezdi. Boşluk, sıfır mürekkeple çalışan
tek gruplama aracıdır.

Ölçüt şu biçimde yazılabilir: **ortak bölge, yakınlığın kuramadığı ilişkiyi kurmak için
saklanır.** Yakınlığın yetmediği üç durum vardır. Birincisi, ögeler yatay ve dikey olarak
ayrışmıyorsa; bir tablonun satırlarında boşluk vermek satır yüksekliğini şişireceği için
zemin rengiyle ayırmak daha ucuz olabilir. İkincisi, grup ekranın geri kalanından farklı
bir işlev taşıyorsa; ödünç alma eyleminin bulunduğu bölüm bir eylem alanıdır ve okuma
alanından yalnız boşlukla ayrılması bilgi kaybettirir. Üçüncüsü, grup kaydırılabilir bir
alansa; sınırı görünmeyen bir kaydırma alanı içeriğin nerede bittiğini gizler.

#### Yanlış Bağlanan Etiket

Yakınlık hatasının en pahalı biçimi arama alanının bulunduğu bölümde ortaya çıkar. Katalog
arayüzünde üç süzgeç alanı vardır: yayın yılı, dil ve materyal türü. Her alanın üstünde
bir etiket, altında bir açıklama satırı bulunur.

Etiket ile alan arasındaki boşluk 8 piksel, alan ile açıklama arasındaki boşluk 8 piksel,
açıklama ile sonraki etiket arasındaki boşluk 12 piksel yapılırsa, en küçük boşluğa oran
12 / 8 = 1.5 olur ve eşiğin altında kalır. Sonuç, üç ayrı süzgecin tek bir bloğa
dönüşmesidir; okur hangi açıklamanın hangi alana ait olduğunu boşluktan okuyamaz, deneyerek
bulur.

Doğru düzenleme, etiketi alana bağlayıp grubu dışarıdan ayırmaktır: etiket–alan 4 piksel,
alan–açıklama 4 piksel, gruplar arası 24 piksel. Oran 24 / 4 = 6 olur. Bu düzenleme
kâğıt üzerinde daha "sıkışık" görünür, ekranda ise daha okunur olur; çünkü sıkışıklık
grubun içinde, ferahlık grupların arasındadır.

Aynı kural etiketin konumuna da uygulanır. Etiket alanın soluna yerleştirildiğinde, etiket
ile kendi alanı arasındaki yatay boşluk, etiket ile bir sonraki satırın dikey boşluğundan
küçük olmalıdır. İki eksende çalışan bir gruplamada oran ölçütü her iki eksende ayrı ayrı
sınanır.

#### Özet

- Yakınlık, birbirine yakın ögelerin tek birim olarak algılanmasıdır; ögeler bir düzleme
  konduğu anda gruplanır, dolayısıyla soru gruplamanın var olup olmadığı değil amaçlanan
  gruplamayla örtüşüp örtüşmediğidir.
- Gruplama mutlak boşluk değeriyle değil, komşu boşluklar arasındaki oranla kurulur; bu
  kursun ölçütü, grup sınırının en küçük boşluğun en az iki katı olmasıdır.
- Boşluk eklemek gruplamayı iyileştirmez; boşluğu farklılaştırmak iyileştirir. Toplam
  boşluğu daha fazla olan bir tasarım daha zayıf gruplayabilir.
- İç içe gruplama düzeyleri çarpımsal ilerler; toplamalı bir dizi üst düzeylerde oranı
  bire yaklaştırdığı için sınırları siler.
- Ortak bölge yakınlıktan güçlüdür ama mürekkep maliyeti taşır; yakınlığın kuramadığı
  ilişkiler için saklanır.
- Boşluk kararları tek tek değil küme olarak alınır, çünkü bir boşluğun anlamı komşu
  boşluklara bağlıdır.

#### Sonraki Adım

Bu ders ilişkiyi kurmayı ele aldı. Geriye ayrımı **ölçülü** kurmak kalıyor. Önceki iki
derste ayrım hep tek yönlü ele alındı: bir ögeyi öne çıkarmak. Oysa vurgu bir bütçedir;
ekrandaki her vurgulanmış öge, diğer vurgulanmış ögelerin payını azaltır. Sonraki ders
görsel ağırlığı alan ve parlaklık farkı üzerinden hesaplar, katalog ekranındaki vurgu
paylarını çıkarır ve üç birincil eylemin neden hiçbirini birincil bırakmadığını sayıyla
gösterir.

### Karşıtlık ve Vurgu

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/karsitlik-ve-vurgu

Karşıtlığın algısal ilke, kontrast oranının ölçülen nicelik olarak ayrılması; görsel ağırlığın alan ve parlaklık farkıyla hesaplanması ve vurgu paylarının bütçe olarak denetlenmesi.

Önceki iki ders ayrımı hep tek yönlü ele aldı: bir ögeyi öne çıkarmak. Bu bakış, ekranda
tek bir öge varmış gibi davranır. Oysa vurgu paylaşılan bir kaynaktır. Katalog kaydının
altındaki eylem satırında üç düğme vardır — "Ödünç al", "Listeye ekle", "Paylaş" — ve
üçünü de öne çıkarmak, hiçbirini öne çıkarmamakla aynı sonucu verir.

Bu ders vurguyu bir bütçe olarak ele alır. Bütçenin ölçülebilmesi için önce görsel ağırlığın
hesaplanması, hesaplanabilmesi için de karşıtlık ile kontrast oranının birbirinden
ayrılması gerekir.

#### Karşıtlık İlke, Kontrast Oranı Ölçüdür

Türkçede iki ayrı kavram aynı sözcüğe sıkışabilir, bu yüzden kurs boyunca ayrım
korunacaktır.

**Karşıtlık (contrast)** algısal bir ilkedir: iki öge arasındaki fark, o ögelerin ayrı
şeyler olduğunu ilan eder. Karşıtlık renkle kurulabileceği gibi boyutla, biçimle, yönle,
dokuyla ya da boşlukla da kurulur. Büyük bir başlığın küçük bir üstveri satırıyla ilişkisi
bir boyut karşıtlığıdır; yatay bir listedeki tek dikey öge bir yön karşıtlığıdır.

**Kontrast oranı (contrast ratio)** ise ölçülen bir niceliktir: iki rengin bağıl
parlaklıklarından türetilen ve 1:1 ile 21:1 arasında değişen tek bir sayı. Bir kontrast
oranı yalnız renk çifti için tanımlıdır; bir boyut karşıtlığının kontrast oranı yoktur.

Ayrımın pratik sonucu şudur: "bu iki öge arasında yeterli karşıtlık var mı" sorusu, tek
başına kontrast oranı ölçerek yanıtlanamaz. Kontrast oranı yalnız bir kanalı ölçer; ilke
tüm kanalları kapsar.

#### Görsel Ağırlığı Hesaplamak

Bir ögenin dikkat çekme gücü, önceki derste kanallar üzerinden niteliksel olarak ele
alındı. Eylem satırı gibi dolgulu ve çerçeveli ögelerin bulunduğu bir bölgede bu güç
sayısallaştırılabilir. Bu kursun kullandığı model şudur:

**Görsel ağırlık, zeminden ayrışan yüzeylerin alanı ile o yüzeylerin zeminle bağıl
parlaklık farkının çarpımıdır.** Dolgu ve kenar çizgisi ayrı ayrı hesaplanır, toplanır.

Model metni yok sayar; ölçtüğü şey renkli yüzeydir. Bu bir eksiklik değil, kasıtlı bir
sadeleştirmedir: eylem satırında kararı belirleyen şey düğmenin dolgusu ve çerçevesidir,
etiket metninin kendisi değil.

```js
// 03-vurgu.mjs — eylem satirindaki gorsel agirligin ve vurgu paylarinin hesabi

// Model: bir ogenin gorsel agirligi, zeminden ayrisan alanlarinin
// (alan x bagil parlaklik farki) toplamidir. Dolgu ve kenar cizgisi ayri hesaplanir.
// Metin agirligi bu modelde yok sayilir; olculen sey renkli yuzeydir.

function kanal(v) {
  const s = v / 255;
  return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
}
function parlaklik(hex) {
  const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
  return 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);
}

const ZEMIN = "#ffffff";
const VURGU = "#1b4d3e";

function agirlik(oge) {
  const alan = oge.genislik * oge.yukseklik;
  const dolgu = oge.dolgu ? alan * Math.abs(parlaklik(oge.dolgu) - parlaklik(ZEMIN)) : 0;
  const cevre = 2 * (oge.genislik + oge.yukseklik);
  const kenar = oge.kenar ? cevre * oge.kenarKalinligi * Math.abs(parlaklik(oge.kenar) - parlaklik(ZEMIN)) : 0;
  return dolgu + kenar;
}

function senaryo(ad, ogeler) {
  const agirliklar = ogeler.map((o) => ({ ad: o.ad, deger: agirlik(o) }));
  const toplam = agirliklar.reduce((t, a) => t + a.deger, 0);
  console.log(`${ad}`);
  console.log("  oge              gorsel agirlik   pay");
  for (const a of agirliklar) {
    console.log(
      `  ${a.ad.padEnd(15)} ${a.deger.toFixed(0).padStart(14)} ${((100 * a.deger) / toplam)
        .toFixed(1)
        .padStart(5)}%`
    );
  }
  console.log(`  toplam agirlik: ${toplam.toFixed(0)}   en buyuk pay: ${(
    (100 * Math.max(...agirliklar.map((a) => a.deger))) / toplam
  ).toFixed(1)}%`);
  console.log("");
}

// A: tek birincil eylem. Ikincil cerceveli, ucuncul ciplak.
senaryo("senaryo A  bir birincil eylem", [
  { ad: "Odunc al", genislik: 120, yukseklik: 40, dolgu: VURGU, kenar: null, kenarKalinligi: 0 },
  { ad: "Listeye ekle", genislik: 128, yukseklik: 40, dolgu: null, kenar: VURGU, kenarKalinligi: 1 },
  { ad: "Paylas", genislik: 76, yukseklik: 40, dolgu: null, kenar: null, kenarKalinligi: 0 },
]);

// B: uc eylem de dolgulu.
senaryo("senaryo B  uc dolgulu eylem", [
  { ad: "Odunc al", genislik: 120, yukseklik: 40, dolgu: VURGU, kenar: null, kenarKalinligi: 0 },
  { ad: "Listeye ekle", genislik: 128, yukseklik: 40, dolgu: VURGU, kenar: null, kenarKalinligi: 0 },
  { ad: "Paylas", genislik: 76, yukseklik: 40, dolgu: VURGU, kenar: null, kenarKalinligi: 0 },
]);

// C: birincil ayni kalir, ikincil notr griye bastirilir.
senaryo("senaryo C  ikincil bastirilmis", [
  { ad: "Odunc al", genislik: 120, yukseklik: 40, dolgu: VURGU, kenar: null, kenarKalinligi: 0 },
  { ad: "Listeye ekle", genislik: 128, yukseklik: 40, dolgu: null, kenar: "#767676", kenarKalinligi: 1 },
  { ad: "Paylas", genislik: 76, yukseklik: 40, dolgu: null, kenar: null, kenarKalinligi: 0 },
]);

console.log("kenar renginin zeminle kontrast orani (WCAG 1.4.11 esigi 3:1)");
for (const renk of [VURGU, "#c9c9c9", "#767676"]) {
  const o = kontrast(renk, ZEMIN);
  console.log(`  ${renk}  ${o.toFixed(2)}:1  ${o >= 3 ? "gecer" : "kalir"}`);
}
```

```
senaryo A  bir birincil eylem
  oge              gorsel agirlik   pay
  Odunc al                  4517  93.5%
  Listeye ekle               316   6.5%
  Paylas                       0   0.0%
  toplam agirlik: 4834   en buyuk pay: 93.5%

senaryo B  uc dolgulu eylem
  oge              gorsel agirlik   pay
  Odunc al                  4517  37.0%
  Listeye ekle              4819  39.5%
  Paylas                    2861  23.5%
  toplam agirlik: 12197   en buyuk pay: 39.5%

senaryo C  ikincil bastirilmis
  oge              gorsel agirlik   pay
  Odunc al                  4517  94.3%
  Listeye ekle               275   5.7%
  Paylas                       0   0.0%
  toplam agirlik: 4792   en buyuk pay: 94.3%

kenar renginin zeminle kontrast orani (WCAG 1.4.11 esigi 3:1)
  #1b4d3e  9.64:1  gecer
  #c9c9c9  1.66:1  kalir
  #767676  4.54:1  gecer
```

#### Vurgu Bir Bütçedir

Senaryo A ile B'nin karşılaştırması bu dersin ana bulgusudur. "Ödünç al" düğmesinin görsel
ağırlığı iki senaryoda da aynıdır: 4517. Değişmeyen bir ögenin payı 93.5 yüzdeden 37.0
yüzdeye düşer. Düğmeye hiçbir şey yapılmamıştır; yanındakiler değişmiştir.

Vurgu payı bir orandır ve oranın paydası ekrandaki toplam ağırlıktır. Bir ögeyi öne
çıkarmanın iki yolu vardır: payı büyütmek ya da paydayı küçültmek. İkinci yol çoğu zaman
daha ucuzdur, çünkü ekranda yer tüketmez.

Senaryo B'nin ikinci bulgusu daha rahatsız edicidir. Üç düğme de aynı biçimde çizildiğinde
en yüksek payı "Ödünç al" değil, **"Listeye ekle" alır: 39.5 yüzde.** Sebep basit bir
geometridir: etiketi daha uzun olduğu için düğmesi daha geniştir, 128 piksele karşı 120
piksel. Bütün eylemler aynı biçimde çizildiğinde vurgu sırasını belirleyen şey eylemin
önemi değil, etiketinin karakter sayısı olur.

Bu, "hepsini öne çıkaralım" kararının neden yalnızca hiyerarşiyi silmekle kalmayıp aynı
zamanda **yanlış** bir hiyerarşi kurduğunu gösterir. Hiyerarşinin yokluğu nötr bir durum
değildir; boşluğu rastgele bir sıralama doldurur.

#### Bastırmanın Sınırı

Payda küçültme yolu senaryo C'de sınanır: ikincil eylemin çerçevesi vurgu renginden nötr
griye çekilir. Sonuç 93.5 yüzdeden 94.3 yüzdeye çıkar. Kazanç 0.8 puandır.

Bu küçük sayı önemlidir, çünkü sezgiye aykırıdır. İkincil eylemi soluklaştırmak birincil
eylemi belirgin biçimde öne çıkarmaz; asıl fark, ikincil eylemi **dolgulu çizmemekten**
gelir. Senaryo A ile B arasındaki fark 56.5 puan, A ile C arasındaki fark 0.8 puandır.
Vurgu bütçesinde kazanç, gürültüyü kısmaktan değil gürültüyü hiç eklememekten gelir.

Bastırmanın bir de alt sınırı vardır. Çerçeve rengi daha da açığa çekilirse görsel ağırlık
düşmeye devam eder ama bir noktada çerçeve görünmez olur. Çıktının son bloğu bunu ölçüyor:
`#c9c9c9` çerçeve zeminle 1.66:1 kontrast oranı verir ve WCAG 1.4.11 ölçütünün metin dışı
ögeler için istediği 3:1 eşiğinin altında kalır. `#767676` ise 4.54:1 ile eşiği geçer.

Kural şudur: **bastırma, ögenin algılanabilirlik eşiğinde durur.** Bir ögeyi bastırmak
onu ikinci sıraya koymak demektir, görünmez kılmak değil. Eşiklerin nasıl türetildiği ve
hangi ölçütün hangi öge sınıfına uygulandığı Karşıtlık ve Erişilebilirlik dersinin
konusudur; burada yalnızca bütçenin bir tabanı olduğu saptanmıştır.

#### Karşıtlığı Renkten Başka Kanallarda Kurmak

Vurgu bütçesi dar olduğu için, bütçeyi tüketmeyen karşıtlık biçimleri değerlidir.

- **Boyut karşıtlığı.** Katalogda kayıt başlığı ile üstveri arasındaki ayrım, ek renk
  kullanmadan kurulur. Bedeli dikey yerdir.
- **Biçim karşıtlığı.** Yuvarlatılmış köşeli bir öge, keskin köşeli ögelerin arasında
  ayrışır. Bedeli, biçim kararının anlam taşımaya başlamasıdır: yuvarlak köşe bir kez
  "eylem" demeye başladıysa, süs olarak kullanılamaz.
- **Yön karşıtlığı.** Dikey akan bir listede yatay bir şerit güçlü ayrışır. Bedeli, yön
  değişiminin okuma akışını kesmesidir.
- **Boşluk karşıtlığı.** Çevresi boş bırakılmış bir öge, çevresi dolu ögelerden ayrışır.
  Bedeli, önceki derste hesaplanan gruplama oranlarının bozulma riskidir.

Bu kanalların ortak özelliği, kontrast oranı ölçüsüne girmemeleridir. Bir arayüzün
erişilebilirlik denetiminden geçmesi, karşıtlığının yeterli olduğu anlamına gelmez; denetim
yalnız renk kanalını ölçer. Tersi de doğrudur: renk kontrastı eşiği geçemeyen bir ayrım,
boyut ya da biçim karşıtlığıyla desteklenerek kurtarılamaz — ölçüt renk kanalı için ayrıca
sağlanmalıdır.

#### Özet

- Karşıtlık algısal bir ilkedir ve renk, boyut, biçim, yön, boşluk kanallarında kurulabilir;
  kontrast oranı ise yalnız renk çiftleri için tanımlı ölçülen bir niceliktir.
- Görsel ağırlık, zeminden ayrışan yüzeylerin alanı ile bağıl parlaklık farkının çarpımı
  olarak modellenebilir ve böylece vurgu tartışması sayıya bağlanır.
- Vurgu payı bir orandır; bir ögenin payı, kendisi hiç değişmeden yalnız komşuları
  değiştiği için düşebilir. Ölçülen düşüş 93.5 yüzdeden 37.0 yüzdeye kadardır.
- Bütün eylemler aynı biçimde çizildiğinde vurgu sırasını eylemin önemi değil etiketin
  uzunluğu belirler; hiyerarşinin yokluğu nötr değil, rastgeledir.
- Bütçedeki asıl kazanç ikincil ögeyi soluklaştırmaktan (0.8 puan) değil, onu dolgulu
  çizmemekten (56.5 puan) gelir.
- Bastırmanın tabanı algılanabilirlik eşiğidir; metin dışı ögeler için WCAG 1.4.11 ölçütü
  3:1 kontrast oranı ister ve bu eşik bütçe hesabının altına inilemeyeceği sınırı çizer.

#### Sonraki Adım

Buraya kadar ögeler ayrıldı, gruplandı ve vurgulandı; hepsi tek tek ögelerin özellikleri
üzerinden yürüdü. Bir ekranı bir arada tutan şey ise ögelerin kendileri değil,
aralarındaki **eksenlerdir**: sol kenarların hizası, sütun başlangıçları, ağırlığın ekranın
iki yanına dağılımı. Sonraki ders hizalama eksenlerinin sayısını bir ölçü olarak kullanır,
katalog arayüzünün denge momentini hesaplar ve düzensiz sağ kenarın ne kadar düzensiz
olduğunu gerçek bir metin üzerinde ölçer.

### Denge ve Hizalama

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/denge-ve-hizalama

Hizalamanın paylaşılan görünmez eksen olarak tanımlanması; eksen sayısının ve alan ağırlıklı yatay dengenin hesaplanması, optik hizalama ile düzensiz sağ kenarın ölçülmesi.

Buraya kadar her karar tek tek ögeler üzerinden verildi: bu öge ne kadar büyük, bu ikisi
ne kadar yakın, bu düğme ne kadar ağır. Bir ekranı bir arada tutan şey ise ögelerin
kendileri değil, aralarındaki görünmez çizgilerdir. Katalog sayfasında süzgeç panelinin
sol kenarı ile sayfa başlığının sol kenarı aynı hizadaysa, aralarında hiçbir çizgi
olmamasına rağmen bir ilişki kurulmuş olur.

Bu ders üç soruyu sayıya bağlar: bir düzende kaç hizalama ekseni vardır, ekrandaki ağırlık
yatay eksende nasıl dağılmıştır ve bir metin bloğunun sağ kenarı ne kadar düzensizdir.

#### Hizalama Paylaşılan Bir Eksendir

**Hizalama (alignment)**, birden çok ögenin aynı görünmez çizgiyi paylaşmasıdır. Bu çizgi ögelerin
sol kenarı, sağ kenarı, dikey orta ekseni ya da metin taban çizgisi olabilir. Terim, Bilgisayarlar
Nasıl Çalışır kursunda bellek adreslerinin sözcük sınırına oturması anlamında da geçmişti; burada
kastedilen görsel eksendir ve iki kullanım arasındaki tek ortak nokta "ortak bir sınıra oturma"
fikridir.

Hizalamanın işlevi ilişki kurmaktır. Yakınlık ilkesi ögeleri birbirine bağlarken boşluk
harcar; hizalama hiçbir şey harcamadan aynı işi uzak ögeler arasında yapar. Ekranın en
üstündeki sayfa başlığı ile en altındaki dipnot aynı sol eksende duruyorsa, aralarında 500
piksel olsa bile aynı sütuna ait oldukları okunur.

Bunun karşılığında hizalama bir maliyet üretir: **her yeni eksen bir kural daha demektir.**
Ekranda kaç farklı sol kenar varsa, okur o kadar farklı başlangıç noktası öğrenmek
zorundadır. Eksen sayısı bu yüzden doğrudan ölçülebilir bir düzen niteliğidir.

#### Eksen Sayısı ve Yatay Denge

Aşağıdaki program katalog sonuç sayfasının bloklarını alır, kaç ayrı sol ve sağ eksen
kullanıldığını sayar ve düzenin **alan ağırlıklı yatay merkezini** hesaplar. İkinci ölçü
dengeyi verir: her bloğun alanı ağırlık kabul edilir, blokların ortak ağırlık merkezi
bulunur ve kapsayıcının orta ekseninden sapması ölçülür.

```js
// 04-hizalama.mjs — hizalama ekseni sayisi ve alan agirlikli yatay denge

const KAPSAYICI = 1200;

// Katalog sonuc sayfasinin bloklari: (ad, sol kenar, genislik, yukseklik)
const oncesi = [
  { ad: "sayfa basligi", x: 24, g: 600, y: 40 },
  { ad: "arama alani", x: 32, g: 520, y: 44 },
  { ad: "suzgec paneli", x: 24, g: 264, y: 480 },
  { ad: "suzgec basligi", x: 40, g: 232, y: 24 },
  { ad: "sonuc listesi", x: 312, g: 600, y: 480 },
  { ad: "kayit basligi", x: 328, g: 560, y: 24 },
  { ad: "kayit ustverisi", x: 336, g: 540, y: 18 },
];

const sonrasi = [
  { ad: "sayfa basligi", x: 24, g: 1152, y: 40 },
  { ad: "arama alani", x: 24, g: 1152, y: 44 },
  { ad: "suzgec paneli", x: 24, g: 264, y: 480 },
  { ad: "suzgec basligi", x: 24, g: 264, y: 24 },
  { ad: "sonuc listesi", x: 312, g: 864, y: 480 },
  { ad: "kayit basligi", x: 312, g: 864, y: 24 },
  { ad: "kayit ustverisi", x: 312, g: 864, y: 18 },
];

function eksenler(bloklar) {
  const sol = new Set(bloklar.map((b) => b.x));
  const sag = new Set(bloklar.map((b) => b.x + b.g));
  return { sol: [...sol].sort((a, b) => a - b), sag: [...sag].sort((a, b) => a - b) };
}

function denge(bloklar) {
  const toplamAlan = bloklar.reduce((t, b) => t + b.g * b.y, 0);
  const moment = bloklar.reduce((t, b) => t + b.g * b.y * (b.x + b.g / 2), 0);
  const merkez = moment / toplamAlan;
  const sapma = merkez - KAPSAYICI / 2;
  return { merkez, sapma, oran: (100 * Math.abs(sapma)) / (KAPSAYICI / 2) };
}

function rapor(ad, bloklar) {
  const e = eksenler(bloklar);
  const d = denge(bloklar);
  console.log(ad);
  console.log(`  sol eksenler (${e.sol.length}): ${e.sol.join(", ")}`);
  console.log(`  sag eksenler (${e.sag.length}): ${e.sag.join(", ")}`);
  console.log(`  toplam eksen sayisi: ${e.sol.length + e.sag.length}`);
  console.log(
    `  alan agirlikli merkez: ${d.merkez.toFixed(1)}px   kapsayici merkezi: ${KAPSAYICI / 2}px`
  );
  console.log(`  sapma: ${d.sapma.toFixed(1)}px   yari genisligin ${d.oran.toFixed(1)}%'i`);
  console.log("");
}

rapor("oncesi", oncesi);
rapor("sonrasi", sonrasi);
```

```
oncesi
  sol eksenler (6): 24, 32, 40, 312, 328, 336
  sag eksenler (7): 272, 288, 552, 624, 876, 888, 912
  toplam eksen sayisi: 13
  alan agirlikli merkez: 459.7px   kapsayici merkezi: 600px
  sapma: -140.3px   yari genisligin 23.4%'i

sonrasi
  sol eksenler (2): 24, 312
  sag eksenler (2): 288, 1176
  toplam eksen sayisi: 4
  alan agirlikli merkez: 608.6px   kapsayici merkezi: 600px
  sapma: 8.6px   yari genisligin 1.4%'i
```

"Öncesi" düzeni tek tek bakıldığında kusursuz görünür: her blok kendi kabının içinde
düzgün durur, iç boşluklar makul, hiçbir öge taşmaz. Sayı başka bir şey söylüyor. Yedi
blok on üç ayrı eksen kullanıyor; sol kenarlar 24, 32, 40, 312, 328 ve 336 piksellerde
başlıyor. Bu değerlerin hiçbiri hatalı değildir; sorun, hepsinin **aynı anda** var
olmasıdır. 8 piksellik farklar hizalama olarak da algılanmaz, kasıtlı girinti olarak da
algılanmaz; yalnızca kenarı bulanıklaştırır.

"Sonrası" düzeni aynı bilgiyi dört eksende taşır. Kayıt başlığı artık kendi kabının iç
boşluğuyla değil, sonuç listesinin sol ekseniyle hizalıdır. İç boşluk yok olmadı;
listenin dışına, blokların arasına taşındı.

İkinci ölçü de ilk düzenin bir başka kusurunu gösteriyor. Alan ağırlıklı merkez 459.7
pikselde, kapsayıcının orta ekseninden 140.3 piksel solda. Sağ tarafta 288 piksel genişliğinde
bir boşluk şeridi vardır ve bu şerit hiçbir işlev taşımaz. Sonuç listesi kapsayıcının sağ
kenarına kadar genişletildiğinde sapma 8.6 piksele düşer.

#### Sıfır Sapma Hedef Değildir

Denge ölçüsünün amacı simetri dayatmak değildir. Simetrik bir düzen dengelidir ama her
dengeli düzen simetrik değildir: solda dar ve uzun bir panel, sağda geniş ve kısa bir blok
bulunan asimetrik bir düzen de sıfıra yakın sapma verebilir.

Ölçünün işlevi, **niyetsiz** dengesizliği yakalamaktır. Yüzde 23'lük bir sapma çoğu zaman bir
karar değil, bir artıktır: içerik sağa kadar uzatılmamış, kapsayıcı genişliği değiştirilmiş ama
bloklar güncellenmemiştir. Kurs boyunca kullanılacak ölçüt şudur: **yarı genişliğin yüzde 10'unu
aşan sapma gerekçelendirilir.** Gerekçe varsa sapma kalır — bir okuma sütunu bilerek sola
yaslanmış olabilir ya da sağdaki boşluk bilerek ayrılmış olabilir. Gerekçe yoksa düzeltilir.

#### Optik Hizalama

Matematiksel hizalama her zaman algılanan hizalamayı vermez. Kenarları düz olmayan
biçimler, kutuları hizalandığında hizasız görünür. Bu farkı düzeltmeye **optik hizalama
(optical alignment)** denir.

Üç yaygın durum vardır. Birincisi yuvarlak biçimlerdir: bir daire, kare ile aynı kutuya
oturduğunda daha küçük görünür, çünkü kenarları kutuya yalnız dört noktada değer. İkincisi
noktalama işaretleridir: tırnakla başlayan bir satır, tırnağın açık alanı yüzünden bir
adım içeride görünür. Üçüncüsü üçgen biçimlerdir: sağa bakan bir üçgen, kutusunun tam
ortasına konduğunda sola kaymış görünür, çünkü kütlesi tabanında toplanmıştır.

Bu üç durumun ortak yanı, düzeltmenin **ölçülemez** değil **hesaplanamaz** olmasıdır: fark
gerçektir ama biçime özgüdür ve genel bir formülü yoktur. Uygulanabilir kural şudur: optik
düzeltme yalnız kenarı düz olmayan biçimlere, yalnız büyük boyutlarda ve yalnız kaydırma miktarı
belgelenerek uygulanır. Belgelenmemiş optik düzeltmeler, bir sonraki derste ele alınacak
tutarlılık sorununun en sık kaynağıdır: kimse o 2 pikselin neden orada olduğunu bilmez ve kimse
silmeye cesaret edemez.

#### Metnin Sağ Kenarı

Bir metin bloğunun sol kenarı bir eksendir; sağ kenarı ise satır uzunluklarının ürettiği
düzensiz bir profildir. Bu profile **düzensiz sağ kenar (ragged right)** denir ve
düzensizliğinin derecesi ölçülebilir.

Aşağıdaki program bir paragrafı üç farklı satır uzunluğunda sarar, satır uzunluklarının
dağılımını çıkarır ve **iki yana yaslama (justification)** uygulansaydı kelime aralıklarının
ne kadar gerileceğini hesaplar. Ölçüm tek genişlikli varsayımla yapılır: her karakter 8
piksel, normal kelime aralığı 4 piksel kabul edilir.

```js
// 04-rag.mjs — duzensiz sag kenarin derinligi ve iki yana yaslamanin bosluk maliyeti

const metin =
  "Katalog kaydinin ayrinti gorunumu kitabin baslik bilgisini, yazar adini, " +
  "basim yilini, raf kodunu ve o an rafta bulunup bulunmadigini tek ekranda " +
  "toplar. Okur bu ekrana iki soruyla gelir: aradigim kitap bu mu ve su anda " +
  "odunc alabilir miyim. Ayrinti gorunumunun duzeni bu iki soruyu ayri ayri " +
  "yanitlayacak bicimde kurulur.";

// Tek genislikli olcum: her karakter esit genislikte varsayilir.
function sar(metin, olcu) {
  const kelimeler = metin.split(" ");
  const satirlar = [];
  let simdiki = "";
  for (const k of kelimeler) {
    const aday = simdiki ? `${simdiki} ${k}` : k;
    if (aday.length <= olcu) simdiki = aday;
    else {
      satirlar.push(simdiki);
      simdiki = k;
    }
  }
  if (simdiki) satirlar.push(simdiki);
  return satirlar;
}

const KARAKTER_GENISLIGI = 8; // 16px govde metninde ortalama karakter genisligi
const NORMAL_BOSLUK = 4; // 0.25em kelime araligi

function olc(olcu) {
  const satirlar = sar(metin, olcu);
  const govde = satirlar.slice(0, -1); // son satir raga dahil edilmez
  const uzunluklar = govde.map((s) => s.length);
  const ort = uzunluklar.reduce((t, u) => t + u, 0) / uzunluklar.length;
  const sapma = Math.sqrt(
    uzunluklar.reduce((t, u) => t + (u - ort) ** 2, 0) / uzunluklar.length
  );
  const enKisa = Math.min(...uzunluklar);
  const ragDerinligi = olcu - enKisa;

  // Iki yana yaslama: eksik genislik kelime araliklarina dagitilir.
  const gerilmeler = govde.map((s) => {
    const bosluk = s.split(" ").length - 1;
    const eksik = (olcu - s.length) * KARAKTER_GENISLIGI;
    return bosluk > 0 ? eksik / bosluk : 0;
  });
  const enBuyukGerilme = Math.max(...gerilmeler);

  console.log(`olcu ${olcu} karakter  (${olcu * KARAKTER_GENISLIGI}px)`);
  console.log(`  satir sayisi: ${satirlar.length}  (rag hesabina giren: ${govde.length})`);
  console.log(`  satir uzunluklari: ${uzunluklar.join(", ")}`);
  console.log(`  ortalama ${ort.toFixed(1)}  standart sapma ${sapma.toFixed(2)}`);
  console.log(`  rag derinligi: ${ragDerinligi} karakter  (olcunun %${((100 * ragDerinligi) / olcu).toFixed(1)}'i)`);
  console.log(
    `  yaslamada en buyuk kelime araligi: ${(NORMAL_BOSLUK + enBuyukGerilme).toFixed(1)}px  ` +
      `(normalin ${((NORMAL_BOSLUK + enBuyukGerilme) / NORMAL_BOSLUK).toFixed(2)} kati)`
  );
  console.log("");
}

olc(45);
olc(68);
olc(92);
```

```
olcu 45 karakter  (360px)
  satir sayisi: 8  (rag hesabina giren: 7)
  satir uzunluklari: 41, 44, 32, 41, 43, 43, 42
  ortalama 40.9  standart sapma 3.76
  rag derinligi: 13 karakter  (olcunun %28.9'i)
  yaslamada en buyuk kelime araligi: 21.3px  (normalin 5.33 kati)

olcu 68 karakter  (544px)
  satir sayisi: 5  (rag hesabina giren: 4)
  satir uzunluklari: 65, 67, 68, 68
  ortalama 67.0  standart sapma 1.22
  rag derinligi: 3 karakter  (olcunun %4.4'i)
  yaslamada en buyuk kelime araligi: 7.4px  (normalin 1.86 kati)

olcu 92 karakter  (736px)
  satir sayisi: 4  (rag hesabina giren: 3)
  satir uzunluklari: 90, 89, 90
  ortalama 89.7  standart sapma 0.47
  rag derinligi: 3 karakter  (olcunun %3.3'i)
  yaslamada en buyuk kelime araligi: 5.7px  (normalin 1.43 kati)
```

Dar sütunun sağ kenarı derindir: bir satır 32 karakterde bitiyor, yani ölçünün yüzde
28.9'u boş kalıyor. Sağ kenar burada bir eksen değil, dişli bir profildir.

İki yana yaslama bu profili düzleştirmeyi vaat eder ama bedelini kelime aralıklarından
öder. Dar sütunda en geniş satır aralığı normalin 5.33 katına çıkar. Kelime aralıkları
harf aralıklarını aştığında satırlar arası boşluklar üst üste gelerek metnin içinde dikey
şeritler oluşturur ve okuma satır boyunca değil, boşluklar arasında sıçrayarak ilerler.

Bu, bir tasarım kararını sayıya bağlar: **iki yana yaslama, satır uzunluğu yeterince
büyük olmadıkça uygulanmaz.** Ölçüm 92 karakterlik sütunda gerilmeyi 1.43 katına
indiriyor; 45 karakterlik sütunda 5.33 kata çıkarıyor. Katalog arayüzünün süzgeç
panelindeki açıklama metinleri dar sütunlarda yaşar, dolayısıyla düzensiz sağ kenar
korunur. Aynı hesabın diğer yönü de vardır: rag derinliği ölçünün dörtte birini aşıyorsa,
sorun yaslama eksikliği değil sütunun fazla dar olmasıdır.

#### Özet

- Hizalama, ögelerin paylaştığı görünmez bir eksendir; uzak ögeler arasında boşluk
  harcamadan ilişki kurar, karşılığında her yeni eksen okura bir kural daha yükler.
- Eksen sayısı doğrudan sayılabilir bir düzen niteliğidir; ölçülen düzende yedi blok on üç
  eksenden dört eksene indirildi ve hiçbir bilgi kaybolmadı.
- Alan ağırlıklı yatay merkezin kapsayıcı merkezinden sapması dengeyi ölçer; yarı
  genişliğin yüzde 10'unu aşan sapma gerekçe ister, ama sıfır sapma hedef değildir.
- Optik hizalama yalnız kenarı düz olmayan biçimlerde gerekir, formülü yoktur ve
  uygulandığında kaydırma miktarı belgelenir.
- Düzensiz sağ kenarın derinliği ölçülebilir; rag derinliği satır uzunluğunun dörtte birini
  aşıyorsa sütun fazla dardır.
- İki yana yaslama, dar sütunda kelime aralıklarını normalin 5.33 katına kadar gerer; kararı
  estetik değil bu gerilme oranı belirler.

#### Sonraki Adım

Bu ders tek bir ekranın iç tutarlılığını ölçtü. Asıl soru ekranlar arasındadır: katalog
arayüzünün arama sayfasında 8 piksel olan bir boşluk, kayıt ayrıntısı sayfasında 10 piksel
olduğunda ne kaybedilir? Sonraki ders yinelenmeyi öğrenilebilirliğin kaynağı olarak ele
alır, bir arayüzün karar envanterini çıkarır ve aynı işi yapan kaç farklı değerin
kullanıldığını sayar.

### Yinelenme ve Tutarlılık

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/yinelenme-ve-tutarlilik

Yinelenmenin öğrenilebilirliği nasıl ürettiği; karar envanterinin çıkarılması, ayırt edilebilirlik eşiğinin altındaki değer çeşitliliğinin ölçülmesi ve ölçeğe oturtmanın maliyeti.

Önceki ders tek bir ekranın iç tutarlılığını ölçtü. Bir arayüz ise tek ekran değildir. Katalog
uygulamasının arama sayfası, sonuç listesi, kayıt ayrıntısı ve ödünç alma onayı ayrı zamanlarda,
çoğu zaman ayrı kişilerce çizilir. Arama sayfasında etiket ile alan arasında 8 piksel bırakılmış;
kayıt ayrıntısında aynı ilişki için 10 piksel kullanılmıştır. İki piksel kimsenin gözüne batmaz.
Sorun, bu iki pikselin bir karar olmasıdır: verilmiş, savunulmamış ve artık kaldırılamaz bir
karar.

Bu ders arayüzün kararlarını sayar. Sayının kendisi bir kalite ölçüsü değildir; kaç
kararın gerçekten bir ayrım ürettiği ile kaçının yalnızca çeşitlilik ürettiği sorusu
kalite ölçüsüdür.

#### Yinelenme Bir Tahmin Sözüdür

**Yinelenme (repetition)**, aynı işlevi taşıyan ögelerin aynı biçimde çizilmesidir.
**Tutarlılık (consistency)** bu yinelenmenin arayüzün tamamına yayılmış hâlidir.

Yinelenmenin değeri estetik değil bilişseldir. Bir kullanıcı arayüzü kullanırken sürekli
tahmin yürütür: bu koyu yeşil dolgulu dikdörtgen bir düğmedir, bu altı çizili metin bir
bağlantıdır, bu 14 pikselik soluk satır bir üstveridir. Bu tahminler her seferinde
doğrulanırsa öğrenme birikir; ekranın yarısı bakılmadan anlaşılır hâle gelir. Bir tek
karşı örnek — dolgulu yeşil bir dikdörtgenin tıklanamaz bir etiket olması — tahmin
zincirini kırar ve kullanıcı her ögeyi yeniden sınamaya döner.

Bundan iki kural türetilir ve bu kurallar simetrik değildir:

- **Aynı görünen aynı davranır.** Bu kuralın ihlali doğrudan hataya yol açar; kullanıcı
  öğrendiği bir kalıba göre davranır ve yanılır.
- **Farklı davranan farklı görünür.** Bu kuralın ihlali hataya değil yavaşlığa yol açar;
  kullanıcı ayrımı biçimden okuyamaz, metni okuyarak bulur.

İkinci kural, tutarlılığın sınırını da çizer. Tutarlılık her şeyi birbirine benzetmek
değildir. **Ödünç al** düğmesiyle **Kaydı sil** düğmesi aynı görünüyorsa tutarlılık
sağlanmış değil, önemli bir ayrım silinmiştir.

#### Karar Envanteri

Tutarlılık tartışmasını ölçülebilir kılmanın yolu, arayüzde fiilen kullanılan değerleri
saymaktır. Aşağıdaki program dört özellik için katalog arayüzünün ham değerlerini alır ve
üç şey hesaplar: kaç ayrı değer kullanıldığı, bu değerler arasında **ayırt edilebilirlik
eşiğinin (just noticeable difference)** altında kalan kaç çift bulunduğu ve değerler bir
ölçeğe oturtulduğunda kaç tanesinin gözle görülür biçimde değişeceği.

```js
// 05-envanter.mjs — arayuzdeki karar envanteri, ayirt edilemeyen cift sayisi ve olcege oturtma

// Katalog arayuzunun dort ekraninda gecen ham degerler.
const envanter = [
  {
    ozellik: "aralik (px)",
    kullanilan: [4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48],
    olcek: [4, 8, 12, 16, 24, 32, 48],
    ayirtEsigi: 2,
  },
  {
    ozellik: "kose yaricapi (px)",
    kullanilan: [2, 3, 4, 6, 8],
    olcek: [4, 8],
    ayirtEsigi: 2,
  },
  {
    ozellik: "kenar kalinligi (px)",
    kullanilan: [1, 1.5, 2, 3],
    olcek: [1, 2],
    ayirtEsigi: 0.5,
  },
  {
    ozellik: "yazi boyutu (px)",
    kullanilan: [12, 13, 14, 15, 16, 18, 20, 24, 32],
    olcek: [14, 16, 20, 24, 32],
    ayirtEsigi: 1,
  },
];

function ayirtEdilemeyenCiftler(degerler, esik) {
  const ciftler = [];
  for (let i = 0; i < degerler.length; i++) {
    for (let j = i + 1; j < degerler.length; j++) {
      if (Math.abs(degerler[i] - degerler[j]) <= esik) ciftler.push([degerler[i], degerler[j]]);
    }
  }
  return ciftler;
}

function enYakin(deger, olcek) {
  return olcek.reduce((a, b) => (Math.abs(b - deger) < Math.abs(a - deger) ? b : a));
}

let oncekiToplam = 0;
let sonrakiToplam = 0;

for (const e of envanter) {
  const ciftler = ayirtEdilemeyenCiftler(e.kullanilan, e.ayirtEsigi);
  const oturtma = e.kullanilan.map((d) => {
    const hedef = enYakin(d, e.olcek);
    return { d, hedef, kayma: Math.abs(hedef - d) };
  });
  const gorunur = oturtma.filter((o) => o.kayma > e.ayirtEsigi);
  const enBuyukKayma = Math.max(...oturtma.map((o) => o.kayma));

  oncekiToplam += e.kullanilan.length;
  sonrakiToplam += e.olcek.length;

  console.log(e.ozellik);
  console.log(`  kullanilan deger sayisi: ${e.kullanilan.length}   olcek adimi sayisi: ${e.olcek.length}`);
  console.log(
    `  ayirt edilemeyen cift (fark <= ${e.ayirtEsigi}px): ${ciftler.length}   ` +
      `ornek: ${ciftler.slice(0, 3).map((c) => c.join("/")).join("  ")}`
  );
  console.log(`  en buyuk oturtma kaymasi: ${enBuyukKayma}px`);
  console.log(`  gozle gorulur degisiklik gerektiren deger sayisi: ${gorunur.length}`);
  console.log(`    ${oturtma.map((o) => `${o.d}->${o.hedef}`).join("  ")}`);
  console.log("");
}

console.log(`toplam ogrenilecek deger: oncesi ${oncekiToplam}   sonrasi ${sonrakiToplam}`);
console.log(`azalma: ${(100 * (1 - sonrakiToplam / oncekiToplam)).toFixed(1)}%`);
```

```
aralik (px)
  kullanilan deger sayisi: 13   olcek adimi sayisi: 7
  ayirt edilemeyen cift (fark <= 2px): 6   ornek: 4/6  6/8  8/10
  en buyuk oturtma kaymasi: 8px
  gozle gorulur degisiklik gerektiren deger sayisi: 3
    4->4  6->4  8->8  10->8  12->12  14->12  16->16  20->16  24->24  28->24  32->32  40->32  48->48

kose yaricapi (px)
  kullanilan deger sayisi: 5   olcek adimi sayisi: 2
  ayirt edilemeyen cift (fark <= 2px): 5   ornek: 2/3  2/4  3/4
  en buyuk oturtma kaymasi: 2px
  gozle gorulur degisiklik gerektiren deger sayisi: 0
    2->4  3->4  4->4  6->4  8->8

kenar kalinligi (px)
  kullanilan deger sayisi: 4   olcek adimi sayisi: 2
  ayirt edilemeyen cift (fark <= 0.5px): 2   ornek: 1/1.5  1.5/2
  en buyuk oturtma kaymasi: 1px
  gozle gorulur degisiklik gerektiren deger sayisi: 1
    1->1  1.5->1  2->2  3->2

yazi boyutu (px)
  kullanilan deger sayisi: 9   olcek adimi sayisi: 5
  ayirt edilemeyen cift (fark <= 1px): 4   ornek: 12/13  13/14  14/15
  en buyuk oturtma kaymasi: 2px
  gozle gorulur degisiklik gerektiren deger sayisi: 2
    12->14  13->14  14->14  15->14  16->16  18->16  20->20  24->24  32->32

toplam ogrenilecek deger: oncesi 31   sonrasi 16
azalma: 48.4%
```

#### Tutarsızlığın Görünmeyen Maliyeti

Envanterin ilk okuması sayı azaltmadır: 31 ayrı değer 16 ölçek adımına iniyor, yüzde 48.4
azalma. Bu, tek başına ilginç bir sayı değildir; asıl bulgu ikinci satırlardadır.

Köşe yarıçapı satırında beş ayrı değer kullanılmış ve bu beş değerin arasında **beş çift** ayırt
edilebilirlik eşiğinin altında kalıyor. Yani beş yarıçapın her biri en az bir komşusundan ayırt
edilemiyor. Beş ayrı karar verilmiş, hiçbiri görünür bir ayrım üretmiyor. Aynı satırda ölçeğe
oturtmanın gözle görülür değişiklik gerektirdiği değer sayısı sıfır: beş değerin hepsi eşiğin
içinde kayıyor.

Bu, tutarsızlığın nasıl bir maliyet ürettiğini gösterir. Maliyet görsel değildir — kimse
2 piksel ile 3 piksel arasındaki farkı fark etmez. Maliyet **karar** üzerindedir: her yeni
öge için yarıçap değeri yeniden düşünülür, gözden geçirmelerde tartışılır, farklı ekiplerce
farklı seçilir. Görünmeyen bir fark için ödenen görünür bir bedel.

Dört özelliğin toplamına bakıldığında 31 değerden yalnız 6'sı (3 + 0 + 1 + 2) ölçeğe
oturtulurken gözle görülür biçimde değişiyor. Kalan 25 değer eşiğin içinde yerine oturuyor.
Bu, ölçeğe geçişin sanıldığı kadar yıkıcı olmadığını söyler: tutarsızlığın büyük bölümü
görünmeyen tutarsızlıktır ve karşılıksız ortadan kaldırılabilir.

Görünür değişiklik gerektiren 6 değer ise ayrı ele alınır. Aralık satırında 40 pikselin 32
piksele oturtulması 8 piksellik bir kaymadır ve düzeni değiştirir. Bu değerler için soru
"ölçek yanlış mı" olur: 40 piksel gerçekten gerekliyse ölçeğe bir adım eklenir, gerekli
değilse değer oturtulur. Ölçek bir dogma değil, istisnaları görünür kılan bir araçtır.

#### Tutarlılık Ne Zaman Bilerek Bozulur

Yinelenme kuralının istisnaları vardır ve istisnalar zararlı değildir; **kayıt dışı**
istisnalar zararlıdır.

Katalog arayüzünde ödünç alma onayı ekranındaki düğme, sonuç listesindeki düğmelerden daha
büyüktür. Bu bir tutarsızlık gibi görünür ama gerekçesi vardır: onay ekranındaki eylem geri
alınması pahalı olan tek eylemdir ve ekranda başka hiçbir eylem yoktur. Boyut farkı burada
bir ayrım üretir.

Ayırt edici ölçüt şudur: **istisna, kuralın hangi koşulda geçersiz olduğunu söyleyebiliyorsa
istisnadır; söyleyemiyorsa hatadır.** "Onay ekranındaki tek eylem için daha büyük boyut
kullanılır" bir koşuldur, denetlenebilir ve yinelenebilir. "Burada 40 piksel daha iyi
duruyordu" bir koşul değildir.

Bu ayrım pratikte bir **istisna kaydı** gerektirir: kuraldan sapan her değerin yanında,
sapmanın koşulu yazılı olur. Kayıt tutulmadığında iki şey birden olur — istisna
çoğalır ve hiç kimse hangi değerin kural, hangisinin istisna olduğunu bilemez hâle gelir.
Önceki derste optik hizalama düzeltmelerinin belgelenmesi istenmişti; aynı gerekçedir.

#### İç Tutarlılık ve Dış Tutarlılık

Tutarlılığın iki yönü vardır ve çatıştıklarında hangisinin kazanacağı önceden
kararlaştırılır.

**İç tutarlılık**, arayüzün kendi kuralına uymasıdır. Katalogda seçili bir süzgeç her yerde
aynı biçimde gösterilir.

**Dış tutarlılık**, arayüzün çalıştığı ortamın yerleşik kurallarına uymasıdır. Altı çizili
mavi metnin bağlantı olması, onay eylemlerinin sağda durması, geri dönüşün sol üstte olması
gibi kurallar hiçbir arayüzün icadı değildir; kullanıcı bunları başka yerlerden öğrenmiştir.

Çatışma çıktığında dış tutarlılık öncelik alır, çünkü kullanıcının önceki öğrenmesi
arayüzün kendi öğrettiğinden daha büyüktür. Bir arayüz kendi içinde tutarlı biçimde
bağlantıları altı çizisiz ve metinle aynı renkte gösterebilir; bu iç tutarlılıktır ve
kullanıcının tıklanabilir ögeyi bulmasını yine de zorlaştırır.

#### Özet

- Yinelenme, aynı işlevi taşıyan ögelerin aynı biçimde çizilmesidir; değeri estetik değil
  bilişseldir, çünkü kullanıcının tahminlerini doğrulayarak öğrenmeyi biriktirir.
- Kural çifti simetrik değildir: aynı görünenin farklı davranması hataya, farklı davrananın
  aynı görünmesi yavaşlığa yol açar.
- Karar envanteri tutarlılığı sayıya bağlar; ölçülen arayüzde dört özellik 31 ayrı değer
  kullanıyor ve bir ölçek bunu 16 adıma indiriyor.
- Tutarsızlığın büyük bölümü görünmez: köşe yarıçapındaki beş değerin beş çifti ayırt
  edilebilirlik eşiğinin altındadır, yani karar maliyeti ödenmiş ama görsel ayrım
  üretilmemiştir.
- Ölçeğe geçiş 31 değerden yalnız 6'sını gözle görülür biçimde değiştirir; kalanı bedelsiz
  yerine oturur, dolayısıyla tutarlılık borcu sanıldığından ucuz kapanır.
- İstisna, kuralın hangi koşulda geçersiz olduğunu söyleyebiliyorsa meşrudur ve kayda
  geçirilir; çatışmada dış tutarlılık iç tutarlılığı yener.

#### Sonraki Adım

Görsel ilkeler bölümü burada tamamlanıyor: ayrım, gruplama, vurgu, hizalama ve yinelenme.
Son iki ders ortak bir eksiği gösterdi — hizalama eksenlerinin ve ölçek adımlarının
nereden geleceği henüz belirlenmedi. Değerler tek tek seçildiği sürece envanter yeniden
şişer. Sonraki bölüm bu boşluğu doldurur ve **ızgara** ile başlar: kapsayıcı genişliği,
sütun sayısı, oluk ve kenar boşluğu arasındaki aritmetik ilişkiyi kurarak hizalama
eksenlerini seçilen değil türetilen değerler hâline getirir.

## Düzen ve Aralık

Izgara sistemleri, aralık ölçeği, yoğunluk kararları, duyarlı ve uyarlanabilir düzen ile girdi türünün ölçüye etkisi.

### Izgara Sistemleri

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/izgara-sistemleri

Kapsayıcı, kenar boşluğu, sütun sayısı ve oluk arasındaki aritmetik ilişki; sütun genişliğinin türetilmesi, açılım hesabı ve ızgaranın içerik ölçütleriyle sınanması.

Görsel ilkeler bölümü hizalama eksenlerinin sayısını azaltmayı ve değerleri bir ölçeğe
oturtmayı gerekli kıldı, ama bu eksenlerin ve değerlerin nereden geleceğini söylemedi.
Değerler tek tek seçildiği sürece envanter yeniden şişer: bugün 24 piksel seçilir, yarın
başka bir ekranda 28 piksel, iki hafta sonra kimse hangisinin doğru olduğunu bilemez.

**Izgara (grid)**, bu seçimi türetmeye çeviren yapıdır. Bir kez dört sayı kararlaştırılır,
kalan bütün yatay ölçüler bu dört sayıdan hesaplanır. Bu ders o aritmetiği kurar ve
katalog arayüzünün sütun bölünmesini içerik ölçütleriyle sınar.

#### Izgaranın Dört Değişkeni

Bir sütun ızgarası dört sayıyla tanımlanır:

- **Kapsayıcı genişliği**, içeriğin yerleştiği alanın toplam genişliği.
- **Kenar boşluğu (grid margin)**, kapsayıcının kenarı ile ilk ve son sütun arasında kalan şerit.
   CSS ile Görsel Sunum kursunda `margin` özelliğinin karşılığı dış boşluk, `padding` özelliğinin
   karşılığı iç boşluk olarak sabitlenmişti; ızgaranın kenar boşluğu ise bir düzen kararıdır ve
   gerçekleştirimde bu iki özellikten biriyle kurulabilir.
- **Sütun sayısı**, içerik alanının kaç eşit paya bölündüğü.
- **Oluk (gutter)**, iki komşu sütun arasındaki boşluk.

Bu dördü arasındaki bağıntı tektir:

$$
\text{sütun} = \frac{\text{kapsayıcı} - 2 \cdot \text{kenar boşluğu} - (n - 1) \cdot \text{oluk}}{n}
$$

Bağıntının önemi, sütun genişliğinin **bağımsız bir karar olmamasıdır**. Dört sayıdan üçü seçilir,
dördüncüsü ise hesaplanır. Bir tasarımda "sütun 74 piksel olsun" diye bir karar verilmez; 74
çıkar.

Birden çok sütunu kaplayan bir bloğun genişliği de türetilir. Bir bloğun kapladığı sütun
sayısına **açılım (span)** denir ve genişliği, kapladığı sütunlara ek olarak aralarındaki
olukları da içerir: $n$ sütunluk bir açılım $n$ sütun genişliği artı $n-1$ oluk kadar
yer kaplar.

#### Sütun Genişliği Türetilir

Aşağıdaki program sabit kenar boşluğu ve oluk değerleriyle farklı kapsayıcı genişlikleri
için sütun genişliğini hesaplar, açılım genişliklerini çıkarır ve sütun sayısının
bölünebilirliğini karşılaştırır.

```js
// 06-izgara.mjs — sutun genisligi aritmetigi, acilim genislikleri ve bolunebilirlik

function izgara(kapsayici, kenar, sutunSayisi, oluk) {
  const icerik = kapsayici - 2 * kenar;
  const toplamOluk = (sutunSayisi - 1) * oluk;
  const sutun = (icerik - toplamOluk) / sutunSayisi;
  return { icerik, toplamOluk, sutun };
}

function acilim(n, sutun, oluk) {
  return n * sutun + (n - 1) * oluk;
}

const KENAR = 24;
const OLUK = 24;
const SUTUN_SAYISI = 12;

console.log("kapsayici  icerik  toplam oluk  sutun genisligi  tam sayi mi");
for (const k of [1440, 1280, 1200, 1024, 960, 768]) {
  const g = izgara(k, KENAR, SUTUN_SAYISI, OLUK);
  console.log(
    `${String(k).padStart(9)}  ${String(g.icerik).padStart(6)}  ${String(g.toplamOluk).padStart(11)}  ` +
      `${g.sutun.toFixed(3).padStart(15)}  ${Number.isInteger(g.sutun) ? "evet" : "HAYIR"}`
  );
}

console.log("");
const temel = izgara(1200, KENAR, SUTUN_SAYISI, OLUK);
console.log(`kapsayici 1200, kenar ${KENAR}, oluk ${OLUK}, ${SUTUN_SAYISI} sutun -> sutun ${temel.sutun}px`);
console.log("acilim  genislik  icerikteki pay");
for (const n of [3, 4, 6, 8, 9, 12]) {
  const g = acilim(n, temel.sutun, OLUK);
  console.log(
    `${String(n).padStart(6)}  ${String(g).padStart(8)}  ${((100 * g) / temel.icerik).toFixed(1).padStart(13)}%`
  );
}

console.log("");
console.log("sutun sayisi  bolenleri (1 ve kendisi haric)  esit parca secenegi");
for (const s of [8, 10, 12, 16]) {
  const bolenler = [];
  for (let d = 2; d < s; d++) if (s % d === 0) bolenler.push(d);
  console.log(`${String(s).padStart(12)}  ${bolenler.join(", ").padEnd(30)}  ${bolenler.length}`);
}

console.log("");
console.log("icerik tabanli denetim: suzgec paneli >= 240px, sonuc listesi >= 480px");
for (const kapsayici of [1200, 768]) {
  const g = izgara(kapsayici, KENAR, SUTUN_SAYISI, OLUK);
  console.log(`  kapsayici ${kapsayici}px  (sutun ${g.sutun}px)`);
  for (const [sol, sag] of [
    [3, 9],
    [4, 8],
    [5, 7],
  ]) {
    const solG = acilim(sol, g.sutun, OLUK);
    const sagG = acilim(sag, g.sutun, OLUK);
    const durum = solG >= 240 && sagG >= 480 ? "gecer" : "kalir";
    console.log(`    ${sol}+${sag} sutun -> ${solG}px + ${sagG}px  ${durum}`);
  }
}
```

```
kapsayici  icerik  toplam oluk  sutun genisligi  tam sayi mi
     1440    1392          264           94.000  evet
     1280    1232          264           80.667  HAYIR
     1200    1152          264           74.000  evet
     1024     976          264           59.333  HAYIR
      960     912          264           54.000  evet
      768     720          264           38.000  evet

kapsayici 1200, kenar 24, oluk 24, 12 sutun -> sutun 74px
acilim  genislik  icerikteki pay
     3       270           23.4%
     4       368           31.9%
     6       564           49.0%
     8       760           66.0%
     9       858           74.5%
    12      1152          100.0%

sutun sayisi  bolenleri (1 ve kendisi haric)  esit parca secenegi
           8  2, 4                            2
          10  2, 5                            2
          12  2, 3, 4, 6                      4
          16  2, 4, 8                         3

icerik tabanli denetim: suzgec paneli >= 240px, sonuc listesi >= 480px
  kapsayici 1200px  (sutun 74px)
    3+9 sutun -> 270px + 858px  gecer
    4+8 sutun -> 368px + 760px  gecer
    5+7 sutun -> 466px + 662px  gecer
  kapsayici 768px  (sutun 38px)
    3+9 sutun -> 162px + 534px  kalir
    4+8 sutun -> 224px + 472px  kalir
    5+7 sutun -> 286px + 410px  kalir
```

İlk tabloda dikkat çeken satırlar 1280 ve 1024 kapsayıcılarıdır: sütun genişliği tam sayı
çıkmaz, 80.667 ve 59.333 olur. Bu, ızgaranın hizalama eksenlerinin tam piksel
konumlarına düşmediği anlamına gelir.

Buradan çıkarılacak sonuç ızgarayı bozmak değildir. Sonuç şudur: **ızgara bir piksel
belirtimi değil, bir bölüştürme kuralıdır.** Sütun genişliği kesirli çıktığında tasarım
kararı "74 piksel" gibi ölçülmüş bir değere değil, "dört sütun açılımı" gibi bir orana
bağlanır. Ölçülmüş değere bağlanan bir tasarım her kapsayıcı genişliğinde yeniden
ölçülmek zorunda kalır; orana bağlanan tasarım kendiliğinden uyar. Kesirli genişliğin
görsel bir dikiş izi bırakıp bırakmadığı çizim aşamasına bağlıdır ve tasarımın buna
bağımlı olmaması gerekir.

İkinci tablo açılım genişliklerini veriyor. Katalog arayüzünde süzgeç paneli 3 sütun
açılımıyla 270 piksel, sonuç listesi 9 sütun açılımıyla 858 piksel yer kaplar. Toplam
270 + 24 + 858 = 1152, yani içerik genişliğinin tamamı. Aradaki 24 piksel bir oluktur;
iki bloğun arasına ayrıca boşluk eklenmez. Izgara kullanmanın en sık yapılan hatası,
oluğun üstüne bir de dış boşluk koymaktır; sonuç, ızgarada olmayan bir eksen üretir.

#### Sütun Sayısı Neden On İki

Üçüncü tablo sütun sayısının neden keyfî olmadığını gösteriyor. Bir ızgaranın işi, içeriği
eşit paylara bölebilmektir; dolayısıyla değerli olan sütun sayısı değil, o sayının kaç ayrı
eşit bölünmeye izin verdiğidir.

On iki sütun 2, 3, 4 ve 6'ya bölünür: yarım, üçte bir, dörtte bir ve altıda bir düzenler
kurulabilir. Sekiz sütun yalnız 2 ve 4'e bölünür; üçlü bir bölünme mümkün değildir. On
altı sütun 2, 4 ve 8'e bölünür, üçlü bölünmeyi yine vermez ama daha ince ayar sağlar.

Bu, on ikinin bir kural olduğu anlamına gelmez. Ölçüt şudur: **sütun sayısı, içeriğin
gerektirdiği bölünmeleri karşılayan en küçük sayıdır.** Katalog arayüzü ikili ve üçlü
bölünme gerektiriyorsa on iki yeterlidir; yalnız ikili bölünme gerekiyorsa sekiz daha az
karar üretir ve daha az karar daha az tutarsızlık demektir.

#### Izgarayı İçerikten Sınamak

Bir ızgara kendi başına doğru ya da yanlış değildir; içerikle sınandığında değerlendirilir.
Katalog arayüzünün iki ölçütü vardır: süzgeç paneli, süzgeç etiketlerinin sarmadan sığması
için en az 240 piksel; sonuç listesi, kayıt başlıklarının okunur satır uzunluğunu tutması
için en az 480 piksel ister.

Dördüncü tablo bu ölçütü üç bölünmeye uyguluyor. 1200 piksellik kapsayıcıda üç bölünme de geçiyor;
seçim başka gerekçelerle yapılır. 3+9 bölünmesi sonuç listesine en çok yeri verdiği için katalogda
tercih edilir: ekranın birincil görevi sonuçları taramaktır, süzgeç ise ikincil bir araçtır.

768 piksellik kapsayıcıda ise **hiçbir bölünme geçmiyor.** 5+7 bölünmesi süzgeç için 286
piksel verir ama sonuç listesine yalnız 410 piksel kalır; 4+8 bölünmesinde iki ölçüt de
kalır. Bu, ızgaranın bir başarısızlığı değil, ızgaranın verdiği bir bilgidir: bu genişlikte
iki sütunlu bir düzen kurulamaz. İki bloğu yan yana tutmakta ısrar etmek, iki bloğu birden
kullanılamaz hâle getirir.

Aynı hesap, düzen değişikliğinin nerede yapılacağını da söyler. Sonuç listesinin 480 piksel
tutabilmesi için gereken en küçük kapsayıcı genişliği, 8 sütun açılımının 480'e eşit
olduğu noktadan bulunur; bunun altında tek sütunlu düzene geçilir. Bu geçişin nasıl
tanımlandığı Duyarlı ve Uyarlanabilir Düzen dersinin konusudur.

#### Izgara Türleri

Sütun ızgarası tek tür değildir; içerik yapısına göre başka ızgaralar seçilir.

- **Elyazması ızgarası** tek bir metin bloğu ve onu çevreleyen kenar boşluklarından oluşur.
  Kayıt ayrıntısındaki açıklama metni bu ızgarayı kullanır; bölünmeye ihtiyacı yoktur.
- **Sütun ızgarası** yukarıda hesaplanan yapıdır: yatay bölünme, dikey serbestlik.
- **Modüler ızgara**, sütun ızgarasına yatay bir bölünme daha ekler; satırlar da eşit
  yüksekliklere ayrılır. Kart biçiminde sunulan sonuç listeleri bu ızgarayı kullanır,
  çünkü kartların hem genişliği hem yüksekliği hizalanmalıdır.
- **Taban çizgisi ızgarası**, dikey ölçüyü metnin satır yüksekliğine bağlar; her metin
  satırı ortak bir yatay çizgiye oturur. Maliyeti yüksektir: farklı boyuttaki her metin
  düzeyinin satır yüksekliği ortak bir bölene uymak zorunda kalır.

Seçim ölçütü, içeriğin hangi eksende hizalanması gerektiğidir. Yalnız yatay hizalama
gerekiyorsa sütun ızgarası yeter; dikey hizalama da gerekiyorsa modüler ya da taban çizgisi
ızgarasının maliyeti göze alınır.

#### Özet

- Izgara dört sayıyla tanımlanır: kapsayıcı genişliği, kenar boşluğu, sütun sayısı ve oluk.
  Sütun genişliği bağımsız bir karar değil, bu dördünden türeyen bir sonuçtur.
- Bir açılımın genişliği kapladığı sütunlar ile aralarındaki olukların toplamıdır; ızgaraya
  yerleşen bloklar arasına ayrıca boşluk eklenmez, oluk bu işi zaten yapar.
- Sütun genişliği her kapsayıcıda tam sayı çıkmaz; tasarım ölçülmüş piksel değerine değil
  açılım oranına bağlanır.
- Sütun sayısının değeri kaç eşit bölünmeye izin verdiğidir; on iki sayısı 2, 3, 4 ve 6'ya
  bölündüğü için yaygındır, ama ölçüt içeriğin gerektirdiği bölünmeleri karşılayan en küçük
  sayıdır.
- Izgara, içeriğin en küçük genişlik gereksinimleriyle sınanır; hiçbir bölünmenin ölçütü
  karşılamadığı genişlik, düzenin değişmesi gereken noktayı işaret eder.
- Izgara türü içeriğin hangi eksende hizalanacağına göre seçilir; dikey hizalama gerekmiyorsa
  modüler ve taban çizgisi ızgaralarının maliyeti ödenmez.

#### Sonraki Adım

Izgara yatay ölçüyü türetilir hâle getirdi: sütun sınırları artık seçilmiyor, hesaplanıyor. Dikey
ölçü ise hâlâ serbest — oluk 24 piksel seçildi, kenar boşluğu 24 piksel seçildi, ama bu sayıların
nereden geldiği söylenmedi. Yinelenme ve Tutarlılık dersinde çıkarılan envanter tam da bu boşlukta
şişmişti. Sonraki ders aralık değerlerini bir üretim kuralına bağlar: doğrusal, geometrik ve karma
ölçekleri hesaplayarak karşılaştırır ve katalog arayüzünün 13 ham aralık değerini kaç adımlı bir
ölçeğin karşıladığını ölçer.

### Aralık Ölçeği

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/aralik-olcegi

Boşluk değerlerinin seçilmek yerine türetilmesi; doğrusal, geometrik ve karma ölçeklerin adım sayısı, ayırt edilebilirlik ve gruplama oranı ölçütleriyle karşılaştırılması.

Izgara yatay ölçüyü türetilir hâle getirdi: sütun sınırları artık seçilmiyor,
hesaplanıyor. Ancak ızgaranın kendi girdileri hâlâ serbestti. Kenar boşluğu 24 piksel
seçildi, oluk 24 piksel seçildi. Aynı serbestlik dikey eksende de sürüyor: kayıtlar arası
boşluk, bölüm başlığının üstündeki boşluk, düğmenin iç boşluğu.

Yinelenme ve Tutarlılık dersinde çıkarılan envanter tam bu boşlukta şişmişti: dört
özellikte 31 ayrı değer, bunların 13'ü aralık. Bu ders o 13 değeri bir üretim kuralına
bağlar ve kuralın hangi ölçütlerle seçileceğini gösterir.

#### Ölçek Bir Üretim Kuralıdır

**Aralık ölçeği (spacing scale)**, arayüzde kullanılabilecek boşluk değerlerinin sonlu ve
kurallı bir kümesidir. Ölçeğin sağladığı şey listenin kendisi değil, listenin **kapalı**
olmasıdır: ölçekte olmayan bir değer kullanılamaz.

Bu kapalılık iki iş görür. Birincisi karar sayısını düşürür; bir boşluk için soru "kaç
piksel" değil "hangi adım" olur ve seçenek sayısı ondan az kalır. İkincisi, kararı
denetlenebilir kılar: ölçek dışı bir değer bir tercih değil, bir ihlaldir.

Ölçeğin dört ölçütü vardır ve bu ölçütler birbirini çeker:

- **Adım sayısı.** Az adım az karar demektir, ama çok az adım gerekli ayrımı veremez.
- **Ayırt edilebilirlik.** İki komşu adım arasındaki fark, ayırt edilebilirlik eşiğinin
  üstünde olmalıdır; olmazsa ölçek gereksiz adım taşır.
- **Gruplama oranı.** Yakınlık ve Gruplama dersinde konan ölçüt, bir grup sınırının grup
  içi boşluğun en az iki katı olmasını istiyordu. Ölçek, oranı en az 2 olan adım çiftleri
  içermelidir.
- **Ham değere yakınlık.** Var olan tasarım ölçeğe oturtulduğunda kaymalar küçük kalmalıdır;
  büyük kaymalar ölçek geçişini pahalı hâle getirir.

#### Üç Ölçek Ailesini Hesaplamak

Aşağıdaki program üç ölçek ailesini üretir ve dört ölçütle değerlendirir. Ham değerler,
Yinelenme ve Tutarlılık dersinde çıkarılan 13 aralık değeridir.

```js
// 07-olcek.mjs — dogrusal, geometrik ve karma aralik olceklerinin karsilastirmasi

const TABAN = 4;
const UST = 96;
const AYIRT_ESIGI = 2; // bu farkin altindaki iki adim ayirt edilemez

function dogrusal(taban, adim, ust) {
  const o = [];
  for (let v = taban; v <= ust; v += adim) o.push(v);
  return o;
}

function geometrik(taban, oran, ust) {
  const o = [];
  let v = taban;
  while (v <= ust) {
    o.push(Math.round(v));
    v *= oran;
  }
  return [...new Set(o)];
}

// Katalog arayuzunun ham aralik degerleri (Yinelenme ve Tutarlilik dersinden).
const HAM = [4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48];

function enYakin(deger, olcek) {
  return olcek.reduce((a, b) => (Math.abs(b - deger) < Math.abs(a - deger) ? b : a));
}

function degerlendir(ad, olcek) {
  const oranlar = [];
  let ayirtEdilemeyen = 0;
  for (let i = 1; i < olcek.length; i++) {
    oranlar.push(olcek[i] / olcek[i - 1]);
    if (olcek[i] - olcek[i - 1] <= AYIRT_ESIGI) ayirtEdilemeyen++;
  }
  const kaymalar = HAM.map((h) => Math.abs(enYakin(h, olcek) - h));
  const ortKayma = kaymalar.reduce((t, k) => t + k, 0) / kaymalar.length;
  // Ic ice gruplama icin ardisik adim cifti: oran en az 2 olmali (bkz. Yakinlik ve Gruplama).
  const gruplanabilir = oranlar.filter((o) => o >= 2).length;

  console.log(`${ad}`);
  console.log(`  adimlar: ${olcek.join(", ")}`);
  console.log(`  adim sayisi: ${olcek.length}`);
  console.log(
    `  komsu oran araligi: ${Math.min(...oranlar).toFixed(2)} - ${Math.max(...oranlar).toFixed(2)}`
  );
  console.log(`  ayirt edilemeyen komsu cift (fark <= ${AYIRT_ESIGI}px): ${ayirtEdilemeyen}`);
  console.log(`  orani 2 ve uzeri olan komsu cift: ${gruplanabilir}`);
  console.log(`  ham degerlerin ortalama kaymasi: ${ortKayma.toFixed(2)}px   en buyuk: ${Math.max(...kaymalar)}px`);
  console.log("");
}

degerlendir("dogrusal  4px adimli", dogrusal(TABAN, 4, UST));
degerlendir("dogrusal  8px adimli", dogrusal(TABAN, 8, UST));
degerlendir("geometrik oran 2.0", geometrik(TABAN, 2, UST));
degerlendir("geometrik oran 1.5", geometrik(TABAN, 1.5, UST));

const KARMA = [4, 8, 12, 16, 24, 32, 48, 64, 96];
degerlendir("karma", KARMA);

// Gruplama duzeyleri komsu adim olmak zorunda degil; olcegin icinden secilir.
const GRUPLAMA = [4, 8, 24, 48];
console.log("gruplama duzeyleri karma olcegin icinden seciliyor mu");
for (const d of GRUPLAMA) {
  console.log(`  ${String(d).padStart(3)}px  olcekte ${KARMA.includes(d) ? "var" : "YOK"}  (adim ${KARMA.indexOf(d) + 1})`);
}
console.log("  ardisik gruplama duzeyi oranlari:");
for (let i = 1; i < GRUPLAMA.length; i++) {
  const o = GRUPLAMA[i] / GRUPLAMA[i - 1];
  console.log(`    ${GRUPLAMA[i - 1]} -> ${GRUPLAMA[i]}  oran ${o.toFixed(2)}  ${o >= 2 ? "ayirir" : "BELIRSIZ"}`);
}
```

```
dogrusal  4px adimli
  adimlar: 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96
  adim sayisi: 24
  komsu oran araligi: 1.04 - 2.00
  ayirt edilemeyen komsu cift (fark <= 2px): 0
  orani 2 ve uzeri olan komsu cift: 1
  ham degerlerin ortalama kaymasi: 0.46px   en buyuk: 2px

dogrusal  8px adimli
  adimlar: 4, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84, 92
  adim sayisi: 12
  komsu oran araligi: 1.10 - 3.00
  ayirt edilemeyen komsu cift (fark <= 2px): 0
  orani 2 ve uzeri olan komsu cift: 1
  ham degerlerin ortalama kaymasi: 2.31px   en buyuk: 4px

geometrik oran 2.0
  adimlar: 4, 8, 16, 32, 64
  adim sayisi: 5
  komsu oran araligi: 2.00 - 2.00
  ayirt edilemeyen komsu cift (fark <= 2px): 0
  orani 2 ve uzeri olan komsu cift: 4
  ham degerlerin ortalama kaymasi: 3.85px   en buyuk: 16px

geometrik oran 1.5
  adimlar: 4, 6, 9, 14, 20, 30, 46, 68
  adim sayisi: 8
  komsu oran araligi: 1.43 - 1.56
  ayirt edilemeyen komsu cift (fark <= 2px): 1
  orani 2 ve uzeri olan komsu cift: 0
  ham degerlerin ortalama kaymasi: 1.69px   en buyuk: 6px

karma
  adimlar: 4, 8, 12, 16, 24, 32, 48, 64, 96
  adim sayisi: 9
  komsu oran araligi: 1.33 - 2.00
  ayirt edilemeyen komsu cift (fark <= 2px): 0
  orani 2 ve uzeri olan komsu cift: 1
  ham degerlerin ortalama kaymasi: 1.69px   en buyuk: 8px

gruplama duzeyleri karma olcegin icinden seciliyor mu
    4px  olcekte var  (adim 1)
    8px  olcekte var  (adim 2)
   24px  olcekte var  (adim 5)
   48px  olcekte var  (adim 7)
  ardisik gruplama duzeyi oranlari:
    4 -> 8  oran 2.00  ayirir
    8 -> 24  oran 3.00  ayirir
    24 -> 48  oran 2.00  ayirir
```

#### Her Ölçek Bir Ölçütü Kaybeder

Dört adayın hiçbiri dört ölçütü birden karşılamıyor ve kayıpların yeri farklı.

**Doğrusal 4 piksel adımlı ölçek** ham değerlere en yakın olanı: ortalama kayma 0.46
piksel. Ama 24 adımı var. Envanterde 13 ayrı aralık değeri vardı; bu ölçek 24 değere izin
veriyor, yani bir kısıt getirmiyor. Ölçeğin işi seçenek sunmak değil seçenek kısmaktır;
seçenek sayısı ham durumdan fazlaysa ölçek adını hak etmez.

**Doğrusal 8 piksel adımlı ölçek** adım sayısını 12'ye indiriyor ama tabandan 4'ten
başladığı için 8, 16, 24 gibi yaygın değerleri hiç içermiyor: adımları 4, 12, 20, 28
biçiminde ilerliyor. Ortalama kayma 2.31 piksele çıkıyor. Kaybı, taban seçimiyle adım
büyüklüğünün uyumsuz olmasıdır.

**Geometrik ölçek, oran 2.0** yalnız beş adım üretiyor ve komşu oranların hepsi tam 2 —
gruplama açısından ideal. Bedeli üst bölgedeki boşluktur: 16'dan sonra doğrudan 32'ye,
sonra 64'e atlıyor. Ham değerlerin ortalama kayması 3.85, en büyük kayma 16 piksel. 24
piksellik bir aralık ölçekte karşılıksız kalıyor.

**Geometrik ölçek, oran 1.5** ince ayar veriyor ama iki kusuru var. Yuvarlama sonucu 9,
14, 30, 46 gibi ezberlenmeyen sayılar üretiyor ve daha önemlisi: **oranı 2 ve üzeri olan
komşu çift sayısı sıfır.** Bu ölçekle bir grup sınırı komşu adımla ifade edilemez;
gruplama için her zaman iki adım atlamak gerekir.

**Karma ölçek** dört ölçütü de orta düzeyde karşılıyor: 9 adım, ayırt edilemeyen komşu
çift yok, ortalama kayma 1.69 piksel. Kaybı en büyük kaymanın 8 piksel olmasıdır — bu, 40
piksellik ham değerin 32'ye oturmasından gelir ve gözle görülür bir değişikliktir.

Seçim ölçütü şudur: **ölçek, en çok kullanılan bölgede en ince, en az kullanılan bölgede
en kaba olmalıdır.** Arayüzlerde boşlukların çoğu 4 ile 32 piksel arasındadır; 64 ile 96
arasında ince ayar gerekmez. Karma ölçek tam bunu yapar: alt bölgede 4 piksel adımlarla
ilerler, üst bölgede iki katına çıkar.

#### Ölçek İki İşi Birden Yapar

Çıktının son bloğu ölçeğin ikinci işini doğruluyor. Yakınlık ve Gruplama dersinde katalog
ekranının dört gruplama düzeyi 4, 8, 24 ve 48 piksel olarak alınmıştı. Bu dört değerin
dördü de karma ölçeğin içinde: 1., 2., 5. ve 7. adımlar. Aralarındaki oranlar 2, 3 ve 2 —
üçü de gruplama eşiğini geçiyor.

Buradaki asıl ders, gruplama düzeylerinin **komşu adım olmak zorunda olmadığıdır.** Bir
ölçek iki farklı iş görür: ince ayar için birbirine yakın adımlar, gruplama için birbirinden
uzak adımlar. Tek bir sabit oranla üretilen ölçek bu iki işten yalnız birini yapabilir. 2.0
oranı gruplamayı verir, ince ayarı vermez; 1.5 oranı ince ayarı verir, gruplamayı vermez.
Karma ölçek ikisini de verir, çünkü gruplama düzeyleri adımların arasından **seçilir**.

Bu, ölçeği kullanırken bir kural daha gerektirir: ölçeğe erişim serbest bırakılmaz. Bir
tasarımcı grup sınırı için 12 pikseli seçebilir; ölçekte vardır ama grup içi boşluk 8
pikselse oran 1.5 kalır. Ölçek, yanlış değeri değil yanlış **çifti** engellemez. Denetim
bu yüzden ölçek üyeliğine değil, kullanılan çiftlerin oranına bakar.

#### Adımı Ölçüsüyle Değil Rolüyle Anmak

Ölçek adımlarına verilecek adlar da bir karardır. Adım ölçüsüyle anılırsa —
"yirmi dört piksellik boşluk" — ölçek değiştiğinde bütün adlar yanlış hâle gelir. Adım
sırasıyla anılırsa — "beşinci adım" — araya bir adım eklendiğinde bütün numaralar kayar.

Kullanılabilir yol, adı ölçüye değil **role** bağlamaktır: grup içi boşluk, blok arası
boşluk, bölüm arası boşluk. Rol adı, ölçeğin hangi adımına karşılık geldiğini tek yerde
tanımlar; ölçek değiştiğinde yalnız o tanım değişir, arayüzün geri kalanı değişmez. Bu
yaklaşımın sistem düzeyindeki karşılığı Tasarım Sistemleri kursunun konusudur; burada
gerekli olan tek kural, aralık kararlarının rol adlarıyla anılmasıdır.

#### Özet

- Aralık ölçeği, kullanılabilecek boşluk değerlerinin kapalı bir kümesidir; değerini
  listeden değil, listenin dışına çıkılamamasından alır.
- Bir ölçek dört ölçütle değerlendirilir: adım sayısı, komşu adımların ayırt edilebilirliği,
  gruplama için gereken 2 ve üzeri oranların varlığı, ham değerlere oturtma kayması.
- Doğrusal 4 piksel adımlı ölçek 24 değere izin verdiği için kısıt getirmez; ölçeğin işi
  seçenek sunmak değil kısmaktır.
- Tek bir sabit oranla üretilen ölçek ince ayar ile gruplamadan yalnız birini verir: 2.0
  oranı gruplamayı, 1.5 oranı ince ayarı verir.
- Karma ölçek alt bölgede ince, üst bölgede kaba ilerleyerek her iki işi de karşılar;
  gruplama düzeyleri komşu adımlardan değil, ölçeğin içinden seçilir.
- Ölçek üyeliği tek başına yetmez; denetim kullanılan boşluk çiftlerinin oranına bakar ve
  adımlar ölçüleriyle değil rolleriyle anılır.

#### Sonraki Adım

Ölçek boşlukların hangi değerleri alabileceğini söyledi, hangi adımın nerede kullanılacağını
söylemedi. Katalog sonuç listesinde her kayıt için 8 piksel mi 24 piksel mi kullanılacağı,
bir ekranda kaç kaydın göründüğünü doğrudan belirler. Sonraki ders bu ödünleşimi hesaplar:
satır yüksekliğinden ekran başına düşen kayıt sayısını çıkarır, 200 sonucun taranması için
gereken ekran sayısını bulur ve yoğunluk kararının hangi görevlerde hangi yönde verildiğini
gösterir.

### Yoğunluk Kararları

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/yogunluk-kararlari

Bilgi yoğunluğunun görevden türetilmesi; kayıt satırı yüksekliğinden ekran başına kayıt sayısının, tarama maliyetinin ve boşluk oranının hesaplanması.

Aralık ölçeği boşlukların hangi değerleri alabileceğini söyledi, hangi adımın nerede
kullanılacağını söylemedi. Bu soru katalog sonuç listesinde en somut hâlini alır: bir kayıt
satırının iç boşluğu 6 piksel mi 16 piksel mi olacak?

Soru estetik gibi görünür, ama sonucu aritmetiktir. İç boşluk satır yüksekliğini belirler, satır
yüksekliği ekranda kaç kaydın göründüğünü belirler, bu da 200 sonucun taranması için kaç ekran
gerektiğini belirler. Bu ders o zinciri baştan sona hesaplar.

#### Yoğunluk Görevden Türetilir

**Bilgi yoğunluğu (information density)**, birim ekran alanına düşen anlamlı bilgi
miktarıdır. Yüksek yoğunluk daha çok bilgiyi aynı anda gösterir; düşük yoğunluk her bilgi
parçasına daha çok yer ayırır.

İkisinden biri genel olarak daha iyi değildir. Doğru yoğunluk, ekranın birincil görevinden
türetilir ve iki görev türü zıt yönde çeker:

- **Tarama görevi.** Kullanıcı çok sayıda öge arasından birini arıyor. Karar ölçütü tek bir
  alandır — çoğu zaman başlık. Bu görevde her ek boşluk, aranan ögenin ekranın dışında
  kalma olasılığını artırır.
- **Değerlendirme görevi.** Kullanıcı az sayıda öge arasından karşılaştırma yaparak
  seçiyor. Karar ölçütü birden çok alandır: yazar, yıl, baskı, rafta bulunma durumu. Bu
  görevde sıkışıklık, alanların birbirine karışmasına yol açar.

Katalog arayüzünde iki görev de vardır ama aynı ekranda değildir. Sonuç listesi bir tarama
ekranıdır; kayıt ayrıntısı bir değerlendirme ekranıdır. Aynı yoğunluk kararının ikisine
birden uygulanması, ikisini de bozar.

#### Zinciri Hesaplamak

Aşağıdaki program üç yoğunluk düzeyi için kayıt satırının toplam yüksekliğini hesaplar ve
buradan ekran başına kayıt sayısını, ekranda görünen alan sayısını, 200 sonucun taranması
için gereken ekran sayısını ve 137. sıradaki bir kayda ulaşmak için geçilecek ekran
sayısını çıkarır. Çıktıdaki `satir` sütunu kayıt satırının toplam yüksekliğidir; `metin`
sütunu bu yüksekliğin metin satırlarına düşen bölümüdür.

```js
// 08-yogunluk.mjs — satir yuksekliginden ekran basina kayit ve tarama maliyeti

const GORUNTU_YUKSEKLIGI = 900;
const SABIT_ALAN = 260; // baslik, arama alani ve suzgec seridi
const LISTE_ALANI = GORUNTU_YUKSEKLIGI - SABIT_ALAN;

const SONUC_SAYISI = 200;
const HEDEF_SIRA = 137; // aranan kaydin listedeki sirasi

const DOKUNMA_ESIGI = 24; // WCAG 2.5.8 en kucuk hedef boyutu (CSS px)

const duzeyler = [
  { ad: "sikisik", yazi: 14, carpan: 1.4, icBosluk: 6, alanSayisi: 1 },
  { ad: "dengeli", yazi: 16, carpan: 1.5, icBosluk: 12, alanSayisi: 2 },
  { ad: "ferah", yazi: 16, carpan: 1.6, icBosluk: 16, alanSayisi: 3 },
];

console.log(`liste alani: ${LISTE_ALANI}px   sonuc sayisi: ${SONUC_SAYISI}   hedef sira: ${HEDEF_SIRA}`);
console.log("");
console.log("duzey    satir  metin  bosluk  kayit/ekran  alan/ekran  ekran  hedefe  dokunma");
for (const d of duzeyler) {
  const satirYuksekligi = Math.round(d.yazi * d.carpan);
  const metin = d.alanSayisi * satirYuksekligi;
  const satir = metin + 2 * d.icBosluk + 1; // 1px ayirici cizgi
  const boslukOrani = (100 * (satir - metin)) / satir;
  const ekrandaKayit = Math.floor(LISTE_ALANI / satir);
  const bilgiBirimi = ekrandaKayit * d.alanSayisi;
  const ekranSayisi = Math.ceil(SONUC_SAYISI / ekrandaKayit);
  const hedefeKadar = Math.ceil(HEDEF_SIRA / ekrandaKayit);
  const dokunma = satir >= DOKUNMA_ESIGI ? "gecer" : "kalir";

  console.log(
    `${d.ad.padEnd(8)} ${String(satir).padStart(4)}px ${String(metin).padStart(6)} ` +
      `${boslukOrani.toFixed(1).padStart(6)}% ${String(ekrandaKayit).padStart(12)} ` +
      `${String(bilgiBirimi).padStart(11)} ${String(ekranSayisi).padStart(6)} ` +
      `${String(hedefeKadar).padStart(7)} ${dokunma.padStart(8)}`
  );
}

// Sikisik duzeyin ic boslugu 6px; karma aralik olceginde (4, 8, 12, 16, 24, ...) yok.
// Olcege oturan iki komsu adim icin ayni zincir yeniden hesaplanir.
console.log("");
console.log("sikisik duzeyin olcege oturtulmasi (yazi 14, carpan 1.4, tek alan)");
console.log("ic bosluk  satir  kayit/ekran  200 sonuc icin ekran  hedefe kadar");
for (const ic of [4, 6, 8]) {
  const satir = Math.round(14 * 1.4) + 2 * ic + 1;
  const kayit = Math.floor(LISTE_ALANI / satir);
  console.log(
    `${String(ic).padStart(9)}px ${String(satir).padStart(6)}px ${String(kayit).padStart(12)} ` +
      `${String(Math.ceil(SONUC_SAYISI / kayit)).padStart(21)} ${String(Math.ceil(HEDEF_SIRA / kayit)).padStart(13)}`
  );
}
```

```
liste alani: 640px   sonuc sayisi: 200   hedef sira: 137

duzey    satir  metin  bosluk  kayit/ekran  alan/ekran  ekran  hedefe  dokunma
sikisik    33px     20   39.4%           19          19     11       8    gecer
dengeli    73px     48   34.2%            8          16     25      18    gecer
ferah     111px     78   29.7%            5          15     40      28    gecer

sikisik duzeyin olcege oturtulmasi (yazi 14, carpan 1.4, tek alan)
ic bosluk  satir  kayit/ekran  200 sonuc icin ekran  hedefe kadar
        4px     29px           22                    10             7
        6px     33px           19                    11             8
        8px     37px           17                    12             9
```

#### Bilgi Miktarı Değil, Bilginin Biçimi Değişiyor

Çıktının en öğretici sütunu `alan/ekran` sütunudur: 19, 16, 15. Üç yoğunluk düzeyi
arasında ekranda görünen toplam alan sayısı yüzde 21 farkla neredeyse aynıdır.

Bu, yoğunluk tartışmasının yaygın çerçevesini bozar. "Sıkışık düzen daha çok bilgi
gösterir" cümlesi doğru değildir; sıkışık düzen **daha çok kayıt** gösterir, ferah düzen
**kayıt başına daha çok alan** gösterir. Ekranın taşıdığı toplam bilgi yaklaşık sabittir.
Karar, bilginin miktarı üzerinde değil dağılımı üzerinedir: 19 kaydın başlığı mı, 5 kaydın
üç alanı mı?

Bu çerçeveyle görev ayrımı doğrudan bir kurala dönüşür. Tarama görevinde kullanıcının
ihtiyacı çok sayıda başlıktır; sıkışık düzen 19 kayıt gösterir ve 137. kayda 8 ekranda
ulaşılır. Ferah düzen aynı kayda 28 ekranda ulaştırır — üç buçuk katı. Buna karşılık
değerlendirme görevinde ferah düzen her kaydın üç alanını birden verir ve kullanıcı
karşılaştırma için ayrıntı görünümüne girmek zorunda kalmaz.

#### Boşluk Oranı Sezgiyi Yanıltır

Çıktının `bosluk` sütunu beklenenin tersini söylüyor: sıkışık satırın boşluk oranı yüzde
39.4, ferah satırınki yüzde 29.7. "Ferah" olarak adlandırılan düzen, satır yüksekliğinin
daha **küçük** bir bölümünü boşluğa ayırıyor.

Çelişki görünüştedir. Ferah düzende satır uzundur çünkü içinde üç metin satırı vardır; iç
boşluk 16 piksele çıksa bile metnin payı daha hızlı büyür. Oran düşer, mutlak boşluk artar:
sıkışık satırda toplam 13 piksel boşluk vardır, ferah satırda 33 piksel.

Buradan iki sonuç çıkar. Birincisi, "ferah" ve "sıkışık" sözcükleri tek başına bir ölçü
vermez; hangi büyüklüğün ölçüldüğü söylenmelidir. İkincisi, yoğunluğu değiştirmenin iki
ayrı yolu vardır ve etkileri farklıdır: **iç boşluğu değiştirmek** satır yüksekliğini
doğrusal etkiler, **alan sayısını değiştirmek** ise satır yüksekliğini metin satırı
yüksekliği kadar sıçratır. Ferah düzende iç boşluk sıkışığın 2.67 katıdır ama satır
yüksekliği 3.36 katına çıkar; farkın kaynağı iki ek alandır.

Yoğunluğu düşürmek isteyen bir tasarımcı önce hangi kaldıraca bastığını bilmelidir. Alan
eklemek pahalıdır ve geri alınması zordur; iç boşluk değiştirmek ucuzdur ve ölçek adımları
arasında gezinir.

#### Yoğunluk Ölçeğin Neresinden Beslenir

Hesabın kendisi bir tutarsızlık ortaya çıkarıyor. Sıkışık düzeyin iç boşluğu 6 pikseldir
ve bu değer önceki derste seçilen karma aralık ölçeğinde — 4, 8, 12, 16, 24 ve devamı —
yoktur. Yoğunluk kararı ölçeğin dışına çıkmış durumdadır.

Çıktının ikinci bloğu, ölçeğe oturtmanın ne değiştirdiğini gösteriyor. İç boşluk 4 piksele
çekilirse satır 29 piksele iner, ekranda 22 kayıt görünür ve 137. kayda 7 ekranda ulaşılır.
8 piksele çıkarılırsa satır 37 piksele, ekran başına kayıt 17'ye, hedefe ulaşma 9 ekrana
gider. Ölçek dışı 6 piksel bu ikisinin arasındadır ve hiçbir şey kazandırmaz: 4 piksellik
adım daha çok kayıt gösterir, 8 piksellik adım daha rahat ayırır, 6 piksel ikisinin de
gerekçesini taşımaz.

Bu, ölçek ile yoğunluk arasındaki ilişkiyi netleştirir. Ölçek, yoğunluk kararını
**ayrıklaştırır**: sonsuz sayıda ara değer yerine sayılabilir birkaç seçenek bırakır. Her
seçeneğin sonucu — ekran başına kayıt sayısı, tarama maliyeti — önceden hesaplanabilir.
Yoğunluk böylece bir ayar düğmesi olmaktan çıkıp bir seçim hâline gelir; seçim
gerekçelendirilebilir, ara değer gerekçelendirilemez.

#### Yoğunluğu Kullanıcıya Bırakmak

Görev ayrımının bir ekranda çözülemediği durumlar vardır: aynı sonuç listesi hem hızlı
tarama hem karşılaştırma için kullanılıyor olabilir. Bu durumda yoğunluk bir ayar hâline
getirilir ve kullanıcı seçer.

Bu çözümün maliyeti gerçek ve çoğu zaman hafife alınır. Her yoğunluk düzeyi ayrı bir
düzendir: ayrı satır yüksekliği, ayrı alan kümesi, ayrı taşma davranışı. İki düzey iki kat
denetim demektir. Ayrıca seçimin kalıcı olması gerekir — kullanıcı her oturumda yeniden
seçmek zorunda kalırsa ayar bir kolaylık değil bir yük olur.

Ölçüt şudur: **yoğunluk ayarı, iki görevin de birincil olduğu kanıtlanabildiğinde
eklenir.** İki görevden biri açıkça baskınsa ayar eklenmez; baskın görev için doğru
yoğunluk seçilir, diğer görev ayrı bir ekrana taşınır. Katalog arayüzünde sonuç listesi
tarama için, kayıt ayrıntısı değerlendirme için vardır; ayara gerek yoktur.

#### Yoğunluğun Alt Sınırı

Yoğunluk serbestçe artırılamaz. Çıktının son sütunu kayıt satırının WCAG 2.5.8 ölçütünün
istediği 24 CSS pikselini karşılayıp karşılamadığını gösteriyor: üç düzey de geçiyor.

Ancak aynı ölçütün gelişmiş biçimi olan 2.5.5 hedef boyutunu 44 CSS pikseline çıkarır.
Sıkışık düzenin 33 piksellik satırı bu eşiğin altındadır; dengeli düzenin 73 pikseli ve
ferah düzenin 111 pikseli üstündedir. Yani sıkışık düzen, satırın tamamı tıklanabilir bir
hedefse yalnız en düşük eşiği karşılar.

Bu, yoğunluk kararının girdi türünden bağımsız verilemeyeceğini gösterir. Aynı 33 piksellik
satır, imleçle çalışan bir ortamda rahat, dokunmayla çalışan bir ortamda kaçırılan bir
hedeftir. Bu bağımlılığın tam ölçüsü bölümün son dersinde hesaplanacaktır.

#### Özet

- Bilgi yoğunluğu bir tercih değil, ekranın birincil görevinden türetilen bir karardır;
  tarama görevi yoğunluğu artırmaya, değerlendirme görevi azaltmaya çeker.
- Kayıt satırı yüksekliği ekran başına kayıt sayısını, o da tarama maliyetini belirler:
  137. kayda sıkışık düzende 8, ferah düzende 28 ekranda ulaşılır. Buna karşılık ekranda
  görünen toplam alan sayısı yaklaşık sabit kalır (19, 16, 15); değişen bilgi miktarı
  değil, bilginin kayıtlara dağılımıdır.
- Boşluk oranı sezgiyi yanıltır: ferah düzenin boşluk oranı daha düşüktür, çünkü satır
  yüksekliğini büyüten şey iç boşluk değil eklenen metin satırlarıdır.
- Yoğunluk kararı aralık ölçeğinin adımlarından beslenir; ölçek dışı 6 piksellik iç boşluk
  yerine 4 ya da 8 seçildiğinde ekran başına kayıt 22 ya da 17 olur ve her iki seçim de
  gerekçelendirilebilir, aradaki değer gerekçelendirilemez.
- Yoğunluk ayarı ancak iki görevin de birincil olduğu gösterilebiliyorsa eklenir; her ayar
  ikinci bir düzen ve kalıcı bir durum saklama yükü getirir.
- Yoğunluğun alt sınırını hedef boyutu ölçütleri çizer; 33 piksellik satır en düşük eşiği
  geçer, gelişmiş eşiğin altında kalır.

#### Sonraki Adım

Bu dersin hesapları tek bir görüntü alanı yüksekliği ve tek bir kapsayıcı genişliği
varsayarak yapıldı. Izgara dersinde 768 piksellik kapsayıcıda hiçbir sütun bölünmesinin
içerik ölçütlerini karşılamadığı görülmüştü. İki bulgu aynı soruya çıkıyor: düzen genişlik
değiştiğinde ne yapar? Sonraki ders duyarlı ve uyarlanabilir yaklaşımların farkını kurar,
sütun sayısının genişlikten nasıl türetildiğini hesaplar ve kesme noktalarının aygıt
listelerinden değil içerik ölçütlerinden çıkarıldığını gösterir.

### Duyarlı ve Uyarlanabilir Düzen

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/duyarli-ve-uyarlanabilir-duzen

Sürekli ve ayrık düzen yaklaşımlarının farkı; kesme noktasının içerik ölçütlerinden türetilmesi ve sürekli bir ızgarada sınanması gereken genişliklerin hesaplanması.

Önceki iki dersin hesapları tek bir kapsayıcı genişliğine dayanıyordu. Izgara dersi bu
varsayımın sınırını da göstermişti: 768 piksellik kapsayıcıda hiçbir sütun bölünmesi
içerik ölçütlerini karşılamıyordu. Yoğunluk dersi ise tek bir görüntü alanı yüksekliği
varsaymıştı.

Her iki bulgu aynı soruya çıkıyor: kapsayıcının genişliği tasarımcının seçtiği bir sayı
değil, kullanım anında belirlenen bir değişkense düzen ne yapar? Bu ders soruya iki farklı
yanıt veren yaklaşımı ayırır ve geçiş noktalarının nereden türetileceğini hesaplar.

#### Sürekli Düzen ve Ayrık Düzen

**Duyarlı düzen (responsive layout)**, kapsayıcı genişliğinin sürekli bir değişken olarak
ele alındığı yaklaşımdır. Ölçüler oranlarla ve en az/en çok sınırlarıyla verilir; düzen her
ara genişlikte tanımlıdır.

**Uyarlanabilir düzen (adaptive layout)**, önceden belirlenmiş birkaç genişlik için ayrı
ayrı düzenlerin tasarlandığı yaklaşımdır. Ara genişlikler en yakın düzene yuvarlanır; düzen
sonlu sayıda durumdan birinde bulunur.

Fark bir gerçekleştirim ayrıntısı değildir, tasarım sözleşmesinin kendisidir. Duyarlı
yaklaşımda tasarımcı **kuralları** tasarlar ve her ara genişliğin sonucunu kurala bırakır.
Uyarlanabilir yaklaşımda tasarımcı **durumları** tasarlar ve her durumun görünüşünü
doğrudan denetler.

Bundan iki asimetri doğar. Duyarlı yaklaşım daha az iş gerektirir ama daha az denetim verir: hiç
bakılmamış bir genişlikte düzen yine de tanımlıdır, doğru olduğu ise garanti değildir.
Uyarlanabilir yaklaşım daha çok iş gerektirir ama denetimi tam tutar: her durum tek tek
tasarlanmıştır ve tasarlanmamış bir durum yoktur.

#### Kesme Noktası İçerikten Türetilir

Her iki yaklaşımda da bir noktada düzenin biçim değiştirmesi gerekir. Bu geçiş genişliğine
**kesme noktası (breakpoint)** denir.

Kesme noktalarının yaygın seçilme biçimi aygıt genişlikleri listesidir. Bu yöntem beş yıl
dayanmaz ve zaten yanlış soruyu sorar: sorun aygıtın ne kadar geniş olduğu değil,
içeriğin ne kadar genişliğe ihtiyaç duyduğudur. Katalog arayüzünün iki ölçütü ızgara
dersinde konmuştu: süzgeç paneli en az 240 piksel, sonuç listesi en az 480 piksel.

Aşağıdaki program bu iki ölçütü sağlayan en küçük kapsayıcı genişliğini arar, ardından
sürekli bir kart ızgarasının sütun sayısını genişlikten türetir ve sütun sayısının
değiştiği tam genişlikleri bulur.

```js
// 09-duyarli.mjs — kesme noktasinin icerikten turetilmesi ve surekli sutun sayisi

const KENAR = 24;
const OLUK = 24;
const SUTUN_SAYISI = 12;

const SUZGEC_ENAZ = 240; // suzgec etiketleri sarmadan sigsin
const LISTE_ENAZ = 480; // kayit basliklari okunur uzunlukta kalsin

function sutunGenisligi(kapsayici) {
  return (kapsayici - 2 * KENAR - (SUTUN_SAYISI - 1) * OLUK) / SUTUN_SAYISI;
}
function acilim(n, sutun) {
  return n * sutun + (n - 1) * OLUK;
}

// 1) Iki sutunlu duzenin ayakta kalabildigi en kucuk kapsayici genisligi.
let enKucuk = null;
let enKucukBolunme = null;
for (let k = 600; k <= 1200; k++) {
  const s = sutunGenisligi(k);
  for (let sol = 2; sol <= 6; sol++) {
    if (acilim(sol, s) >= SUZGEC_ENAZ && acilim(SUTUN_SAYISI - sol, s) >= LISTE_ENAZ) {
      enKucuk = k;
      enKucukBolunme = [sol, SUTUN_SAYISI - sol];
      break;
    }
  }
  if (enKucuk !== null) break;
}
console.log("1) iki sutunlu duzenin esigi");
console.log(`   olcutler: suzgec >= ${SUZGEC_ENAZ}px, liste >= ${LISTE_ENAZ}px`);
console.log(`   en kucuk kapsayici: ${enKucuk}px   bolunme: ${enKucukBolunme.join("+")} sutun`);
const s0 = sutunGenisligi(enKucuk);
console.log(
  `   bu genislikte: suzgec ${acilim(enKucukBolunme[0], s0).toFixed(1)}px, ` +
    `liste ${acilim(enKucukBolunme[1], s0).toFixed(1)}px`
);
console.log(`   aygit listesinden gelen 768px bu esigin ${enKucuk - 768}px altinda`);

// 2) Surekli sutun sayisi: kart izgarasinin en az genisligi 220px, aralik 24px.
const KART_ENAZ = 220;
const ARALIK = 24;
function kartSayisi(kapsayici) {
  return Math.max(1, Math.floor((kapsayici + ARALIK) / (KART_ENAZ + ARALIK)));
}
console.log("");
console.log("2) kart izgarasi: en az kart genisligi 220px, aralik 24px");
console.log("   kapsayici  kart sayisi  kart genisligi");
for (const k of [360, 480, 720, 960, 1200, 1440]) {
  const n = kartSayisi(k);
  const genislik = (k - (n - 1) * ARALIK) / n;
  console.log(
    `   ${String(k).padStart(9)}  ${String(n).padStart(11)}  ${genislik.toFixed(1).padStart(14)}px`
  );
}

// 3) Sutun sayisinin degistigi tam genislikler: W + aralik = n * (enaz + aralik)
console.log("");
console.log("3) sutun sayisinin degistigi genislikler (sinanacak degerler)");
for (let n = 2; n <= 6; n++) {
  const esik = n * (KART_ENAZ + ARALIK) - ARALIK;
  console.log(`   ${n} sutuna gecis: ${esik}px  (dogrulama: ${kartSayisi(esik - 1)} -> ${kartSayisi(esik)})`);
}
console.log(`   320-1440 arasindaki tam sayi genislik sayisi: ${1440 - 320 + 1}`);
console.log("   sinanmasi gereken genislik sayisi: 5 gecis + 2 uc = 7");
```

```
1) iki sutunlu duzenin esigi
   olcutler: suzgec >= 240px, liste >= 480px
   en kucuk kapsayici: 816px   bolunme: 4+8 sutun
   bu genislikte: suzgec 240.0px, liste 504.0px
   aygit listesinden gelen 768px bu esigin 48px altinda

2) kart izgarasi: en az kart genisligi 220px, aralik 24px
   kapsayici  kart sayisi  kart genisligi
         360            1           360.0px
         480            2           228.0px
         720            3           224.0px
         960            4           222.0px
        1200            5           220.8px
        1440            6           220.0px

3) sutun sayisinin degistigi genislikler (sinanacak degerler)
   2 sutuna gecis: 464px  (dogrulama: 1 -> 2)
   3 sutuna gecis: 708px  (dogrulama: 2 -> 3)
   4 sutuna gecis: 952px  (dogrulama: 3 -> 4)
   5 sutuna gecis: 1196px  (dogrulama: 4 -> 5)
   6 sutuna gecis: 1440px  (dogrulama: 5 -> 6)
   320-1440 arasindaki tam sayi genislik sayisi: 1121
   sinanmasi gereken genislik sayisi: 5 gecis + 2 uc = 7
```

İlk blok kesme noktasını veriyor: 816 piksel. Bu sayı seçilmedi, iki içerik ölçütünden ve
ızgara aritmetiğinden çıktı. Aygıt listelerinde sık geçen 768 piksel bu eşiğin 48 piksel
altındadır; o genişlikte iki sütunlu düzen kurulursa süzgeç paneli ya da sonuç listesi
kendi en az genişliğinin altına iner.

Türetilmiş kesme noktasının bir üstünlüğü daha vardır: içerik değiştiğinde kesme noktası
da değişir. Süzgeç etiketleri uzarsa en az genişlik büyür, hesap yeniden çalıştırılır ve
yeni eşik bulunur. Aygıt listesinden alınan bir sayı, içerik değiştiğinde sessizce yanlış
hâle gelir.

Kesme noktası bulunduğunda düzen tek bir kuralla ifade edilebilir:

```css
/* Katalog sonuc sayfasi: iki sutunlu duzen yalniz esigin ustunde kurulur. */
.katalog {
  display: grid;
  gap: 24px;
  padding: 0 24px;
  grid-template-columns: 1fr;
}

@media (min-width: 816px) {
  .katalog {
    grid-template-columns: minmax(240px, 1fr) minmax(480px, 2fr);
  }
}
```

Bildirimdeki 240 ve 480 değerleri, kesme noktasını üreten ölçütlerin ta kendisidir; `1fr`
ve `2fr` oranı ise 4+8 sütun bölünmesinden gelir. Eşiğin altında tek sütun kurulur ve
süzgeç paneli listenin üstüne düşer.

#### Sürekli Düzen Kesme Noktası Gerektirmeyebilir

İkinci blok başka bir yol gösteriyor. Kart biçimindeki bir sonuç listesinde sütun sayısı
kesme noktalarıyla değil, doğrudan genişlikten türetilebilir. Kurala göre kapsayıcıya
sığabilecek en çok kart sayısı hesaplanır ve kalan yer kartlar arasında paylaştırılır.

Çıktının ikinci bloğunda kart genişlikleri 360, 228, 224, 222, 220.8 ve 220 pikseldir.
Genişlik arttıkça kart sayısı artar, kart genişliği ise en az değere doğru yaklaşır. Bu,
sürekli düzenin karakteristik davranışıdır: hiçbir genişlikte tanımsız kalmaz ve hiçbir
genişlikte ölçütü ihlal etmez.

Buradaki kural üç sayıya bağlıdır — en az kart genişliği, aralık ve kapsayıcı — ve tasarımcı
yalnız ilk ikisini seçer. Kesme noktası kararı ortadan kalkar. Bu her zaman mümkün değildir: kart
ızgarası gibi **aynı türden ögelerin yinelendiği** düzenlerde işler, ama süzgeç paneli ile sonuç
listesi gibi **farklı türden ögelerin yan yana durduğu** düzenlerde işlemez, çünkü ikisinin en az
genişliği ayrıdır ve biri diğerinin yerine geçemez.

#### Sürekli Düzen Sonsuz Sınama Gerektirmez

Sürekli düzene yöneltilen yaygın itiraz sınama maliyetidir: her genişlikte tanımlı olan bir
düzen, her genişlikte sınanmalı gibi görünür. 320 ile 1440 piksel arasında 1121 tam sayı
genişlik vardır.

Üçüncü blok bu itirazın yanlış olduğunu gösteriyor. Sütun sayısı yalnız beş genişlikte
değişir: 464, 708, 952, 1196 ve 1440 piksel. Bu değerler kural gereği kesindir; program
her birinin bir piksel altında ve tam üstünde sütun sayısını hesaplayarak geçişi
doğruluyor. İki geçiş arasındaki genişliklerde düzenin yapısı aynı kalır, yalnız kart
genişliği sürekli olarak değişir.

Sınanması gereken genişlik kümesi bu yüzden yedi elemanlıdır: beş geçiş noktası ve iki uç
değer. Sürekli düzen, sonsuz durumu sonlu sayıda geçişle tanımlar; sınama bu geçişlere
yapılır. Uyarlanabilir yaklaşımda ise sınanacak durum sayısı tasarlanan düzen sayısına
eşittir ve düzen sayısı arttıkça sınama yükü doğrusal büyür.

#### Hangisi Ne Zaman

İki yaklaşım rakip değildir; ölçüt, genişlik değiştiğinde neyin değiştiğidir.

Değişen yalnızca **diziliş** ise — aynı ögeler, başka bir sırada — duyarlı yaklaşım yeterlidir
ve daha ucuzdur. Kart ızgarasının üç sütundan iki sütuna inmesi böyledir; hiçbir öge
eklenmez ya da çıkarılmaz.

Değişen **içerik** ise uyarlanabilir yaklaşım gerekir. Dar bir görüntü alanında süzgeç
paneli listenin üstüne düşmez, bir açılır panele taşınır; ödünç alma eylemi kayıt satırından
çıkarılıp ayrıntı görünümüne bırakılır. Bunlar oranla ifade edilemez, çünkü öge kümesi
farklıdır.

Pratikte ikisi birlikte kullanılır: iki ya da üç ayrık durum tanımlanır, her durumun kendi
içinde ölçüler sürekli hesaplanır. Katalog arayüzü bu biçimdedir — 816 piksel eşiğinde bir
ayrık geçiş, eşiğin iki yanında sürekli davranış.

#### Özet

- Duyarlı düzen genişliği sürekli bir değişken olarak ele alır ve kurallar tasarlar;
  uyarlanabilir düzen sonlu sayıda durum tanımlar ve her durumu doğrudan tasarlar.
- Kesme noktası aygıt genişliklerinden değil, içeriğin en az genişlik gereksinimlerinden
  türetilir; katalog arayüzünün eşiği hesapla 816 piksel çıkar ve yaygın 768 değeri bu
  eşiğin 48 piksel altındadır.
- Türetilmiş kesme noktası içerik değiştiğinde yeniden hesaplanabilir; listeden alınan
  kesme noktası içerik değiştiğinde sessizce yanlışlanır.
- Aynı türden ögelerin yinelendiği ızgaralarda sütun sayısı doğrudan genişlikten
  türetilebilir ve kesme noktası kararı ortadan kalkar.
- Sürekli düzen sonsuz sınama gerektirmez: yapı yalnız sütun sayısının değiştiği
  genişliklerde değişir, ölçülen örnekte 1121 genişlik yerine 7 değer sınanır.
- Genişlik değiştiğinde değişen yalnız diziliş ise duyarlı yaklaşım yeterlidir; öge kümesi
  değişiyorsa uyarlanabilir yaklaşım gerekir ve ikisi çoğu zaman birlikte kullanılır.

#### Sonraki Adım

Bu ders genişliği tek değişken olarak ele aldı. Oysa dar bir görüntü alanı yalnız dar
değildir: çoğu zaman girdi türü de değişir, dokunma imlecin yerini alır ve hedeflerin
ölçüsü yeniden tanımlanır. Yoğunluk dersinde 33 piksellik satırın en düşük eşiği geçtiği
ama gelişmiş eşiğin altında kaldığı görülmüştü. Sonraki ders bu eşikleri ölçüt numaralarıyla
hesaplar, katalog araç çubuğundaki simge düğmelerini hedef boyutu ve aralık istisnası
ölçütleriyle sınar ve imlece bağlı etkileşimlerin dokunmada nasıl karşılıksız kaldığını
gösterir.

### Ekran Boyutu ve Girdi Türü

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/ekran-boyutu-ve-girdi-turu

Girdi türünün ekran genişliğinden bağımsız bir değişken olması; hedef boyutu ölçütlerinin, aralık istisnasının ve etkin alan genişletmenin hesapla denetlenmesi.

Önceki ders genişliği tek değişken olarak ele aldı ve düzenin genişlikle nasıl
değiştiğini hesapladı. Ancak dar bir görüntü alanı yalnız dar değildir. Çoğu zaman girdi
türü de değişir: imlecin bıraktığı yeri parmak alır, hedeflerin ölçüsü yeniden tanımlanır
ve imlece bağlı etkileşimlerin karşılığı ortadan kalkar.

Yoğunluk dersinde 33 piksellik kayıt satırının en düşük hedef eşiğini geçtiği ama gelişmiş
eşiğin altında kaldığı görülmüştü. Bu ders o eşikleri ölçüt numaralarıyla hesaplar, katalog
kayıt satırındaki simge düğmelerini sınar ve düzeltmenin maliyetini çıkarır.

#### Girdi Türü Ekran Boyutundan Bağımsızdır

**Girdi türü (input modality)**, kullanıcının arayüze dokunma, imleç ya da klavye
üzerinden hangi kesinlikle eriştiğini belirtir. Yaygın varsayım, dar ekranın dokunma, geniş
ekranın imleç anlamına geldiğidir. Bu varsayım üç yerde kırılır: geniş ekranlı dokunmatik
yüzeyler, dar pencereye küçültülmüş imleçli oturumlar ve ikisini birden taşıyan aygıtlar.
Dahası girdi türü oturum içinde değişebilir; kullanıcı aynı ekranda bir dakika parmakla,
bir dakika imleçle çalışabilir.

Bundan iki tasarım kuralı çıkar:

- **Ölçü kararları en kaba girdiye göre verilir.** Etkileşime açık her alan bir
  **dokunma hedefidir (touch target)** ve ölçüsü dokunmanın gerektirdiği değere göre
  belirlenir; imleçle çalışan kullanıcı büyük hedeften zarar görmez.
- **Yetenek kararları özellik saptamayla verilir, genişlikle değil.** Üzerine gelmenin
  desteklenip desteklenmediği ekran genişliğinden çıkarılamaz; ilgili ortam özelliğiyle
  sınanır.

#### Hedef Boyutu Ölçütleri

İki ölçüt vardır ve ikisi de CSS piksel cinsinden ölçülür. WCAG 2.5.8 hedefin en küçük
kenarının **24 piksel** olmasını ister; 2.5.5 aynı ölçüyü **44 piksele** çıkarır. İlkinin
bir de aralık istisnası vardır: hedef 24 pikselden küçük olsa bile, her hedefin sınırlayıcı
kutusuna çizilen 24 piksel çaplı daireler birbirini kesmiyorsa ölçüt sağlanmış sayılır.

Aşağıdaki program katalog kayıt satırının sağındaki dört simge düğmesini üç ayrı ölçü
kararıyla üretir ve her ikisini de denetler.

```js
// 10-hedef.mjs — hedef boyutu, aralik istisnasi ve etkin alan genisletmenin maliyeti

const ENAZ = 24; // WCAG 2.5.8 Hedef Boyutu (En Az)
const GELISMIS = 44; // WCAG 2.5.5 Hedef Boyutu (Gelismis)

// Katalog kayit satirinin sagindaki simge dugmeleri.
// Simge 16px, iki yanda ic bosluk, dugmeler arasinda ARALIK kadar bosluk.
function satirUret(simge, icBosluk, aralik, sayi, baslangicX) {
  const kenar = simge + 2 * icBosluk;
  const dugmeler = [];
  for (let i = 0; i < sayi; i++) {
    dugmeler.push({
      ad: `dugme-${i + 1}`,
      x: baslangicX + i * (kenar + aralik),
      y: 0,
      g: kenar,
      h: kenar,
    });
  }
  return dugmeler;
}

function merkez(d) {
  return { x: d.x + d.g / 2, y: d.y + d.h / 2 };
}
function merkezUzakligi(a, b) {
  const ma = merkez(a);
  const mb = merkez(b);
  return Math.hypot(ma.x - mb.x, ma.y - mb.y);
}

function denetle(ad, dugmeler) {
  console.log(ad);
  const enKucukKenar = Math.min(...dugmeler.map((d) => Math.min(d.g, d.h)));
  console.log(`  dugme olcusu: ${dugmeler[0].g}x${dugmeler[0].h}px   dugme sayisi: ${dugmeler.length}`);
  console.log(`  2.5.8 (>= ${ENAZ}px): ${enKucukKenar >= ENAZ ? "gecer" : "kalir"}`);
  console.log(`  2.5.5 (>= ${GELISMIS}px): ${enKucukKenar >= GELISMIS ? "gecer" : "kalir"}`);

  // Aralik istisnasi: her hedefin merkezine cizilen 24px capli daireler kesismemeli,
  // yani iki merkez arasindaki uzaklik en az 24px olmali.
  let enYakin = Infinity;
  for (let i = 0; i < dugmeler.length; i++) {
    for (let j = i + 1; j < dugmeler.length; j++) {
      enYakin = Math.min(enYakin, merkezUzakligi(dugmeler[i], dugmeler[j]));
    }
  }
  console.log(
    `  en yakin iki merkez arasi: ${enYakin}px   aralik istisnasi: ${enYakin >= ENAZ ? "gecer" : "kalir"}`
  );
  console.log("");
}

denetle("A) simge 16, ic bosluk 4, aralik 4", satirUret(16, 4, 4, 4, 0));
denetle("B) simge 16, ic bosluk 2, aralik 2", satirUret(16, 2, 2, 4, 0));
denetle("C) simge 16, ic bosluk 14, aralik 4", satirUret(16, 14, 4, 4, 0));

// Etkin alani gorsel olcuyu buyutmeden 44px'e cikarmak
const gorsel = 24;
const buyume = (GELISMIS - gorsel) / 2;
console.log("etkin alani gorsel olcuyu degistirmeden 44px'e cikarmak");
console.log(`  her yandan eklenecek: ${buyume}px`);
for (const aralik of [4, 12, 20, 24]) {
  const kalanBosluk = aralik - 2 * buyume;
  console.log(
    `  dugme araligi ${String(aralik).padStart(2)}px -> etkin alanlar ` +
      `${kalanBosluk >= 0 ? `${kalanBosluk}px aralikla ayrik` : `${-kalanBosluk}px ust uste biner`}`
  );
}

// Satirin tamami hedefse: 44px satir yuksekliginin tarama maliyetine etkisi
console.log("");
const LISTE_ALANI = 640;
console.log("satirin tamami hedefse ekran basina kayit");
for (const satir of [33, 44, 73]) {
  const kayit = Math.floor(LISTE_ALANI / satir);
  console.log(
    `  satir ${String(satir).padStart(3)}px -> ${String(kayit).padStart(2)} kayit/ekran   ` +
      `200 sonuc icin ${Math.ceil(200 / kayit)} ekran`
  );
}
```

```
A) simge 16, ic bosluk 4, aralik 4
  dugme olcusu: 24x24px   dugme sayisi: 4
  2.5.8 (>= 24px): gecer
  2.5.5 (>= 44px): kalir
  en yakin iki merkez arasi: 28px   aralik istisnasi: gecer

B) simge 16, ic bosluk 2, aralik 2
  dugme olcusu: 20x20px   dugme sayisi: 4
  2.5.8 (>= 24px): kalir
  2.5.5 (>= 44px): kalir
  en yakin iki merkez arasi: 22px   aralik istisnasi: kalir

C) simge 16, ic bosluk 14, aralik 4
  dugme olcusu: 44x44px   dugme sayisi: 4
  2.5.8 (>= 24px): gecer
  2.5.5 (>= 44px): gecer
  en yakin iki merkez arasi: 48px   aralik istisnasi: gecer

etkin alani gorsel olcuyu degistirmeden 44px'e cikarmak
  her yandan eklenecek: 10px
  dugme araligi  4px -> etkin alanlar 16px ust uste biner
  dugme araligi 12px -> etkin alanlar 8px ust uste biner
  dugme araligi 20px -> etkin alanlar 0px aralikla ayrik
  dugme araligi 24px -> etkin alanlar 4px aralikla ayrik

satirin tamami hedefse ekran basina kayit
  satir  33px -> 19 kayit/ekran   200 sonuc icin 11 ekran
  satir  44px -> 14 kayit/ekran   200 sonuc icin 15 ekran
  satir  73px ->  8 kayit/ekran   200 sonuc icin 25 ekran
```

Üç ölçü kararının üçü de aynı 16 piksellik simgeyi taşıyor; farkı yalnız iç boşluk
yaratıyor. A kararı 4 piksel iç boşlukla tam 24 pikselde durur ve en düşük eşiği geçer.
B kararı 2 piksel iç boşlukla 20 piksele iner ve iki ölçütü birden kaybeder. C kararı 14
piksel iç boşlukla 44 piksele çıkar ve ikisini de geçer.

Aradaki tek değişken, ekranda hiç görünmeyen bir değerdir. İç boşluk simgenin çevresindeki
şeffaf alandır; kullanıcı onu görmez, yalnız dokunduğunda varlığını fark eder. Hedef
boyutu bu yüzden görsel bir karar değil, **görünmeyen** bir karardır ve gözle
denetlenemez.

#### Aralık İstisnası Neyi Kurtarır

B kararında düğme ölçüsü 20 piksele indi ve ölçüt kaldı. Aralık istisnası bu durumu
kurtarabilir miydi? Çıktı hayır diyor: en yakın iki merkez arası 22 piksel, gereken 24.
İstisna da sağlanmıyor.

Aritmetik ilginçtir. B kararında düğmeler 20 piksel genişliğinde ve aralarında 2 piksel
var; merkezler arası uzaklık 22 piksel. İstisnanın geçmesi için ya düğme en az 22 piksele
çıkmalı ya da aralarındaki boşluk en az 4 piksel olmalı. İkinci yol daha ucuzdur: aynı
görsel ölçüyle, yalnız 2 piksel daha boşlukla ölçüt sağlanır.

Bu, aralık istisnasının ne işe yaradığını gösterir. İstisna, küçük hedefleri
meşrulaştırmak için değil, **hedefler arası boşluğun hedef boyutunun bir parçası
olduğunu** söylemek için vardır. Yan yana duran iki küçük düğme birbirini tehlikeli kılar;
tek başına duran bir küçük düğme kılmaz. Ölçüt, yalıtılmışlığı ödüllendirir.

#### Etkin Alanı Büyütmenin Gizli Maliyeti

Yaygın bir çözüm, görsel ölçüyü değiştirmeden **etkin alanı (hit area)** büyütmektir:
düğme 24 piksel görünür, 44 piksel tıklanır. Çıktının ikinci bloğu bu çözümün gizli
maliyetini veriyor.

24 pikselden 44 piksele çıkmak her yandan 10 piksel ekler. Düğmeler arasındaki boşluk 4
pikselse, iki komşu düğmenin etkin alanları 16 piksel üst üste biner: üst üste binen
bölgeye yapılan bir dokunuşun hangi düğmeye gideceği tasarımın söyleyemediği bir şeydir.
Boşluk 12 pikselde hâlâ 8 piksel binişme kalır. Ayrılma ancak 20 piksellik boşlukta
sağlanır.

Sonuç şudur: **etkin alanı büyütmek, aralığı da büyütmeden yapılamaz.** Görsel ölçüyü
sabit tutup dokunma kolaylığı kazanma vaadi, komşu hedefler varken karşılıksızdır. Tek
başına duran bir düğmede — örneğin kayıt ayrıntısındaki "Ödünç al" düğmesinde — vaat
geçerlidir, çünkü çakışacak komşu yoktur.

#### Satırın Tamamı Hedefse

Katalog sonuç listesinde tıklanabilir olan çoğu zaman tek tek simgeler değil, kayıt
satırının tamamıdır. Bu durumda hedef boyutu doğrudan satır yüksekliğidir ve yoğunluk
kararıyla aynı sayıya bağlanır.

Çıktının son bloğu bağlantıyı gösteriyor. Sıkışık düzenin 33 piksellik satırı ekranda 19
kayıt gösterir ve 200 sonuç 11 ekranda taranır. Satır gelişmiş eşiğe, 44 piksele
çıkarıldığında ekranda 14 kayıt kalır ve tarama 15 ekrana çıkar — dört ekran daha. Dengeli
düzenin 73 piksellik satırında maliyet 25 ekrandır.

Bu, yoğunluk dersindeki ödünleşimin girdi türüne bağlı olduğunu kesinleştirir. Aynı liste,
imleçle çalışan bir oturumda 33 piksellik satırla 11 ekranda taranabilir; dokunmayla
çalışan bir oturumda gelişmiş eşiği tutmak için 15 ekrana çıkar. Fark yüzde 36'dır ve
tasarımın bunu ya kabul etmesi ya da satır dışındaki bir hedefle çözmesi gerekir — örneğin
satırın tamamını değil, yalnız kayıt başlığını hedef yapmak ve başlığın kendi yüksekliğini
eşiğe çıkarmak.

#### İmlece Bağlı Etkileşimin Karşılığı Yoktur

Ölçü dışında bir de yetenek farkı vardır. İmleç bir konum bildirir ve bu konum tıklama
olmadan da bilinir; dokunma böyle bir konum üretmez. **Üzerine gelme (hover)** durumunda
görünen her şey, dokunmayla çalışan bir oturumda ya hiç görünmez ya da ancak dokunulduktan
sonra görünür.

Buradan bir kısıt çıkar: **üzerine gelme, bilgi taşıyan tek yol olamaz.** Kayıt
satırındaki simge düğmeleri yalnız üzerine gelindiğinde beliriyorsa, dokunmayla çalışan
kullanıcı onların varlığını bilemez. Aynı biçimde, bir kısaltmanın açılımı ya da bir raf
kodunun anlamı yalnız ipucu balonunda veriliyorsa, o bilgi bir kullanıcı kümesi için yok
demektir.

Üzerine gelmenin meşru kullanımı **pekiştirmedir**: zaten görünen bir şeyin
tıklanabilirliğini vurgulamak, zaten yazılı olan bir etiketi belirginleştirmek. Yeteneğin
varlığı ekran genişliğinden değil, ilgili ortam özelliğinden okunur; `hover` ve `pointer`
ortam özellikleri bu ayrımı doğrudan sorgular ve `pointer: coarse` değeri kaba çözünürlüklü
bir girdiyi bildirir. Ölçü kararlarının bu sorgulamaya bağlanmaması gerekir: hedef boyutu
her koşulda en kaba girdiye göre verilir, çünkü girdi türü oturum içinde değişebilir.

#### Özet

- Girdi türü ekran genişliğinden türetilemez; ölçü kararları en kaba girdiye göre verilir,
  yetenek kararları ise ortam özelliği sorgulanarak verilir.
- Hedef boyutu görünmeyen bir karardır: aynı 16 piksellik simge, iç boşluğa göre 20, 24 ya
  da 44 piksellik hedef üretir ve fark ekranda görünmez.
- Aralık istisnası küçük hedefleri meşrulaştırmaz; hedefler arası boşluğun hedef boyutunun
  parçası olduğunu söyler ve yalıtılmış hedefi ödüllendirir.
- Etkin alanı görsel ölçüyü değiştirmeden büyütmek komşu hedefler varken karşılıksızdır:
  24 pikselden 44 piksele çıkmak her yandan 10 piksel ekler ve 20 pikselden dar aralıkta
  etkin alanlar üst üste biner.
- Satırın tamamı hedefse yoğunluk kararı ile hedef boyutu aynı sayıya bağlanır; 33 piksellik
  satır 11 ekranda, 44 piksellik satır 15 ekranda taranır.
- Üzerine gelme bilgi taşıyan tek yol olamaz; meşru kullanımı zaten görünen bir şeyi
  pekiştirmektir.

#### Sonraki Adım

Düzen iskeleti bu dersle tamamlandı: sütunlar, oluklar ve kenar boşlukları ekran
genişliğinden türetiliyor, aralıklar kapalı bir ölçekten geliyor, yoğunluk görevden
çıkarılıyor ve hedef ölçüsü en kaba girdiye göre belirleniyor. İskeletin içine girecek
metin ise henüz seçilmedi. Katalog arayüzünün taşıdığı yükün neredeyse tamamı metindir ve
bu ders boyunca metin hep bir dikdörtgen gibi ele alındı — 16 piksel yüksekliğinde, 8
piksel genişliğinde karakterlerden oluşan bir blok. Sonraki bölüm bu varsayımı açar ve
yazı tipi seçimini beğeniden ölçüte taşır: görünen büyüklük nasıl hesaplanır, hangi
karakterler birbirine karışır, bir yazı tipi ailesi kaç hiyerarşi düzeyi taşıyabilir.

## Tipografi ve Renk

Yazı tipi seçimi, tipografik ölçek, satır uzunluğu ve aralığı, renk rolleri, işlevsel renkler, kontrast eşikleri ve koyu tema dönüşümü.

### Yazı Tipi Seçimi

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/yazi-tipi-secimi

Seçilebilirlik ile okunabilirliğin ayrımı, yazı tipi sınıflarının vaatleri, x-yüksekliğinden görünen büyüklük hesabı ve abece kapsamı ile karakter ayırt edilebilirliğinin denetlenmesi.

Önceki ders düzen iskeletini tamamladı: sütunlar, oluklar ve kenar boşlukları ekran
genişliğine göre yerini aldı, dokunma hedeflerinin en küçük ölçüsü belirlendi ve imleçle
dokunmanın aynı arayüzden farklı şeyler beklediği gösterildi. İskelet ayakta; içine
girecek metin ise henüz seçilmedi. Katalog arayüzünün taşıdığı yükün neredeyse tamamı
metindir: kayıt başlıkları, yazar adları, raf kodları, ISBN numaraları, tarihler,
ödünç alma düğmesinin üzerindeki iki sözcük.

Bu ders tek bir soruyu yanıtlar: bir yazı tipi, beğeniyle değil hangi ölçütlerle seçilir?
Ölçütler dört başlıkta toplanır ve üçü hesaplanabilir — görünen büyüklük, abece kapsamı ve
karakter ayırt edilebilirliği. Dördüncüsü, ailenin taşıyabileceği hiyerarşi yükü, ilk
dersteki iki kanal kuralının doğrudan sonucudur.

#### Seçilebilirlik ile Okunabilirlik Ayrı Şeylerdir

İki ölçüt sık sık tek sözcükle anılır ve karışır.

**Seçilebilirlik (legibility)**, tek bir karakterin komşularından ayırt edilebilme
derecesidir. Bir raf kodundaki `I` harfinin `1` rakamından ayrılıp ayrılmadığı bir
seçilebilirlik sorusudur. Karakter düzeyinde ölçülür ve yazı tipinin kendi özelliğidir.

**Okunabilirlik (readability)**, bir metin bloğunun akıcı okunma derecesidir. Satır
uzunluğu, satır aralığı, hizalama ve kontrast oranı bu ölçüte girer. Blok düzeyinde
ölçülür ve yazı tipinin değil, tasarımın özelliğidir.

Ayrımın pratik sonucu şudur: seçilebilirlik sorunu yazı tipi değiştirilerek çözülür,
okunabilirlik sorunu ise düzen değiştirilerek. Katalog arayüzünde raf kodunun yanlış
okunması birinci sınıfa, arama sonuçları listesinin göz yorması ikinci sınıfa girer. İkisi
aynı çözümle iyileşmez.

Katalog arayüzünün iki ayrı metin kütlesi vardır ve ölçütleri farklıdır. Sonuç listesi ile
üstveri alanları kısa, taranan, birbirine benzeyen dizgilerden oluşur; buralarda
seçilebilirlik belirleyicidir. Kayıt ayrıntısındaki özet paragrafı ise sürekli okunur;
orada okunabilirlik belirleyicidir.

#### Sınıflar ve Vaat Ettikleri

Yazı tipleri, taşıdıkları biçim özelliklerine göre sınıflara ayrılır. Sınıf, ürün adı
vermeden konuşmayı sağlar ve daha önemlisi, her sınıfın ne vaat ettiğini söyler.

- **Tırnaklı (serif).** Harf uçlarında bitiş çizgileri taşır. Bu çizgiler yatay bir akış
  ipucu üretir; uzun sürekli metinde işe yarar. Küçük boyutlarda bitiş çizgileri incelir
  ve düşük çözünürlükte kaybolabilir.
- **Tırnaksız (sans-serif).** Bitiş çizgisi yoktur. Küçük boyutlarda ve arayüz
  etiketlerinde daha kararlıdır; harf gövdeleri sadeleştiği için birbirine benzeyen
  karakterlerin ayrımı zayıflayabilir.
- **Eşit aralıklı (monospace).** Her karakter aynı ilerleme genişliğini kaplar. Sütun
  hizası gerektiren yerlerde — raf kodu, ISBN, tarih — hizalamayı ücretsiz verir. Sürekli
  metinde sözcük şekilleri bozulduğu için okuma hızını düşürür.
- **Değişken yazı tipi (variable font).** Tek dosyada sürekli bir ağırlık ekseni taşır;
  her ağırlık için ayrı dosya indirilmesi gerekmez. Sağladığı şey yeni bir biçim değil,
  ağırlık kanalının ucuzlamasıdır.

CSS tarafında bu sınıfların karşılığı genel aile anahtar sözcükleridir: `serif`,
`sans-serif`, `monospace`. Bunlar CSS ile Görsel Sunum kursunda aile listesinin son
öğesi olarak tanıtılmıştı; tasarım tarafında ise doğrudan bir sınıf kararıdır.

Katalog arayüzünün kararı bu ayrımdan çıkar: gövde ve başlıklar için tek bir tırnaksız
aile, raf kodu ve ISBN alanları için eşit aralıklı bir aile. İki aile, çünkü iki farklı
iş var; üç aile ise savunulamaz, çünkü üçüncü işin adı konamaz.

#### Nominal Boyut Görünen Büyüklük Değildir

Aynı boyutta yazılmış iki yazı tipi aynı büyüklükte görünmez. Nominal boyut em kutusunun
ölçüsüdür; gözün büyüklük olarak algıladığı şey ise küçük harflerin gövde yüksekliği,
yani **x-yüksekliği (x-height)** değeridir. Yazı tipi dosyası bu ölçüyü kendi metaverisinde
em birimi cinsinden bildirir, dolayısıyla hesaplanabilir.

```js
// xyukseklik.mjs — bildirilen yazi tipi olculerinden gorunen buyukluk hesabi

// Her yazi tipi dosyasi olculerini em birimi cinsinden bildirir (burada em = 1000 birim).
// Asagidakiler uc aday ailenin bildirdigi olculerdir.
const adaylar = [
  { ad: "aday-1", xYukseklik: 480, buyukHarf: 700, cikinti: 750, inis: 250 },
  { ad: "aday-2", xYukseklik: 520, buyukHarf: 715, cikinti: 800, inis: 230 },
  { ad: "aday-3", xYukseklik: 548, buyukHarf: 730, cikinti: 776, inis: 275 },
];

const EM = 1000;
const TABAN_PX = 16; // govde metni icin secilen nominal boyut

console.log("aday      x/em    x-yuksekligi  buyuk harf  x/buyuk harf");
for (const a of adaylar) {
  const xOran = a.xYukseklik / EM;
  const xPx = xOran * TABAN_PX;
  const bhPx = (a.buyukHarf / EM) * TABAN_PX;
  console.log(
    `${a.ad.padEnd(9)} ${xOran.toFixed(3)}   ${xPx.toFixed(2).padStart(9)} px ${bhPx.toFixed(2).padStart(8)} px  ${(a.xYukseklik / a.buyukHarf).toFixed(3)}`
  );
}

// Ayni gorunen buyuklugu tutturmak icin gereken nominal boyut.
// Hedef, aday-3'un 16 pikselde urettigi x-yuksekligidir.
const hedefX = (548 / EM) * TABAN_PX;
console.log(`\nhedef x-yuksekligi: ${hedefX.toFixed(2)} px (aday-3, 16px)`);
console.log("aday      esdeger nominal boyut   fark");
for (const a of adaylar) {
  const gereken = hedefX / (a.xYukseklik / EM);
  const fark = gereken - TABAN_PX;
  console.log(
    `${a.ad.padEnd(9)} ${gereken.toFixed(2).padStart(18)} px ${(fark >= 0 ? "+" : "") + fark.toFixed(2)} px`
  );
}

// Satir kutusunun tasidigi dikey olcu: cikinti + inis.
console.log("\naday      cikinti+inis (em)   esdeger boyutta (px)");
for (const a of adaylar) {
  const toplam = (a.cikinti + a.inis) / EM;
  const gereken = hedefX / (a.xYukseklik / EM);
  console.log(
    `${a.ad.padEnd(9)} ${toplam.toFixed(3).padStart(16)} ${(toplam * gereken).toFixed(2).padStart(21)}`
  );
}
```

```
aday      x/em    x-yuksekligi  buyuk harf  x/buyuk harf
aday-1    0.480        7.68 px    11.20 px  0.686
aday-2    0.520        8.32 px    11.44 px  0.727
aday-3    0.548        8.77 px    11.68 px  0.751

hedef x-yuksekligi: 8.77 px (aday-3, 16px)
aday      esdeger nominal boyut   fark
aday-1                 18.27 px +2.27 px
aday-2                 16.86 px +0.86 px
aday-3                 16.00 px +0.00 px

aday      cikinti+inis (em)   esdeger boyutta (px)
aday-1               1.000                 18.27
aday-2               1.030                 17.37
aday-3               1.051                 16.82
```

Çıktının ikinci bloğu, "16 piksel" ifadesinin tek başına bir şey söylemediğini gösteriyor.
Aynı görünen büyüklüğü tutturmak için aday-1'in 18.27 piksele çıkması gerekiyor; aradaki
fark 2.27 piksel, yani taban boyutun yüzde on dördü. Bir yazı tipi değiştirildiğinde bütün
boyut ölçeğinin yeniden ayarlanması gerekmesinin nedeni budur. Ölçek, bir sonraki dersin
konusudur ve girdisi bu hesaptır.

Üçüncü blok ikinci bir sonucu veriyor. Çıkıntı ile inişin toplamı, satır kutusunun taban
yüksekliğini belirler. Aday-3, eşdeğer büyüklükte daha küçük bir nominal boyutta çalıştığı
için satır kutusu da daha kısa oluyor: 16.82 piksele karşı 18.27. Aynı satır aralığı
oranıyla dizilmiş iki metin bloğundan biri, aynı görünen büyüklükte olmasına rağmen daha
sıkı duracaktır.

Büyük harf yüksekliğine oranlanmış x-yüksekliği (`x/buyuk harf` sütunu) ikinci bir işaret
verir. Bu oran yükseldikçe küçük harfler büyük harflere yaklaşır; büyük harfle başlayan
sözcüklerin baştaki çıkıntısı zayıflar. Sözcük şeklinin taranmayı hızlandırdığı yerlerde —
arama sonuçlarındaki kayıt başlıkları — bu, çok yüksek bir oranın bedelidir.

#### Karakterlerin Birbirinden Ayrılması

Seçilebilirliğin iki denetlenebilir yüzü vardır: gereken karakterlerin yazı tipinde
bulunması ve bulunanların birbirine karışmaması. İkisi de arayüzde gerçekten geçen metin
üzerinden sınanır, örnek bir cümle üzerinden değil.

```js
// kapsam.mjs — katalog metinlerinin yazi tipi kapsamiyla ve ayirt edilebilirlikle denetimi

// Katalog arayuzunde gercekten gorunen alanlar.
const kayitlar = [
  { baslik: "Işık ve Gölge: Bir Optik Tarihi", yazar: "Öztürk, Şükran", yil: 1998, rafKodu: "QC353.I8", isbn: "9789750718533" },
  { baslik: "Çağdaş Türk Şiirinde İmge", yazar: "Ilgaz, İbrahim", yil: 2004, rafKodu: "PL248.C1", isbn: "9789944882101" },
  { baslik: "Ağ Protokolleri El Kitabı", yazar: "Gündoğdu, Elif", yil: 2011, rafKodu: "TK5105.5", isbn: "9786055829476" },
];

// 1) Kapsam denetimi: aday yazi tipinin bildirdigi kod noktasi araliklari.
const araliklar = {
  "yalniz-latin-temel": [[0x0020, 0x007e]],
  "latin-1-ekli": [[0x0020, 0x007e], [0x00a0, 0x00ff]],
  "latin-genisletilmis-a": [[0x0020, 0x007e], [0x00a0, 0x00ff], [0x0100, 0x017f], [0x2010, 0x2027]],
};

function kapsiyorMu(araliklar, kod) {
  return araliklar.some(([alt, ust]) => kod >= alt && kod <= ust);
}

const metin = kayitlar.map((k) => `${k.baslik} ${k.yazar} ${k.yil} ${k.rafKodu} ${k.isbn}`).join(" ");
const kullanilan = [...new Set([...metin])].sort((a, b) => a.codePointAt(0) - b.codePointAt(0));

console.log(`katalog metinlerinde gecen farkli karakter sayisi: ${kullanilan.length}`);
for (const [ad, ar] of Object.entries(araliklar)) {
  const eksik = kullanilan.filter((c) => !kapsiyorMu(ar, c.codePointAt(0)));
  const yazim = eksik
    .map((c) => `${c}(U+${c.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")})`)
    .join(" ");
  console.log(`${ad.padEnd(24)} eksik ${String(eksik.length).padStart(2)}: ${yazim || "-"}`);
}

// 2) Ayirt edilebilirlik: birbirine karisabilen karakter kumeleri.
const karisabilir = [
  ["I", "l", "1", "|"],
  ["0", "O", "o"],
  ["5", "S", "s"],
  ["8", "B"],
  ["I", "İ", "i", "ı"],
];

console.log("\nalan       karisabilir kume icinden birlikte gecenler");
const alanlar = [
  ["rafKodu", kayitlar.map((k) => k.rafKodu)],
  ["isbn", kayitlar.map((k) => String(k.isbn))],
  ["yazar", kayitlar.map((k) => k.yazar)],
];
for (const [alanAdi, degerler] of alanlar) {
  const harfler = new Set(degerler.join("").split(""));
  const catisan = karisabilir
    .map((kume) => kume.filter((c) => harfler.has(c)))
    .filter((v) => v.length >= 2)
    .map((v) => v.join("/"));
  console.log(`${alanAdi.padEnd(10)} ${catisan.length ? catisan.join("  ") : "-"}`);
}
```

```
katalog metinlerinde gecen farkli karakter sayisi: 54
yalniz-latin-temel       eksik  9: Ç(U+00C7) Ö(U+00D6) ö(U+00F6) ü(U+00FC) ğ(U+011F) İ(U+0130) ı(U+0131) Ş(U+015E) ş(U+015F)
latin-1-ekli             eksik  5: ğ(U+011F) İ(U+0130) ı(U+0131) Ş(U+015E) ş(U+015F)
latin-genisletilmis-a    eksik  0: -

alan       karisabilir kume icinden birlikte gecenler
rafKodu    I/1
isbn       -
yazar      I/l  I/İ/i
```

Birinci blok, kapsam sorununun neden gözle yakalanamadığını gösteriyor. Latin-1 ekli
kapsam Türkçenin çoğu harfini karşılar; eksik kalan beş kod noktası tam olarak Latin
Genişletilmiş-A bölgesindeki noktalı ve noktasız `i` çifti, `ğ` ve `ş` harfleridir.
Bir yazı tipi, örnek metinde bu beş harf geçmediği sürece kusursuz görünür. Yedek aileye
düşen harfler farklı bir x-yüksekliğiyle çizilir ve aynı sözcük içinde iki ayrı yazı tipi
belirir. Kod noktaları M01/K01 Bilgisayarlar Nasıl Çalışır kursunda tanımlanmıştı; buradaki
denetim onların doğrudan bir uygulamasıdır.

İkinci blok, hangi alanın hangi karışıklığı taşıdığını söylüyor. Raf kodu alanında `I` ile
`1` birlikte geçiyor; bu alan bir kütüphanede sesli okunarak ya da elle not edilerek
kullanılır, yani karışıklığın bedeli somuttur. ISBN alanı yalnız rakam taşıdığı için
listedeki hiçbir kümeyle çakışmıyor. Yazar alanında ise Türkçeye özgü bir çakışma var:
büyük `I`, küçük `l` ve noktalı `İ` aynı alanda bulunuyor.

Bu bulgunun iki ayrı çözümü vardır ve ikisi farklı yerlere dokunur. Raf kodu alanına eşit
aralıklı aileyi vermek, harf ile rakamı iki farklı çizim geleneğine ayırır — bu bir yazı
tipi kararıdır. Yazar alanındaki `I` ve `İ` karışıklığı ise yazı tipiyle çözülmez, çünkü
ikisi de gerçekten gereken ayrı harflerdir; orada gereken şey, adayın büyük `I` harfini
tabanlı ve kancalı biçimlerden hangisiyle çizdiğini denetlemektir.

#### Ailenin Taşıyabileceği Hiyerarşi Yükü

Görsel Hiyerarşi dersinde kritik ayrımların en az iki kanalda ifade edilmesi kural olarak
konmuştu. Kanallardan biri ağırlıktı. Bir yazı tipi ailesinin sunduğu ağırlık sayısı, bu
kuralın kaç yerde uygulanabileceğini doğrudan sınırlar.

Katalog arayüzü en az üç ağırlık düzeyi ister: kayıt başlığı için kalın, gövde için normal,
bastırılmış üstveri için yine normal ama düşük kontrastlı. İki ağırlık sunan bir aile bunu
karşılar. Tek ağırlık sunan bir aile karşılamaz; o durumda hiyerarşinin bütün yükü boyut ve
kontrast kanallarına biner ve ilk derste hesaplanan "tek bacaklı ayrım" durumu üretilir.

Rakam biçimi ikinci bir kısıttır. Bir ailenin rakamları **orantılı (proportional)** ya da
**eşit genişlikli (tabular)** çizilmiş olabilir. Orantılı rakamlarda `1` diğerlerinden dar
durur; alt alta dizilmiş yayın yıllarında sütun kayar. Katalog sonuç listesi yılları bir
sütunda hizalıyorsa eşit genişlikli rakam gerekir. Eşit aralıklı aile bunu zaten sağlar;
tırnaksız ailede ise ayrıca bildirilmiş bir özellik olması gerekir.

Üçüncü kısıt, gerçek eğik biçimin bulunup bulunmadığıdır. Ailede eğik kesim yoksa
tarayıcı harfleri yatırarak taklit üretir; bu taklit, harf uçlarını bozar ve küçük
boyutlarda seçilebilirliği düşürür. Katalog arayüzünde eğik biçim yalnızca bir yerde
gerekiyor — kayıt ayrıntısındaki kaynak künyesi — ve o tek yer bile ailenin seçimini
kısıtlar.

Bu üç kısıt bir araya geldiğinde seçim ölçütü sadeleşir: aile, arayüzün istediği kanalları
gerçekten sunuyor mu? Sunmuyorsa, eksik kanalın yükü başka kanallara aktarılır ve o
kanallar zaten doludur.

#### Özet

- Seçilebilirlik karakter düzeyinde, okunabilirlik blok düzeyinde ölçülür; birincisi yazı
  tipi değişikliğiyle, ikincisi düzen değişikliğiyle çözülür.
- Yazı tipi sınıfları ürün adı vermeden karar vermeyi sağlar; her sınıf belirli bir işi
  ucuzlatır ve başka bir işi pahalılaştırır.
- Nominal boyut görünen büyüklüğü vermez; görünen büyüklük x-yüksekliği oranından
  hesaplanır ve aynı ölçüyü tutturmak farklı ailelerde farklı nominal boyut gerektirir.
- Abece kapsamı, arayüzde gerçekten geçen kod noktaları üzerinden denetlenir; eksik
  kapsam örnek metinde görünmez ama yedek aileye düşen harflerle kendini gösterir.
- Karakter karışıklığı alan alan denetlenir; hangi karışabilir kümenin hangi alanda
  gerçekten bir arada geçtiği hesaplanabilir bir sorudur.
- Ailenin sunduğu ağırlık sayısı, rakam genişliği ve gerçek eğik kesimi, hiyerarşinin kaç
  kanalda kurulabileceğini doğrudan sınırlar.

#### Sonraki Adım

Bu ders tek bir boyutu — gövde metninin taban boyutunu — sabitledi ve o boyutun ailelere
göre değiştiğini gösterdi. Arayüzde ise tek boyut yoktur: sayfa başlığı, kayıt başlığı,
yazar adı, üstveri ve düğme metni ayrı basamaklardadır. Bu basamaklar tek tek seçilirse
sayı kalabalığı büyür ve ilk derste ölçülen ayrım eşikleri rastgele tutturulur ya da
kaçırılır. Sonraki ders basamakları bir tabandan ve bir orandan türetir, üretilen ölçeği
piksel ızgarasına yuvarlar ve komşu basamakların ayrım eşiğini gerçekten geçip geçmediğini
hesaplar.

### Tipografik Ölçek

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/tipografik-olcek

Boyut basamaklarının bir taban ve bir orandan türetilmesi, yuvarlamanın komşu basamak oranını nasıl bozduğu, basamak bütçesinin hesabı ve düzeylerin basamaklara yerleştirilmesi.

Önceki ders gövde metninin taban boyutunu bir yazı tipi kararına bağladı ve aynı görünen
büyüklüğün ailelere göre farklı nominal boyutlar gerektirdiğini hesapladı. Ancak arayüzde
tek bir boyut yoktur. Katalog arayüzü en az beş metin düzeyi taşıyor: sayfa başlığı, kayıt
başlığı, ödünç alma düğmesinin metni, yazar adı ve üstveri. Bu beş sayı tek tek seçilirse
ne olur?

Olan şudur: sayılar birbirinden bağımsız seçilir, aralarındaki oranlar rastgele çıkar ve
Görsel Hiyerarşi dersinde konan 1.15 boyut oranı eşiği kimi çiftte tesadüfen tutturulur,
kimi çiftte kaçırılır. Ölçek, bu tesadüfü ortadan kaldıran yapıdır: boyutlar tek tek
seçilmez, **bir tabandan ve bir orandan türetilir.**

#### Ölçek Bir Üstel Dizidir

**Tipografik ölçek (typographic scale)**, bir taban boyuttan başlayarak sabit bir oranla
çarpılan ya da bölünen boyut basamakları dizisidir. `n` numaralı basamağın boyutu şu
biçimde yazılır:

$$
b_n = T \cdot r^{\,n}
$$

Burada $T$ taban boyut, $r$ ölçek oranı, $n$ ise basamak numarasıdır. Taban basamağı sıfır
kabul edilir; pozitif basamaklar büyür, negatif basamaklar küçülür. Ölçeğin verdiği güvence
şudur: **herhangi iki komşu basamağın oranı sabittir ve $r$ değerine eşittir.** Ayrımı
denetlemek için tek bir sayıya bakmak yeterli olur.

Bu yapı M14/K02 CSS ile Görsel Sunum kursunda modüler ölçek adıyla tanıtılmıştı; orada
sorun bir bildirim yazma sorunuydu. Burada sorun karar sorunudur: hangi oran, kaç basamak
ve hangi düzey hangi basamağa.

#### Yuvarlama Sabit Oranı Bozar

Formül sürekli bir dizi üretir; ekran ise metni tam sayı piksel değerlerinde daha kararlı
çizer. Aradaki fark önemsiz görünür ama komşu basamak oranını doğrudan bozar.

```js
// olcek.mjs — oran tabanli tipografik olcek uretimi ve yuvarlamanin ayrima etkisi

const TABAN = 16;          // govde metni boyutu (px)
const ADIMLAR = [-2, -1, 0, 1, 2, 3, 4];
const ORANLAR = [1.125, 1.200, 1.250, 1.333];
const ESIK = 1.15;         // Gorsel Hiyerarsi dersinde konan boyut orani esigi

function olcek(oran) {
  return ADIMLAR.map((n) => {
    const ham = TABAN * Math.pow(oran, n);
    return { n, ham, yuvarlanmis: Math.round(ham) };
  });
}

for (const oran of ORANLAR) {
  const s = olcek(oran);
  console.log(`\n--- oran ${oran.toFixed(3)} ---`);
  console.log("adim   ham (px)  yuvarlanmis  komsu orani (ham)  komsu orani (yuv)  esik");
  for (let i = 0; i < s.length; i++) {
    const a = s[i];
    if (i === 0) {
      console.log(
        `${String(a.n).padStart(4)} ${a.ham.toFixed(3).padStart(10)} ${String(a.yuvarlanmis).padStart(12)}` +
          `${"-".padStart(20)}${"-".padStart(19)}${"-".padStart(6)}`
      );
      continue;
    }
    const onceki = s[i - 1];
    const hamOran = a.ham / onceki.ham;
    const yuvOran = a.yuvarlanmis / onceki.yuvarlanmis;
    console.log(
      `${String(a.n).padStart(4)} ${a.ham.toFixed(3).padStart(10)} ${String(a.yuvarlanmis).padStart(12)}` +
        `${hamOran.toFixed(4).padStart(20)}${yuvOran.toFixed(4).padStart(19)}` +
        `${(yuvOran >= ESIK ? "gecti" : "KALDI").padStart(8)}`
    );
  }
}
```

```
--- oran 1.125 ---
adim   ham (px)  yuvarlanmis  komsu orani (ham)  komsu orani (yuv)  esik
  -2     12.642           13                   -                  -     -
  -1     14.222           14              1.1250             1.0769   KALDI
   0     16.000           16              1.1250             1.1429   KALDI
   1     18.000           18              1.1250             1.1250   KALDI
   2     20.250           20              1.1250             1.1111   KALDI
   3     22.781           23              1.1250             1.1500   gecti
   4     25.629           26              1.1250             1.1304   KALDI

--- oran 1.200 ---
adim   ham (px)  yuvarlanmis  komsu orani (ham)  komsu orani (yuv)  esik
  -2     11.111           11                   -                  -     -
  -1     13.333           13              1.2000             1.1818   gecti
   0     16.000           16              1.2000             1.2308   gecti
   1     19.200           19              1.2000             1.1875   gecti
   2     23.040           23              1.2000             1.2105   gecti
   3     27.648           28              1.2000             1.2174   gecti
   4     33.178           33              1.2000             1.1786   gecti

--- oran 1.250 ---
adim   ham (px)  yuvarlanmis  komsu orani (ham)  komsu orani (yuv)  esik
  -2     10.240           10                   -                  -     -
  -1     12.800           13              1.2500             1.3000   gecti
   0     16.000           16              1.2500             1.2308   gecti
   1     20.000           20              1.2500             1.2500   gecti
   2     25.000           25              1.2500             1.2500   gecti
   3     31.250           31              1.2500             1.2400   gecti
   4     39.063           39              1.2500             1.2581   gecti

--- oran 1.333 ---
adim   ham (px)  yuvarlanmis  komsu orani (ham)  komsu orani (yuv)  esik
  -2      9.005            9                   -                  -     -
  -1     12.003           12              1.3330             1.3333   gecti
   0     16.000           16              1.3330             1.3333   gecti
   1     21.328           21              1.3330             1.3125   gecti
   2     28.430           28              1.3330             1.3333   gecti
   3     37.897           38              1.3330             1.3571   gecti
   4     50.517           51              1.3330             1.3421   gecti
```

Birinci ölçek başarısız. Oranı 1.125 seçmek, eşiğin (1.15) altında bir ayrım seçmek demektir;
yuvarlama bunu ne düzeltir ne de kötüleştirir, yalnızca **düzensizleştirir**. Gerçekleşen oranlar
1.0769 ile 1.1500 arasında salınıyor: bir basamak çiftinde artış yüzde 7.7, başka bir çiftte yüzde
15, yani neredeyse iki katı. Ölçeğin verdiği tek güvence — komşu oranın sabit olması —
yuvarlamayla birlikte kayboluyor.

Küçük boyutlarda salınımın büyümesi tesadüf değil. Yuvarlama hatası en çok bir yarım
pikseldir; bu hatanın orana etkisi boyutla ters orantılıdır. 13 pikselde yarım piksel
yüzde 3.8'lik bir sapma, 39 pikselde yüzde 1.3'lük bir sapmadır. Ölçeğin alt ucu, üst
ucundan üç kat daha kırılgandır.

Diğer üç ölçekte yuvarlanmış oranların hepsi eşiği geçiyor. Oran büyüdükçe salınımın
etkisi de göreli olarak küçülüyor: 1.333 oranlı ölçekte gerçekleşen oranlar 1.3125 ile
1.3571 arasında, yani en geniş oran en darının 1.034 katı.

Buradan çıkan kural şu biçimde yazılabilir: **ölçek oranı, ayrım eşiğinden belirgin biçimde
büyük seçilir.** Eşiğe eşit ya da yakın bir oran, yuvarlamanın hangi yönde çalıştığına göre
kimi basamak çiftinde eşiğin altına düşer.

#### Basamak Bütçesi

Oranı büyütmenin bir bedeli vardır. Arayüzün kullanabileceği boyut aralığı sınırlıdır: alt
ucu okunabilirlik, üst ucu ekranda kapladığı yer belirler. Oran büyüdükçe bu aralığa daha
az basamak sığar.

```js
// basamak.mjs — olcegin basamak butcesi ve duzeylerin basamaklara yerlestirilmesi

const TABAN = 16;
const EN_KUCUK = 12;   // altina inilmeyecek govde-disi metin boyutu
const EN_BUYUK = 40;   // katalog arayuzunde gereken en buyuk baslik boyutu

console.log("oran    basamak sayisi (12-40 px arasi)  ilk basamak  son basamak");
for (const oran of [1.125, 1.2, 1.25, 1.333, 1.5]) {
  const altAdim = Math.ceil(Math.log(EN_KUCUK / TABAN) / Math.log(oran));
  const ustAdim = Math.floor(Math.log(EN_BUYUK / TABAN) / Math.log(oran));
  const sayi = ustAdim - altAdim + 1;
  const ilk = Math.round(TABAN * Math.pow(oran, altAdim));
  const son = Math.round(TABAN * Math.pow(oran, ustAdim));
  console.log(
    `${oran.toFixed(3)} ${String(sayi).padStart(28)} ${String(ilk).padStart(12)} ${String(son).padStart(12)}`
  );
}

// Katalog arayuzunun duzeyleri, 1.25 oranli olcegin basamaklarina yerlestirildi.
const ORAN = 1.25;
const boyut = (n) => Math.round(TABAN * Math.pow(ORAN, n));

const duzeyler = [
  { ad: "sayfa basligi", adim: 3, agirlik: 700 },
  { ad: "kayit basligi", adim: 1, agirlik: 600 },
  { ad: "dugme metni",   adim: 0, agirlik: 600 },
  { ad: "yazar",         adim: 0, agirlik: 400 },
  { ad: "ustveri",       adim: -1, agirlik: 400 },
];

const ESIK = { boyutOrani: 1.15, agirlikFarki: 200 };

console.log("\nduzey           adim  boyut(px)  agirlik");
for (const d of duzeyler) {
  console.log(
    `${d.ad.padEnd(15)} ${String(d.adim).padStart(4)} ${String(boyut(d.adim)).padStart(9)} ${String(d.agirlik).padStart(8)}`
  );
}

console.log("\nkomsu duzey cifti               boyut orani  agirlik farki  gecen kanal");
for (let i = 0; i < duzeyler.length - 1; i++) {
  const ust = duzeyler[i];
  const alt = duzeyler[i + 1];
  const bOran = boyut(ust.adim) / boyut(alt.adim);
  const aFark = ust.agirlik - alt.agirlik;
  const gecen = [];
  if (bOran >= ESIK.boyutOrani) gecen.push("boyut");
  if (aFark >= ESIK.agirlikFarki) gecen.push("agirlik");
  const etiket = `${ust.ad} > ${alt.ad}`;
  console.log(
    `${etiket.padEnd(31)} ${bOran.toFixed(3).padStart(11)} ${String(aFark).padStart(14)}  ${gecen.length ? gecen.join("+") : "ZAYIF"}`
  );
}
```

```
oran    basamak sayisi (12-40 px arasi)  ilk basamak  son basamak
1.125                           10           13           36
1.200                            7           13           40
1.250                            6           13           39
1.333                            5           12           38
1.500                            3           16           36

duzey           adim  boyut(px)  agirlik
sayfa basligi      3        31      700
kayit basligi      1        20      600
dugme metni        0        16      600
yazar              0        16      400
ustveri           -1        13      400

komsu duzey cifti               boyut orani  agirlik farki  gecen kanal
sayfa basligi > kayit basligi         1.550            100  boyut
kayit basligi > dugme metni           1.250              0  boyut
dugme metni > yazar                   1.000            200  agirlik
yazar > ustveri                       1.231              0  boyut
```

Birinci tablo ölçek oranı seçiminin gerçek ödünleşimini gösteriyor. Aynı 12–40 piksel
aralığına 1.125 oranıyla on basamak, 1.5 oranıyla üç basamak sığıyor. Bir önceki hesap
1.125 oranının eşiği tutturamadığını göstermişti; bu tablo da 1.5 oranının yalnızca üç
düzeye yer bıraktığını gösteriyor. Katalog arayüzü beş düzey istiyor, dolayısıyla 1.5
oranı doğrudan elenir. Kalan aday 1.2, 1.25 ve 1.333; hepsi eşiği geçiyor ve hepsi beş
düzeye yetiyor.

Karar 1.25 oranıyla verildi. Gerekçe, ikinci tablodaki boşluktur: altı basamak, beş düzey
için bir basamaklık pay bırakır. Ölçeğin dolu olması, sonradan bir düzey eklenmesi
gerektiğinde ölçeğin tamamının yeniden hesaplanması demektir.

#### Her Düzey Yeni Bir Basamak İstemez

Üçüncü tablodaki düzey yerleşiminde iki nokta dikkat çekiyor.

Birincisi, düğme metni ile yazar adı **aynı basamakta** duruyor: ikisi de sıfırıncı
basamak, 16 piksel. Boyut oranı 1.000; boyut kanalı ayrım üretmiyor. Ayrım ağırlıkta
kuruluyor ve 200 birimlik fark eşiği geçiyor. Görsel Hiyerarşi dersindeki iki kanal kuralı
burada üçüncü bir kanalla karşılanıyor: düğme metni dolgulu bir yüzeyin üzerinde duruyor,
yazar adı düz zeminde. Yüzey, bu tabloda görünmeyen ama gerçekte çalışan bir kanaldır ve
Renk Sistemi dersinin konusudur.

İkincisi, üçüncü satırdaki karşılaştırma aslında hiyerarşik bir çift değil. Düğme metni ile yazar
adı aynı görsel alanda yarışmaz; biri eylem bölgesinde, diğeri kayıt satırının içindedir. Komşuluk
denetimi düzeyleri sıralı bir liste sanar; hiyerarşi ise yalnızca **aynı anda görülen** ögeler
arasında anlamlıdır. Tablo bir uyarı üretmediği için bu ayrım sorun çıkarmadı, ancak "ZAYIF" çıkan
bir satırın gerçek bir sorun olup olmadığı her zaman bu soruyla denetlenir.

Son satır ise ilk dersteki bir bulgunun kapanışıdır. Yazar ile üstveri arasındaki ayrım o
derste yalnız kontrast kanalında duruyordu ve tek bacaklı sayılmıştı. Ölçek, üstveriyi 14
yerine 13 piksele indirerek boyut oranını 1.231'e çıkardı; artık ayrım hem boyutta hem
kontrastta duruyor. Basamak kararı, tek tek boyut seçmenin çözemediği bir sorunu ölçeğin
kendi yapısıyla çözdü.

#### Ölçeğin Yazılı Biçimi

Ölçek bir hesap olarak kalırsa her kullanımda yeniden yapılır ve sapar. Yazılı biçimi,
basamakların adlandırılmış değerlere dönüştürülmesidir:

```css
:root {
  --boyut--1: 13px;
  --boyut-0: 16px;
  --boyut-1: 20px;
  --boyut-2: 25px;
  --boyut-3: 31px;
}
```

Adların basamak numarasını taşıması, "başlık" ya da "küçük" gibi anlam taşımasından daha
dayanıklıdır. Anlam taşıyan adlar arayüz değiştiğinde yalan söylemeye başlar: "başlık"
adlı değer bir gün başlık olmayan bir yerde kullanılır. Basamak numarası ise yalnızca
ölçekteki konumu bildirir ve o konum değişmez.

Bu listede ara değer yoktur ve olmamalıdır. Bir düzey için 18 piksel gerektiği hissi
doğduğunda yanıt yeni bir değişken eklemek değil, düzeyin hangi basamağa ait olduğuna karar
vermektir. Ölçeğin dışına çıkan her sayı, ölçeğin verdiği güvenceyi o noktada iptal eder.

#### Özet

- Tipografik ölçek, boyutları bir taban ve bir orandan üstel olarak türetir; sağladığı
  güvence, komşu basamak oranının sabit olmasıdır.
- Tam sayı piksele yuvarlama bu sabitliği bozar; sapmanın orana etkisi küçük boyutlarda
  büyüktür, dolayısıyla ölçeğin alt ucu en kırılgan yeridir.
- Ölçek oranı ayrım eşiğine eşit değil, ondan belirgin biçimde büyük seçilir; eşiğe yakın
  oran kimi basamak çiftinde yuvarlamayla eşiğin altına düşer.
- Oran büyüdükçe verilen boyut aralığına sığan basamak sayısı azalır; oran seçimi
  ayrım gücü ile basamak bütçesi arasındaki ödünleşimdir.
- Her düzey yeni bir basamak istemez; aynı basamaktaki iki düzey ağırlık ya da yüzey
  kanalında ayrılabilir.
- Ölçek adlandırılmış basamak değerleri olarak yazılır; ara değer eklemek, ölçeğin verdiği
  güvenceyi o noktada iptal eder.

#### Sonraki Adım

Ölçek metnin ne kadar büyük olacağını söyledi, ne kadar geniş olacağını değil. Bir kayıt
ayrıntısındaki özet paragrafı sütunun tamamına yayıldığında satırlar uzar ve göz satır
sonundan bir sonraki satırın başına dönerken hedefi kaçırmaya başlar. Sonraki ders satır
uzunluğunu karakter cinsinden hesaplar, ölçüyü sütun genişliğine bağlar ve satır aralığının
neden satır uzunluğuyla birlikte ayarlanması gerektiğini gösterir.

### Satır Uzunluğu ve Aralığı

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/satir-uzunlugu-ve-araligi

Sütun genişliğinden karakter cinsinden satır uzunluğunun hesaplanması, satır aralığının satır uzunluğuna bağlanması, dönüş açısı ölçütü ve paragraf aralığının alt sınırı.

Ölçek metnin ne kadar büyük olacağını verdi. Kayıt ayrıntısı ekranındaki özet paragrafı
ise ölçekten bir sayı alıyor — 16 piksel, sıfırıncı basamak — ve düzenin ona verdiği sütunun
tamamına yayılıyor. Sütun geniş bir ekranda 880 piksele kadar açılıyor. Bu paragraf
okunabilir mi?

Soru, önceki derste tanımlanan ayrımın ikinci yakasına düşüyor. Yazı tipi kararı verildi,
boyut kararı verildi; ikisi de seçilebilirlik tarafındaydı. Okunabilirlik ise blok
düzeyinde ölçülür ve iki büyüklüğe bağlıdır: bir satırın ne kadar uzun olduğu ve satırların
birbirinden ne kadar ayrıldığı. Bu iki büyüklük bağımsız değildir; biri değiştiğinde diğeri
de değişmek zorundadır.

#### Satır Uzunluğu Piksel Değil Karakter Ölçüsüdür

**Satır uzunluğu (line length)**, bir satıra sığan karakter sayısıdır. Piksel değil karakter
cinsinden ölçülmesinin nedeni, gözün satır sonundan bir sonraki satırın başına dönerken
kat ettiği mesafenin sözcük sayısıyla ilgili olmasıdır; aynı piksel genişliği farklı yazı
boyutlarında farklı sayıda sözcük taşır.

Karakter sayısı doğrudan ölçülemez, çünkü karakterler farklı genişliktedir. Ancak yazı
tipi her karakterin **ilerleme genişliğini (advance width)** bildirir; gerçek metin
üzerinden ortalama alınarak hesaplanabilir.

```js
// satiruzunlugu.mjs — sutun genisliginden karakter cinsinden satir uzunlugu hesabi

// Aday ailenin bildirdigi ilerleme genislikleri (em = 1000 birim).
// Turkce'ye ozgu harfler taban harflerinin genisligini alir.
const G = {
  " ": 260, a: 556, b: 574, c: 500, ç: 500, d: 574, e: 545, f: 340, g: 574,
  ğ: 574, h: 560, i: 244, ı: 244, j: 244, k: 520, l: 244, m: 856, n: 560,
  o: 570, ö: 570, p: 574, r: 366, s: 480, ş: 480, t: 358, u: 560, ü: 560,
  v: 508, y: 508, z: 470, A: 660, B: 660, C: 686, Ç: 686, D: 720, E: 610,
  G: 740, H: 730, I: 280, İ: 280, K: 660, M: 890, N: 730, O: 760, Ö: 760,
  P: 610, R: 660, S: 620, Ş: 620, T: 620, U: 720, Ü: 720, V: 660, Y: 620,
  Z: 600, ",": 260, ".": 260, ":": 260, "-": 340, "1": 560, "9": 560,
  "0": 560, "8": 560,
};
const VARSAYILAN = 560;

const ozet =
  "Bu ciltte optigin gelisimi, gozlem araclarinin degisimiyle birlikte ele aliniyor. " +
  "Yazar, isik ve golge kavramlarini once olcum sorunu olarak kuruyor; ardindan " +
  "gorme kuramlarinin bu olcumlerden nasil turedigini gosteriyor. Kaynakca genis, " +
  "dizin ayrintili ve gorseller ozgun baskilardan alinmis.";

const toplam = [...ozet].reduce((s, c) => s + (G[c] ?? VARSAYILAN), 0);
const ortalama = toplam / [...ozet].length;

const BOYUT = 16; // px
const ortalamaPx = (ortalama / 1000) * BOYUT;

console.log(`ozet metni karakter sayisi: ${[...ozet].length}`);
console.log(`ortalama ilerleme genisligi: ${ortalama.toFixed(1)} / 1000 em`);
console.log(`16 px'te ortalama karakter genisligi: ${ortalamaPx.toFixed(3)} px\n`);

const HEDEF = [45, 75];
console.log("sutun (px)  satir uzunlugu (karakter)  hedef 45-75");
for (const sutun of [320, 480, 560, 640, 720, 880, 1040]) {
  const karakter = sutun / ortalamaPx;
  const durum = karakter < HEDEF[0] ? "KISA" : karakter > HEDEF[1] ? "UZUN" : "uygun";
  console.log(`${String(sutun).padStart(10)} ${karakter.toFixed(1).padStart(26)}  ${durum}`);
}

console.log("\nhedef karakter sayisi -> gereken sutun genisligi");
for (const k of [45, 66, 75]) {
  console.log(`${String(k).padStart(3)} karakter -> ${(k * ortalamaPx).toFixed(1)} px`);
}

// Ayni sutun genisliginde boyut degistirilirse satir uzunlugu da degisir.
console.log("\nsutun 640 px sabit  ->  boyut  satir uzunlugu (karakter)");
for (const b of [13, 16, 20, 25]) {
  const gen = (ortalama / 1000) * b;
  console.log(`${String(b).padStart(24)} px ${(640 / gen).toFixed(1).padStart(24)}`);
}
```

```
ozet metni karakter sayisi: 293
ortalama ilerleme genisligi: 437.8 / 1000 em
16 px'te ortalama karakter genisligi: 7.005 px

sutun (px)  satir uzunlugu (karakter)  hedef 45-75
       320                       45.7  uygun
       480                       68.5  uygun
       560                       79.9  UZUN
       640                       91.4  UZUN
       720                      102.8  UZUN
       880                      125.6  UZUN
      1040                      148.5  UZUN

hedef karakter sayisi -> gereken sutun genisligi
 45 karakter -> 315.2 px
 66 karakter -> 462.3 px
 75 karakter -> 525.4 px

sutun 640 px sabit  ->  boyut  satir uzunlugu (karakter)
                      13 px                    112.4
                      16 px                     91.4
                      20 px                     73.1
                      25 px                     58.5
```

45–75 karakter aralığı, bu kursun benimsediği ve denetlenebilir olsun diye sayısallaştırdığı
ölçüttür; alt sınır satırın çok sık kesilmesini, üst sınır dönüşün zorlaşmasını engeller.

Çıktının ikinci bloğu asıl bulgudur. 880 piksellik sütuna 125.6 karakter sığıyor, yani üst sınırın
yaklaşık 1.7 katı. Özet paragrafı sütunun tamamına yayıldığında okunabilirlik ölçütünü tek bir
noktada değil, geniş bir ekran aralığının tamamında kaçırıyor. 560 piksel ve üzerindeki her sütun
genişliği ölçüt dışında.

Üçüncü blok çözümün ölçüsünü veriyor. Sütun kaç piksel olursa olsun, özet metninin
kaplayacağı genişlik en fazla 525 piksel olmalı. Bu, düzenin sütun genişliğinden bağımsız
bir kısıttır: metin bloğu kendi en büyük genişliğini taşır ve sütun ondan genişse metin
sütunu doldurmaz.

Dördüncü blok bir tuzağı açığa çıkarıyor. Sütun sabit tutulup boyut değiştirildiğinde satır
uzunluğu da değişiyor: 640 piksellik sütunda 13 pikselle 112.4, 25 pikselle 58.5 karakter.
Satır uzunluğu bir düzen kararı gibi görünse de, ölçekten seçilen basamağın doğrudan
sonucudur. Bir başlığın satır uzunluğu ile gövde metninin satır uzunluğu aynı sütunda bile
farklıdır ve üst sınır her düzey için ayrı denetlenir.

Kısıtın yazılı biçimi, sütuna değil metin bloğuna verilen bir en büyük genişliktir:

```css
.ozet {
  font-size: 16px;
  max-width: 525px;
}
```

Bu değerin karakter cinsinden yazılabilen bir birimi de vardır: `ch` birimi, geçerli yazı
tipindeki sıfır rakamının ilerleme genişliğini kullanır. Sıfırın genişliği ortalama karakter
genişliğine eşit olmadığı için `ch` cinsinden verilen bir sayı, hesaplanan karakter sayısına
eşit olmaz; bu ders ölçüyü ortalama üzerinden hesaplar ve sonucu piksel olarak yazar.

#### Satır Aralığı Satır Uzunluğuna Bağlıdır

**Satır aralığı (leading)**, ardışık iki satırın taban çizgileri arasındaki uzaklıktır.
M14/K02 CSS ile Görsel Sunum kursunda bu ölçü `line-height` özelliğiyle verilmişti ve
orada **satır yüksekliği** adıyla anılmıştı; birimsiz yazıldığında yazı boyutunun çarpanı
olarak çalışır. Tasarım tarafında sorulan soru ise çarpanın kaç olacağıdır.

Çarpanın iki ayrı alt sınırı vardır ve ikisi de hesaplanabilir.

Birinci alt sınır yazı tipinden gelir. Önceki derste hesaplanan çıkıntı ve iniş toplamı,
harflerin gerçekten kapladığı dikey ölçüdür. Çarpan bu değerin altına indiğinde bir satırın
inen harfleri, bir sonraki satırın çıkan harfleriyle örtüşür.

İkinci alt sınır göz hareketinden gelir. Satır sonunda göz bir sonraki satırın başına
**dönüş süpürmesi (return sweep)** yapar. Bu hareketin yatay bileşeni satır uzunluğu, dikey
bileşeni satır aralığıdır. İkisinin oranı, dönüşün hangi açıyla yapıldığını verir; açı
küçüldükçe hedef satırın komşularından ayrılması zorlaşır ve göz aynı satırı iki kez okur
ya da bir satır atlar.

```js
// satiraraligi.mjs — satir kutusu, satir arasi bosluk, donus acisi ve paragraf araligi

const BOYUT = 16;            // px
const CIKINTI_INIS = 1.051;  // aday-3'un bildirdigi cikinti+inis (em)
const ESIK_ACI = 2.0;        // bu kursun benimsedigi en kucuk donus acisi (derece)

const derece = (rad) => (rad * 180) / Math.PI;

console.log("carpan  satir kutusu   satir arasi bosluk   paragraf araligi >=");
for (const c of [1.2, 1.3, 1.4, 1.5, 1.6, 1.75]) {
  const kutu = c * BOYUT;
  const bosluk = kutu - CIKINTI_INIS * BOYUT;
  console.log(
    `${c.toFixed(2).padStart(6)} ${kutu.toFixed(2).padStart(14)} px ${bosluk.toFixed(2).padStart(17)} px ${(bosluk * 2).toFixed(2).padStart(17)} px`
  );
}

console.log("\nsatir uzunlugu (px) -> donus acisi (derece), carpana gore");
const carpanlar = [1.2, 1.4, 1.5, 1.6, 1.75];
console.log("uzunluk  " + carpanlar.map((c) => c.toFixed(2).padStart(7)).join(""));
for (const uzunluk of [315, 462, 525, 640, 880]) {
  const satir = carpanlar
    .map((c) => derece(Math.atan((c * BOYUT) / uzunluk)).toFixed(2).padStart(7))
    .join("");
  console.log(`${String(uzunluk).padStart(7)}  ${satir}`);
}

console.log("\nsatir uzunlugu -> esigi (2.0 derece) saglayan en kucuk carpan");
for (const uzunluk of [315, 462, 525, 640, 880]) {
  const acidan = (uzunluk * Math.tan((ESIK_ACI * Math.PI) / 180)) / BOYUT;
  // Satirlarin ust uste binmemesi icin carpan cikinti+inis degerinin altina inemez.
  const alt = Math.max(acidan, CIKINTI_INIS);
  const secilen = Math.ceil(alt * 20) / 20; // 0.05'lik adimlara yuvarla
  console.log(
    `${String(uzunluk).padStart(4)} px -> acidan ${acidan.toFixed(3)}  alt sinir ${alt.toFixed(3)}  secilen ${secilen.toFixed(2)}`
  );
}
```

```
carpan  satir kutusu   satir arasi bosluk   paragraf araligi >=
  1.20          19.20 px              2.38 px              4.77 px
  1.30          20.80 px              3.98 px              7.97 px
  1.40          22.40 px              5.58 px             11.17 px
  1.50          24.00 px              7.18 px             14.37 px
  1.60          25.60 px              8.78 px             17.57 px
  1.75          28.00 px             11.18 px             22.37 px

satir uzunlugu (px) -> donus acisi (derece), carpana gore
uzunluk     1.20   1.40   1.50   1.60   1.75
    315     3.49   4.07   4.36   4.65   5.08
    462     2.38   2.78   2.97   3.17   3.47
    525     2.09   2.44   2.62   2.79   3.05
    640     1.72   2.00   2.15   2.29   2.51
    880     1.25   1.46   1.56   1.67   1.82

satir uzunlugu -> esigi (2.0 derece) saglayan en kucuk carpan
 315 px -> acidan 0.688  alt sinir 1.051  secilen 1.10
 462 px -> acidan 1.008  alt sinir 1.051  secilen 1.10
 525 px -> acidan 1.146  alt sinir 1.146  secilen 1.15
 640 px -> acidan 1.397  alt sinir 1.397  secilen 1.40
 880 px -> acidan 1.921  alt sinir 1.921  secilen 1.95
```

İki derecelik eşik de bu kursun benimsediği bir ölçüttür; sayının kendisi değil, kararın bir
sayıya bağlanmış olması önemlidir.

Son blok iki alt sınırın nasıl yer değiştirdiğini gösteriyor. 315 ve 462 piksellik satırlarda
açı ölçütü sırasıyla 0.688 ve 1.008 çarpanıyla karşılanıyor; ikisi de yazı tipinin dayattığı
1.051 sınırının altında, dolayısıyla belirleyici olan yazı tipidir. 525 pikselden itibaren
açı ölçütü öne geçiyor ve 880 pikselde 1.921 çarpanı istiyor.

Bu, önceki bölümün bulgusunu ikinci bir yoldan doğruluyor. 880 piksellik satır, satır
aralığı çarpanı 1.95'e çıkarılarak "kurtarılabilir" görünüyor. Ancak 16 piksellik metinde
1.95 çarpanı 31.2 piksellik bir satır kutusu demektir; satırlar arasındaki boşluk harflerin
kendi yüksekliğine yaklaşır ve blok satır kümesi olmaktan çıkıp ayrı satırlar dizisine
dönüşür. Uzun satırın gerçek çözümü satır aralığını büyütmek değil, satırı kısaltmaktır.

İkinci tablo ters yönde bir uyarı taşıyor. 315 piksellik kısa satırda 1.75 çarpanı 5.08
derecelik bir dönüş açısı üretiyor. Açının büyük olması bir sorun değildir; ama satır
arası boşluğun 11.18 piksele çıkması, kısa satırlı bir blokta satırların birbirinden
kopmasına yol açar. Kısa satır az aralık, uzun satır çok aralık ister; bu, tek bir kuralın
iki ucudur.

#### Paragraf Aralığı Satır Arası Boşluktan Türer

İlk tablonun son sütunu, satır aralığının bir yan sonucunu veriyor. Bir paragrafın nerede
bitip diğerinin nerede başladığı, ancak paragraflar arasındaki boşluk satır arası boşluktan
belirgin biçimde büyükse anlaşılır. Ölçüt olarak iki kat alındığında, 1.5 çarpanıyla dizilmiş
bir metinde paragraf aralığının en az 14.37 piksel olması gerekiyor.

Bu, Yakınlık ve Gruplama dersinde kurulan bağıl boşluk ilkesinin tipografiye uygulanmış
biçimidir: grup içi boşluk ile gruplar arası boşluk arasındaki oran, gruplamayı belirler.
Burada grup paragraf, grup içi boşluk ise satır arası boşluktur.

Aynı gerekçe paragraf girintisi ile paragraf aralığının birlikte kullanılmamasını da
açıklar. İkisi de aynı işi yapar — paragraf sınırını ilan eder — ve ikisi birden
kullanıldığında sınır iki kez işaretlenmiş olur. İki kanalda ifade etme kuralı burada
geçerli değildir, çünkü paragraf sınırı kaybolduğunda okurun ödediği bedel bir hiyerarşi
kaybı değil, yalnızca bir duraklamadır.

#### Hizalama ve Bölünme

Satır uzunluğu sabitlendikten sonra geriye satır sonlarının nasıl kesileceği kalır.

Sola hizalı metinde sağ kenar düzensizdir ve sözcük araları sabit kalır. İki yana yaslı
metinde sağ kenar düzelir, ancak sözcük araları satır satır esner. Dar sütunlarda bu esneme
büyür ve satır içinde gözle görülür boşluk kanalları oluşur. Katalog arayüzünün özet
paragrafı 45 karakterin yakınına inebildiği için sola hizalı dizilir.

Ortalanmış metin sürekli okuma için elverişsizdir, çünkü her satırın başlangıcı farklı
noktadadır ve dönüş süpürmesinin hedefi her satırda değişir. Ortalama, tek satırlık ya da
en fazla iki satırlık metinlerde — boş durum iletisi, düğme metni — kullanılabilir; bunlar
Bileşen Durumları konusunda ele alınacaktır.

Sözcük bölme, iki yana yaslı dizgide esnemeyi azaltır; sola hizalı dizgide ise sağ kenarı
düzeltir ama sözcük bütünlüğünü bozar. Katalog arayüzünde yazar adları ve eser başlıkları
özel adlardır ve bölünmemeleri gerekir; bu yüzden bölme yalnızca özet paragrafında açılır,
başlık ve üstveri alanlarında kapalı kalır.

#### Özet

- Satır uzunluğu karakter cinsinden ölçülür; piksel genişliği aynı kalsa bile yazı boyutu
  değiştiğinde karakter sayısı değişir.
- Karakter sayısı, yazı tipinin bildirdiği ilerleme genişliklerinden gerçek metin üzerinden
  ortalama alınarak hesaplanır ve bir en büyük genişlik kısıtına çevrilir.
- Uzun satır sorunu sütuna değil metin bloğuna verilen bir genişlik sınırıyla çözülür; her
  metin düzeyi için sınır ayrı hesaplanır.
- Satır aralığı çarpanının iki alt sınırı vardır: yazı tipinin çıkıntı ve iniş toplamı ile
  dönüş açısı ölçütü. Kısa satırlarda birincisi, uzun satırlarda ikincisi belirleyicidir.
- Aralık büyütmek uzun satırı kurtarmaz; gereken çarpan büyüdükçe blok, satır kümesi
  olmaktan çıkar.
- Paragraf aralığı satır arası boşluktan belirgin biçimde büyük olmalıdır; paragraf sınırı
  girinti ya da aralıkla ilan edilir, ikisiyle birden değil.

#### Sonraki Adım

Bu üç ders metnin biçimini sabitledi: hangi aile, hangi boyut, hangi genişlik ve hangi
aralık. Metnin ve zeminin **rengi** ise hâlâ ilk dersteki koyu gri ile beyazdan ibaret.
Arayüzün renge ihtiyacı bir süsleme ihtiyacı değildir: birincil eylemi ikincil eylemden
ayırmak, bir kaydın seçili olduğunu bildirmek ve rafta bulunma durumunu göstermek için
ayrı yüzeyler gerekir. Sonraki ders renkleri tek tek seçmek yerine rol olarak tanımlar,
rolleri açıklık ekseninde bir ölçeğe oturtur ve her rolün hangi zeminle eşleşeceğini
hesapla belirler.

### Renk Sistemi

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/renk-sistemi

Rengin rol olarak tanımlanması, tek tondan açıklık ekseninde rampa üretimi, rollerin basamaklara bağlanması ve eşleşmelerin bağıl parlaklık üzerinden denetlenmesi.

Önceki üç ders metnin biçimini sabitledi. Renk tarafında ise elde yalnızca ilk dersten
kalan iki değer var: koyu gri metin ve beyaz zemin. Katalog arayüzü bununla yürümez.
Ödünç alma düğmesinin birincil eylem olduğu, seçili kaydın seçili olduğu, bir kaydın rafta
bulunmadığı — bunların hepsi ayrı yüzeyler ve ayrı metin renkleri ister.

Buradaki asıl risk renk seçmek değil, renkleri **tek tek** seçmektir. Her ihtiyaç
doğduğunda bir renk daha eklenirse arayüz kısa sürede birbirine yakın onlarca değer taşır;
hangisinin nerede kullanılacağı kimsenin bilmediği bir liste oluşur. Ölçek tipografide bu
sorunu nasıl çözdüyse, renk sistemi de aynı biçimde çözer: değerler tek tek seçilmez,
**türetilir**; ve kullanım yerleri değerle değil **rolle** adlandırılır.

#### Renk Bir Değer Değil Bir Rol Taşır

`#275ea5` bir değerdir. "Birincil eylemin zemini" bir roldür. Aradaki fark, ikincisinin
arayüz değiştiğinde de doğru kalmasıdır.

Değerle adlandırılmış bir sistemde — "mavi", "açık mavi", "koyu mavi" — üç sorun çıkar. Birincisi,
aynı değer birbiriyle ilgisiz yerlerde kullanılır ve sonradan yalnızca birinin değişmesi
gerektiğinde ayrıştırılamaz. İkincisi, ton değiştiğinde bütün adlar yalan söyler; "mavi" adlı
sabit yeşile ayarlanır. Üçüncüsü, koyu temada değerler tersine döner ve "açık mavi" adı koyu bir
rengi gösterir. Koyu tema, bu konunun yedinci dersinin konusudur ve rol adlandırmasının bedelini
orada geri öder.

Rol adlandırması üç parçadan kurulur: neyin rengi (yüzey, metin, kenarlık, eylem), hangi
düzeyde (birincil, ikincil) ve gerekiyorsa neyin üzerinde (`-uzeri` eki). Bir rolün
üzerine düşen metnin rolü ayrıca adlandırılır, çünkü o metin zeminiyle birlikte denetlenir.

#### Rampa Tek Tondan Türetilir

Bir aileyi oluşturan basamaklar tek tek seçilmez. Ton ve doygunluk sabit tutulur, yalnızca
açıklık değiştirilir. Bu yaklaşım M14/K02 CSS ile Görsel Sunum kursunda tanıtılmıştı; burada
üretilen rampanın her basamağının **bağıl parlaklık (relative luminance)** değeri de
hesaplanır, çünkü rolleri birbirine eşleştirmenin ölçütü budur.

```js
// rampa.mjs — tek tondan aciklik ekseninde renk rampasi ve rampanin bagil parlakligi

// HSL -> RGB (CSS renk gosterimi dersindeki donusumun ters yonu)
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("");

// WCAG bagil parlaklik ve kontrast orani
function kanal(v) {
  const s = v / 255;
  return s <= 0.04045 ? 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);
}

const BASAMAK = [
  { ad: "000", l: 100 }, { ad: "050", l: 97 }, { ad: "100", l: 92 },
  { ad: "200", l: 84 }, { ad: "300", l: 74 }, { ad: "400", l: 62 },
  { ad: "500", l: 50 }, { ad: "600", l: 40 }, { ad: "700", l: 31 },
  { ad: "800", l: 22 }, { ad: "900", l: 14 },
];

const AILE = {
  notr:    { ton: 214, doygunluk: 8 },
  ana:     { ton: 214, doygunluk: 62 },
  ikincil: { ton: 268, doygunluk: 46 },
};

const rampa = {};
for (const [ad, { ton, doygunluk }] of Object.entries(AILE)) {
  rampa[ad] = {};
  for (const b of BASAMAK) {
    rampa[ad][b.ad] = hslRgb(ton, doygunluk, b.l);
  }
}

const BEYAZ = [255, 255, 255];
for (const ad of Object.keys(AILE)) {
  console.log(`\n--- ${ad} (ton ${AILE[ad].ton}, doygunluk ${AILE[ad].doygunluk}%) ---`);
  console.log("basamak  aciklik  onaltilik   bagil parlaklik  beyaza karsi");
  for (const b of BASAMAK) {
    const rgb = rampa[ad][b.ad];
    console.log(
      `${b.ad.padStart(7)} ${String(b.l).padStart(7)}%  ${onaltilik(rgb)}  ` +
        `${parlaklik(rgb).toFixed(4).padStart(15)}  ${kontrast(rgb, BEYAZ).toFixed(2).padStart(11)}:1`
    );
  }
}
```

```
--- notr (ton 214, doygunluk 8%) ---
basamak  aciklik  onaltilik   bagil parlaklik  beyaza karsi
    000     100%  #ffffff           1.0000         1.00:1
    050      97%  #f7f7f8           0.9307         1.07:1
    100      92%  #e9eaec           0.8223         1.20:1
    200      84%  #d3d6d9           0.6695         1.46:1
    300      74%  #b7bcc2           0.4993         1.91:1
    400      62%  #969da6           0.3335         2.74:1
    500      50%  #757e8a           0.2054         4.11:1
    600      40%  #5e656e           0.1281         5.89:1
    700      31%  #494e55           0.0752         8.39:1
    800      22%  #34383d           0.0390        11.80:1
    900      14%  #212327           0.0167        15.74:1

--- ana (ton 214, doygunluk 62%) ---
basamak  aciklik  onaltilik   bagil parlaklik  beyaza karsi
    000     100%  #ffffff           1.0000         1.00:1
    050      97%  #f3f7fc           0.9260         1.08:1
    100      92%  #dee9f7           0.8052         1.23:1
    200      84%  #bdd3ef           0.6364         1.53:1
    300      74%  #94b7e6           0.4588         2.06:1
    400      62%  #6296da           0.2947         3.05:1
    500      50%  #3075cf           0.1786         4.59:1
    600      40%  #275ea5           0.1115         6.50:1
    700      31%  #1e4980           0.0660         9.05:1
    800      22%  #15335b           0.0328        12.68:1
    900      14%  #0e213a           0.0149        16.19:1

--- ikincil (ton 268, doygunluk 46%) ---
basamak  aciklik  onaltilik   bagil parlaklik  beyaza karsi
    000     100%  #ffffff           1.0000         1.00:1
    050      97%  #f7f4fb           0.9144         1.09:1
    100      92%  #eae1f4           0.7787         1.27:1
    200      84%  #d5c3e9           0.5906         1.64:1
    300      74%  #bb9edb           0.4013         2.33:1
    400      62%  #9b72cb           0.2331         3.71:1
    500      50%  #7c45ba           0.1209         6.15:1
    600      40%  #633795           0.0755         8.36:1
    700      31%  #4d2b73           0.0454        11.00:1
    800      22%  #361e52           0.0232        14.34:1
    900      14%  #231334           0.0107        17.30:1
```

Üç aile aynı basamak adlarını taşıyor ve aynı açıklık değerlerinden üretildi. Buna rağmen
aynı basamaktaki renklerin bağıl parlaklıkları eşit değil. Beş yüzüncü basamakta nötr
0.2054, ana 0.1786, ikincil 0.1209 parlaklık taşıyor. Aradaki fark küçük değil: ikincil
ailenin beş yüzüncü basamağı, nötr ailenin aynı basamağından yaklaşık yüzde kırk daha
karanlık.

Bunun nedeni, HSL gösterimindeki açıklık ekseninin algısal olmamasıdır. Bağıl parlaklık
üç kanalı eşit ağırlıklandırmaz; yeşile 0.7152, kırmızıya 0.2126, maviye 0.0722 katsayı
verir. Mavi ağırlıklı bir ton aynı açıklık değerinde daha karanlık bir parlaklık üretir.

Bunun pratik sonucu şudur: **basamak numarası eşitliği, kontrast eşitliği anlamına
gelmez.** Bir rolü bir basamağa bağlamak yetmez; eşleşmenin gerçekten çalıştığı ayrıca
hesaplanmalıdır.

#### Roller Basamaklara Bağlanır ve Eşleşmeler Denetlenir

Rol tablosu, arayüzün ihtiyaç duyduğu görevleri sayar ve her birine bir basamak verir.
Ardından hangi rolün hangi zemin üzerine düştüğü listelenir ve her eşleşme denetlenir.

```js
// roller.mjs — renk rollerinin rampa basamaklarina baglanmasi ve eslesme denetimi

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.04045 ? 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);
}

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 TON = { notr: [214, 8], ana: [214, 62], ikincil: [268, 46] };
const renk = (aile, basamak) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[basamak]);

// Roller: her rol bir gorevi adlandirir, bir rengi degil.
const ROL = {
  "yuzey":              ["notr", "000"],
  "yuzey-ikincil":      ["notr", "050"],
  "kenarlik":           ["notr", "300"],
  "metin-birincil":     ["notr", "900"],
  "metin-ikincil":      ["notr", "600"],
  "eylem-ana":          ["ana", "600"],
  "eylem-ana-uzeri":    ["notr", "000"],
  "vurgu-soluk":        ["ana", "100"],
  "vurgu-soluk-uzeri":  ["ana", "800"],
  "eylem-ikincil":      ["ikincil", "600"],
};

console.log("rol                  aile     basamak  onaltilik");
for (const [ad, [aile, b]] of Object.entries(ROL)) {
  console.log(`${ad.padEnd(20)} ${aile.padEnd(8)} ${b.padStart(7)}  ${onaltilik(renk(aile, b))}`);
}

// Eslesmeler: hangi rol hangi zeminin uzerine dusuyor ve hangi esik gecerli.
const ESLESME = [
  ["metin-birincil", "yuzey", 4.5],
  ["metin-ikincil", "yuzey", 4.5],
  ["metin-ikincil", "yuzey-ikincil", 4.5],
  ["kenarlik", "yuzey", 3.0],
  ["eylem-ana-uzeri", "eylem-ana", 4.5],
  ["eylem-ana", "yuzey", 3.0],
  ["vurgu-soluk-uzeri", "vurgu-soluk", 4.5],
  ["eylem-ikincil", "yuzey", 3.0],
];

console.log("\non plan             zemin            kontrast  esik  sonuc");
for (const [on, zemin, esik] of ESLESME) {
  const k = kontrast(renk(...ROL[on]), renk(...ROL[zemin]));
  console.log(
    `${on.padEnd(19)} ${zemin.padEnd(15)} ${k.toFixed(2).padStart(7)}:1 ${esik.toFixed(1).padStart(5)}  ${k >= esik ? "gecti" : "KALDI"}`
  );
}

// Iki eylem rengi yalniz tonla ayriliyorsa, gri tonlamada birbirine karisir.
const a = renk(...ROL["eylem-ana"]);
const b = renk(...ROL["eylem-ikincil"]);
console.log(
  `\neylem-ana (${onaltilik(a)}) ile eylem-ikincil (${onaltilik(b)}) arasindaki kontrast: ${kontrast(a, b).toFixed(3)}:1`
);
console.log(`bagil parlakliklar: ${parlaklik(a).toFixed(4)} ve ${parlaklik(b).toFixed(4)}`);

// Ayni ayrimi aciklik ekseninde kurmak icin kac basamak gerekiyor?
console.log("\neylem-ikincil basamagi  kontrast (eylem-ana'ya karsi)");
for (const bas of ["200", "300", "400", "500", "600", "700", "800"]) {
  const k = kontrast(a, renk("ikincil", bas));
  console.log(`${bas.padStart(22)}  ${k.toFixed(3).padStart(7)}:1  ${k >= 3.0 ? "ayrilir" : "-"}`);
}

// Kenarlik rolu esigi gecmediyse: hangi notr basamak 3.0'i ilk saglar?
console.log("\nnotr basamak  yuzeye karsi kontrast  3.0 esigi");
for (const bas of ["200", "300", "400", "500", "600"]) {
  const k = kontrast(renk("notr", bas), renk(...ROL["yuzey"]));
  console.log(`${bas.padStart(12)}  ${k.toFixed(2).padStart(21)}:1  ${k >= 3.0 ? "gecti" : "KALDI"}`);
}
```

```
rol                  aile     basamak  onaltilik
yuzey                notr         000  #ffffff
yuzey-ikincil        notr         050  #f7f7f8
kenarlik             notr         300  #b7bcc2
metin-birincil       notr         900  #212327
metin-ikincil        notr         600  #5e656e
eylem-ana            ana          600  #275ea5
eylem-ana-uzeri      notr         000  #ffffff
vurgu-soluk          ana          100  #dee9f7
vurgu-soluk-uzeri    ana          800  #15335b
eylem-ikincil        ikincil      600  #633795

on plan             zemin            kontrast  esik  sonuc
metin-birincil      yuzey             15.74:1   4.5  gecti
metin-ikincil       yuzey              5.89:1   4.5  gecti
metin-ikincil       yuzey-ikincil      5.51:1   4.5  gecti
kenarlik            yuzey              1.91:1   3.0  KALDI
eylem-ana-uzeri     eylem-ana          6.50:1   4.5  gecti
eylem-ana           yuzey              6.50:1   3.0  gecti
vurgu-soluk-uzeri   vurgu-soluk       10.33:1   4.5  gecti
eylem-ikincil       yuzey              8.36:1   3.0  gecti

eylem-ana (#275ea5) ile eylem-ikincil (#633795) arasindaki kontrast: 1.287:1
bagil parlakliklar: 0.1115 ve 0.0755

eylem-ikincil basamagi  kontrast (eylem-ana'ya karsi)
                   200    3.966:1  ayrilir
                   300    2.794:1  -
                   400    1.753:1  -
                   500    1.058:1  -
                   600    1.287:1  -
                   700    1.693:1  -
                   800    2.206:1  -

notr basamak  yuzeye karsi kontrast  3.0 esigi
         200                   1.46:1  KALDI
         300                   1.91:1  KALDI
         400                   2.74:1  KALDI
         500                   4.11:1  gecti
         600                   5.89:1  gecti
```

Denetim iki bulgu üretti. Eşiklerin gerekçesi ve hangi ölçütten geldikleri altıncı dersin
konusudur; burada önemli olan denetimin sistem kurulurken yapılmasıdır.

#### Birinci Bulgu: Kenarlık Rolü Eşiği Geçmiyor

Kenarlık rolü nötr üç yüzüncü basamağa bağlanmıştı ve yüzeye karşı 1.91:1 kontrast oranı
üretiyor. Eşik 3.0 olduğuna göre bu kenarlık, arayüzün bir bileşen sınırını ilan etmek için
kullandığı çizgiyi güvenilir biçimde göstermiyor.

Son tablo düzeltmenin ölçüsünü veriyor: nötr ailede eşiği geçen ilk basamak 500, oranı
4.11:1. Dört yüzüncü basamak 2.74 ile hâlâ altta kalıyor. Kenarlık rolü nötr 500'e taşınır.

Bu düzeltmenin bir yan etkisi var ve fark edilmesi gerekir: nötr 500 gözle bakıldığında belirgin
bir gri çizgidir; üç yüzüncü basamağın verdiği ince ayrımdan uzaktır. Kenarlığın tek işi bileşen
sınırını göstermekse ve o sınır zaten yüzey farkıyla ya da boşlukla kuruluyorsa, doğru karar
kenarlığı koyulaştırmak değil kaldırmaktır. Yakınlık ve Gruplama dersindeki kural burada geri
döner: çizgiyle kurulan bir ayrım, çoğu zaman boşlukla daha ucuza kurulur. Denetim başarısız
olduğunda seçenek iki tanedir — rolü eşiğe taşımak ya da rolü gereksiz kılmak.

#### İkinci Bulgu: İkinci Ton Bir Hiyerarşi Kanalı Değil

Ana eylem rengi ile ikincil eylem rengi aynı basamakta duruyor ve aralarındaki kontrast
oranı 1.287:1. Bu, iki rengin bağıl parlaklık ekseninde neredeyse aynı yerde olduğu
anlamına gelir. Renk ayrımı sınırlı olan bir okur ya da gri tonlamalı bir çıktı için bu iki
düğme aynı düğmedir.

Üçüncü tablo çözümün neden ton ekseninde olmadığını gösteriyor. İkincil aileyi ana eylemden
3.0 kontrastla ayırmak için 200 basamağına, yani açık bir mora inmek gerekiyor; ama o
basamak beyaz metin taşıyamaz, çünkü kendisi yüzeye 1.64:1 kontrastla oturuyor. Aradaki
basamakların hiçbiri ayrımı üretmiyor: 500 basamağında oran 1.058, yani iki renk pratikte
özdeş parlaklıkta.

Buradan çıkan kural, ilk dersteki iki kanal kuralının renk tarafındaki karşılığıdır:
**birincil ve ikincil eylem ton farkıyla ayrılmaz.** Ayrım yüzey kanalında kurulur —
birincil eylem dolgulu bir yüzey taşır, ikincil eylem yalnızca kenarlık ya da düz metin
olarak çizilir. Ton farkı bu ayrımın üzerine eklenebilir; ayrımı tek başına taşıyamaz.

İkinci tonun sistemde bir yeri vardır, ancak o yer eylem hiyerarşisi değildir: birbirinden
bağımsız kategorileri işaretlemek, bir bilgi türünü diğerinden ayırmak ya da vurgu
alanlarında zemin oluşturmak. Katalog arayüzünde ikincil aile, koleksiyon türünü gösteren
etiketlerde kullanılır; oradaki ayrım hiyerarşik değil, kategoriktir ve etiketin üzerindeki
metin ayrımı zaten taşır.

#### Sistemin Yazılı Biçimi

Rampa ve roller iki ayrı katman olarak yazılır. Birinci katman ham basamaklardır, ikinci
katman rollerdir ve roller yalnızca basamaklara başvurur:

```css
:root {
  /* basamaklar */
  --notr-000: #ffffff;
  --notr-050: #f7f7f8;
  --notr-500: #757e8a;
  --notr-600: #5e656e;
  --notr-900: #212327;
  --ana-100: #dee9f7;
  --ana-600: #275ea5;
  --ana-800: #15335b;

  /* roller */
  --yuzey: var(--notr-000);
  --yuzey-ikincil: var(--notr-050);
  --kenarlik: var(--notr-500);
  --metin-birincil: var(--notr-900);
  --metin-ikincil: var(--notr-600);
  --eylem-ana: var(--ana-600);
  --eylem-ana-uzeri: var(--notr-000);
}
```

İki katmanın ayrı tutulması, bileşenlerin yalnızca rol adlarını kullanmasını sağlar. Bir
bileşen `--notr-600` yazdığı anda sistem delinmiştir: o bileşen artık ton değişikliğinden,
tema değişikliğinden ve rol yeniden atamasından etkilenmez. Rol adlarına bağlı kalan bir
bileşen ise bu değişikliklerin hepsini ücretsiz devralır.

#### Özet

- Renk kararları değerle değil rolle adlandırılır; rol, arayüz değiştiğinde de doğru kalan
  addır ve tema geçişini mümkün kılan yapıdır.
- Bir renk ailesi tek tondan, açıklık ekseninde türetilir; basamak adları aileler arasında
  ortaktır.
- Aynı basamak numarası aynı kontrastı vermez; bağıl parlaklık kanalları eşit
  ağırlıklandırmadığı için ton değiştiğinde aynı açıklık farklı parlaklık üretir.
- Rol eşleşmeleri sistem kurulurken denetlenir; denetim başarısız olduğunda ya rol eşiğe
  taşınır ya da rolün gerekli olup olmadığı sorgulanır.
- Birincil ve ikincil eylem ton farkıyla ayrılmaz; iki ton aynı basamakta neredeyse özdeş
  parlaklık taşır ve gri tonlamada tek bir renge iner.
- Sistem iki katman olarak yazılır: ham basamaklar ve rol adları. Bileşenler yalnızca rol
  adlarına başvurur.

#### Sonraki Adım

Bu ders rengi görsel bir göreve bağladı: yüzey, metin, kenarlık, eylem. Arayüzün rengi
kullandığı ikinci bir alan daha var ve orada renk görsel bir görev değil, doğrudan bir
**anlam** taşıyor. Ödünç alma işleminin başarıyla tamamlandığı, kaydın rafta bulunmadığı,
iade tarihinin geçtiği bilgisi arayüzde renkle bildiriliyor. Sonraki ders bu anlamların
hangi renklere bağlandığını, bağlamanın neden tek başına yeterli olmadığını ve her işlevsel
rengin kendi zemin ve metin çiftini nasıl taşıması gerektiğini ele alır.

### İşlevsel Renkler

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/islevsel-renkler

Başarı, uyarı, hata ve bilgi anlamlarının renk ailelerine bağlanması, aynı basamağın ailelere göre farklı kontrast üretmesi, bildirim üçlüsünün kendi zeminine göre denetimi ve rengin tek kanal olamayacağı.

Önceki ders rengi görsel bir göreve bağladı: yüzey, metin, kenarlık, eylem. Bu roller
arayüzün nasıl göründüğünü belirler ama ne söylediğini belirlemez. Katalog arayüzünde
renk ikinci bir iş daha yapıyor. Ödünç alma işlemi tamamlandığında bir bildirim beliriyor;
kayıt rafta bulunmuyorsa bir uyarı; ISBN alanına geçersiz bir değer girildiğinde bir hata;
iade tarihinin yaklaştığını söyleyen bir bilgi satırı.

Bu dört durum, arayüzün kullanıcıya söylediği dört ayrı şeydir ve renkle işaretlenir.
Sorulacak soru şudur: bu renkler nasıl seçilir, seçildikten sonra nasıl denetlenir ve renk
bu işi tek başına yapabilir mi?

#### Dört Anlam, Dört Aile

**İşlevsel renkler (semantic colors)**, görünüşü değil durumu bildiren renklerdir. Dört
temel anlam ayrılır:

- **Başarı.** İstenen sonuç gerçekleşti. Katalog arayüzünde ödünç alma işleminin
  tamamlanması.
- **Uyarı.** İşlem sürebilir ama bir koşul dikkat gerektiriyor. Kaydın rafta değil, başka
  bir şubede bulunması.
- **Hata.** İşlem sürdürülemez, kullanıcıdan bir düzeltme bekleniyor. Geçersiz ISBN
  girdisi.
- **Bilgi.** Durumla ilgili nötr bir bildirim, eylem gerekmiyor. İade tarihinin bildirilmesi.

Bu dört anlamın renklerle eşleşmesi doğal bir ilişki değil, öğrenilmiş bir sözleşmedir.
Sözleşme evrensel de değildir; aynı renk farklı kültürlerde farklı okunur. Buradan çıkan
sonuç, sistemin tasarımını doğrudan etkiler: renk bir **ipucu** olarak kullanılabilir, tek
başına **taşıyıcı** olarak kullanılamaz. Bu dersin son iki bölümü bunun hesabını verir.

Her anlam bir renk değil, bir **aile** alır. Bunun nedeni bir anlamın arayüzde tek bir
biçimde görünmemesidir: aynı hata durumu bir bildirim kutusunun soluk zemininde, bir form
alanının kenarlığında ve bir metin satırında görünür. Üçü aynı ailenin farklı
basamaklarıdır.

#### Aynı Basamak Ailelere Göre Farklı İş Yapar

Önceki derste bulunan sonuç burada belirleyici hâle gelir: aynı açıklık değeri farklı
tonlarda farklı bağıl parlaklık üretir. Dört işlevsel aile için bu fark, hangi basamağın
metin taşıyabileceğini doğrudan değiştirir.

```js
// islevsel.mjs — islevsel renk ailelerinin ayni basamakta ayni isi yapmadiginin denetimi

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.04045 ? 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);
}

const ACIKLIK = { 100: 92, 200: 84, 300: 74, 400: 62, 500: 50, 600: 40, 700: 31, 800: 22 };
const AILE = {
  basari: [145, 54],
  uyari:  [42, 92],
  hata:   [8, 68],
  bilgi:  [214, 62],
};
const BEYAZ = [255, 255, 255];
const renk = (aile, b) => hslRgb(AILE[aile][0], AILE[aile][1], ACIKLIK[b]);

console.log("aile    " + Object.keys(ACIKLIK).map((b) => b.padStart(8)).join(""));
for (const aile of Object.keys(AILE)) {
  const satir = Object.keys(ACIKLIK)
    .map((b) => kontrast(renk(aile, b), BEYAZ).toFixed(2).padStart(8))
    .join("");
  console.log(`${aile.padEnd(7)} ${satir}`);
}

console.log("\naile    metin basamagi (>=4.5:1)  dolgu basamagi (>=3.0:1)");
for (const aile of Object.keys(AILE)) {
  const bas = Object.keys(ACIKLIK);
  const metin = bas.find((b) => kontrast(renk(aile, b), BEYAZ) >= 4.5);
  const dolgu = bas.find((b) => kontrast(renk(aile, b), BEYAZ) >= 3.0);
  console.log(
    `${aile.padEnd(7)} ${(metin + " (" + onaltilik(renk(aile, metin)) + ")").padStart(24)}` +
      ` ${(dolgu + " (" + onaltilik(renk(aile, dolgu)) + ")").padStart(24)}`
  );
}
```

```
aile         100     200     300     400     500     600     700     800
basari      1.13    1.30    1.52    1.86    2.25    3.44    5.31    8.67
uyari       1.11    1.24    1.41    1.65    1.92    2.96    4.68    7.78
hata        1.26    1.59    2.17    3.20    4.54    6.51    9.07   12.72
bilgi       1.23    1.53    2.06    3.05    4.59    6.50    9.05   12.68

aile    metin basamagi (>=4.5:1)  dolgu basamagi (>=3.0:1)
basari             700 (#247a48)            600 (#2f9d5d)
uyari              700 (#986c06)            700 (#986c06)
hata               500 (#d64029)            400 (#e06e5c)
bilgi              500 (#3075cf)            400 (#6296da)
```

Tablo, işlevsel renklerde en sık yapılan kurulum hatasını ölçüyor. "Her ailede 500
basamağı metin rengi olsun" biçiminde bir kural konursa hata ve bilgi ailelerinde eşik
tutturulur, başarı ve uyarı ailelerinde tutturulmaz: başarı 500 basamağında 2.25, uyarı
1.92 kontrast üretiyor. Aradaki fark iki katın üzerinde.

Sarıya yakın tonlar en uç örnek. Uyarı ailesinde dolgu için gereken 3.0 eşiği hiçbir orta
basamakta karşılanmıyor; ilk geçen basamak 700, yani metin basamağıyla aynı. Bu ailede
dolgu ile metin arasında bir basamak farkı yaratmak, ailenin daha koyu ucuna inmeyi
gerektirir.

Bunun nedeni bağıl parlaklık formülünün kanal katsayılarıdır. Yeşil kanalın katsayısı
0.7152, kırmızının 0.2126, mavinin 0.0722. Sarı ton hem kırmızı hem yeşil kanalı yüksek
tuttuğu için parlaklığı hızla yükselir; mavi ağırlıklı bir ton ise aynı açıklık değerinde
çok daha karanlık kalır. **Basamak numarası bir kontrast sözü vermez; her aile kendi
eşiğini kendi hesabıyla bulur.**

#### Bildirim Üçlüsü Kendi Zeminine Karşı Denetlenir

Bir bildirim kutusu üç renk taşır: soluk bir zemin, bir kenarlık ve zemin üzerine düşen
metin. Önceki bölümde bulunan metin basamakları beyaza karşı hesaplanmıştı; bildirim
kutusunun zemini ise beyaz değil, ailenin 100 basamağıdır.

```js
// bildirim.mjs — islevsel renklerin bildirim uclusu ve ton kanali olmadan ayrilabilirligi

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.04045 ? 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);
}

const ACIKLIK = { 100: 92, 200: 84, 300: 74, 400: 62, 500: 50, 600: 40, 700: 31, 800: 22, 900: 14 };
const AILE = { basari: [145, 54], uyari: [42, 92], hata: [8, 68], bilgi: [214, 62] };
const renk = (aile, b) => hslRgb(AILE[aile][0], AILE[aile][1], ACIKLIK[b]);
const YUZEY = [255, 255, 255];

// Bildirim uclusu: soluk zemin (100), kenarlik (?) ve metin (aileye gore secilen basamak)
const METIN_BASAMAK = { basari: 700, uyari: 700, hata: 500, bilgi: 500 };

console.log("aile    zemin      metin      metin/zemin  esik 4.5  kenarlik  kenarlik/yuzey  esik 3.0");
for (const aile of Object.keys(AILE)) {
  const zemin = renk(aile, 100);
  const metin = renk(aile, METIN_BASAMAK[aile]);
  const kenar = renk(aile, 200);
  const km = kontrast(metin, zemin);
  const kk = kontrast(kenar, YUZEY);
  console.log(
    `${aile.padEnd(7)} ${onaltilik(zemin)}  ${onaltilik(metin)}  ${km.toFixed(2).padStart(9)}:1  ` +
      `${(km >= 4.5 ? "gecti" : "KALDI").padStart(7)}  ${onaltilik(kenar)}  ${kk.toFixed(2).padStart(12)}:1  ${kk >= 3.0 ? "gecti" : "KALDI"}`
  );
}

// Ton kanali disarida birakildiginda dort anlam birbirinden ayrilabiliyor mu?
console.log("\nikili                 kontrast  ton kanali olmadan");
const adlar = Object.keys(AILE);
for (let i = 0; i < adlar.length; i++) {
  for (let j = i + 1; j < adlar.length; j++) {
    const a = renk(adlar[i], METIN_BASAMAK[adlar[i]]);
    const b = renk(adlar[j], METIN_BASAMAK[adlar[j]]);
    const k = kontrast(a, b);
    console.log(
      `${(adlar[i] + " - " + adlar[j]).padEnd(21)} ${k.toFixed(3).padStart(7)}:1  ${k >= 1.5 ? "ayrilir" : "AYRILMAZ"}`
    );
  }
}

// Bilgi rolu ile birincil eylem rengi ayni tonda kuruldu; birbirine karisiyorlar mi?
const eylemAna = hslRgb(214, 62, 40);
const bilgiMetin = renk("bilgi", 500);
console.log(
  `\neylem-ana (${onaltilik(eylemAna)}) ile bilgi metni (${onaltilik(bilgiMetin)}) kontrasti: ${kontrast(eylemAna, bilgiMetin).toFixed(3)}:1`
);

// Duzeltme: metin ve kenarlik basamaklari gercek zeminlerine karsi secilir.
console.log("\naile    metin basamagi (soluk zemine karsi >=4.5)  kenarlik basamagi (yuzeye karsi >=3.0)");
for (const aile of Object.keys(AILE)) {
  const zemin = renk(aile, 100);
  const bas = Object.keys(ACIKLIK).map(Number);
  const m = bas.find((b) => kontrast(renk(aile, b), zemin) >= 4.5);
  const k = bas.find((b) => kontrast(renk(aile, b), YUZEY) >= 3.0);
  console.log(
    `${aile.padEnd(7)} ${(m + " (" + onaltilik(renk(aile, m)) + ", " + kontrast(renk(aile, m), zemin).toFixed(2) + ":1)").padStart(42)}` +
      ` ${(k + " (" + onaltilik(renk(aile, k)) + ", " + kontrast(renk(aile, k), YUZEY).toFixed(2) + ":1)").padStart(37)}`
  );
}
```

```
aile    zemin      metin      metin/zemin  esik 4.5  kenarlik  kenarlik/yuzey  esik 3.0
basari  #e0f6e9  #247a48       4.69:1    gecti  #c0ecd3          1.30:1  KALDI
uyari   #fdf2d8  #986c06       4.21:1    KALDI  #fce5b1          1.24:1  KALDI
hata    #f8e0dd  #d64029       3.61:1    KALDI  #f2c2ba          1.59:1  KALDI
bilgi   #dee9f7  #3075cf       3.74:1    KALDI  #bdd3ef          1.53:1  KALDI

ikili                 kontrast  ton kanali olmadan
basari - uyari          1.134:1  AYRILMAZ
basari - hata           1.170:1  AYRILMAZ
basari - bilgi          1.157:1  AYRILMAZ
uyari - hata            1.032:1  AYRILMAZ
uyari - bilgi           1.020:1  AYRILMAZ
hata - bilgi            1.012:1  AYRILMAZ

eylem-ana (#275ea5) ile bilgi metni (#3075cf) kontrasti: 1.415:1

aile    metin basamagi (soluk zemine karsi >=4.5)  kenarlik basamagi (yuzeye karsi >=3.0)
basari                       700 (#247a48, 4.69:1)                 600 (#2f9d5d, 3.44:1)
uyari                        800 (#6c4d04, 6.99:1)                 700 (#986c06, 4.68:1)
hata                         600 (#ab3321, 5.17:1)                 400 (#e06e5c, 3.20:1)
bilgi                        600 (#275ea5, 5.29:1)                 400 (#6296da, 3.05:1)
```

İlk tablo dört bildirimin üçünde metin eşiğini kaçırıyor. Nedeni bir hesap hatası değil,
yanlış zemine karşı hesaplamaktır: basamaklar beyaza karşı seçilmişti, ama metin beyazın
üzerine değil ailenin soluk zeminine düşüyor. Soluk zemin beyazdan biraz koyu olduğu için
metin ile zemin arasındaki oran daralıyor.

Kenarlık sütunu daha keskin bir sonuç veriyor. Dört ailenin de 200 basamağı yüzeye karşı
1.24 ile 1.59 arasında kontrast üretiyor; hiçbiri 3.0 eşiğine yaklaşmıyor. Soluk bir
kenarlık, bildirim kutusunun sınırını ilan etme işini yapmıyor.

Son tablo düzeltmeyi veriyor. Metin basamakları kendi zeminlerine karşı yeniden seçildiğinde
uyarı 800'e, hata ve bilgi 600'e taşınıyor; başarı 700'de kalıyor çünkü zaten geçiyordu.
Kenarlık basamakları ise ailelere göre 400 ile 700 arasında dağılıyor — dört aile için tek
bir basamak numarası vermek yine mümkün değil.

Kural olarak yazılırsa: **her kontrast hesabı, rengin gerçekten üzerine düşeceği zeminle
yapılır.** Beyaza karşı hesaplanmış bir değer, beyaz olmayan bir zeminde geçerli değildir.

#### Renk Tek Kanal Olamaz

İkinci tablo, bu dersin başında konan sözleşme uyarısını sayıya çeviriyor. Dört işlevsel
metin renginin ikili kontrast oranları 1.012 ile 1.170 arasında. Hepsi 1'e çok yakın, yani
dört renk bağıl parlaklık ekseninde neredeyse aynı noktada duruyor.

Bunun anlamı şudur: ton kanalı devre dışı kaldığında dört anlam tek bir renge iner. Ton
kanalının devre dışı kalması istisnai bir durum değildir — renk ayrımı sınırlı olan okur,
gri tonlamalı çıktı, güneş altındaki düşük doygunluklu ekran ve yüksek kontrast kipi
bunların hepsidir.

Aynı sonuç WCAG 1.4.1 ölçütünde de yazılıdır: renk, bilgi aktarmanın tek görsel yolu
olamaz. Ölçüt bir tasarım tavsiyesi değil, bir uygunluk koşuludur.

Katalog arayüzünde bu, her işlevsel durumun renge ek olarak en az bir kanal daha taşıması
demektir:

- **Metin.** Bildirim, durumu sözcükle söyler. "Ödünç alma tamamlandı" ile "Bu kayıt rafta
  değil" arasındaki fark renkten okunmaz, metinden okunur.
- **Simge.** Her duruma ayrı bir biçim atanır ve biçim renkten bağımsız ayrılır. Simgenin
  hangi koşullarda anlam taşıdığı, Bileşen Durumları konusundaki Simge Kullanımı dersinin
  sorusudur.
- **Konum.** Alan düzeyindeki hata iletisi alanın hemen altında durur; ilgili alanla
  arasındaki boşluk, diğer alanlarla arasındaki boşluktan küçüktür.

Bu üç kanaldan en az birinin renkle birlikte bulunması, hem ölçütü karşılar hem de ilk
derste konan iki kanal kuralını sürdürür.

#### Bilgi Rolü ile Eylem Rengi Çakışıyor

Çıktının üçüncü bloğu, sistem kurulurken gözden kaçan bir çakışmayı yakalıyor. Bilgi ailesi
ile önceki derste kurulan ana aile aynı tondan üretilmişti. Bilgi metni ile birincil eylem
rengi arasındaki kontrast 1.415:1; iki renk birbirinden ayırt edilemeyecek kadar yakın.

Düzeltme tablosu durumu daha da keskinleştiriyor: bilgi metni doğru basamağa taşındığında
değeri `#275ea5` oluyor — bu, birincil eylem renginin **birebir aynısı**. Arayüzde
tıklanabilir bir eylemle tıklanamaz bir bilgi satırı aynı renkte görünüyor.

Bu, renk sisteminde iki rolün aynı tonu paylaşmasının bedelidir. Üç çözüm vardır ve
seçim arayüzün önceliğine göre yapılır.

Birincisi, bilgi ailesini başka bir tona taşımaktır; ancak bilgi anlamının sözleşmesi
mavimsi tonlara yerleşmiştir ve tondan çıkmak anlamı zayıflatır.

İkincisi, birincil eylem rengini ayırmaktır. Eylem rengi bir sözleşme taşımıyorsa — katalog
arayüzünde taşımıyor — taşınabilir olan odur.

Üçüncüsü, ayrımı renk dışında bir kanala taşımaktır. Birincil eylem dolgulu bir yüzey
üzerinde beyaz metin taşır; bilgi satırı düz zeminde renkli metindir. Yüzey kanalı ikisini
zaten ayırır ve ton çakışması görünür bir soruna dönüşmez.

Katalog arayüzünde üçüncü yol seçilir, çünkü ayrım hâlihazırda kurulmuştur ve önceki dersin
sonucuyla tutarlıdır: eylem hiyerarşisi ton farkıyla değil yüzey farkıyla kurulur. Ancak
bu seçim bir koşula bağlıdır ve koşul yazılmalıdır: bilgi rengi hiçbir yerde dolgulu bir
yüzey olarak kullanılamaz. Kullanıldığı anda çakışma görünür hâle gelir.

#### Özet

- İşlevsel renkler görünüşü değil durumu bildirir; başarı, uyarı, hata ve bilgi dört temel
  anlamdır ve her biri tek bir değer değil bir aile alır.
- Anlam ile renk arasındaki ilişki öğrenilmiş bir sözleşmedir; bu yüzden renk ipucu
  olabilir, tek taşıyıcı olamaz.
- Basamak numarası kontrast sözü vermez; sarıya yakın tonlar aynı açıklık değerinde çok
  daha yüksek parlaklık üretir ve eşiği ancak koyu uçta karşılar.
- Her kontrast hesabı rengin gerçekten üzerine düşeceği zeminle yapılır; beyaza karşı
  hesaplanmış bir metin rengi soluk bir zeminde eşiği kaçırabilir.
- Dört işlevsel renk bağıl parlaklık ekseninde birbirine çok yakındır; ton kanalı devre
  dışı kaldığında hepsi aynı renge iner, bu yüzden metin, simge ya da konum kanallarından
  en az biri renge eşlik eder.
- İki rolün aynı tondan üretilmesi çakışma üretir; çakışma ya tonu taşıyarak ya da ayrımı
  yüzey kanalına devrederek çözülür, ikincisi seçilirse koşulu yazılır.

#### Sonraki Adım

Son iki ders boyunca 4.5 ve 3.0 sayıları her hesapta göründü ama nereden geldikleri
söylenmedi. Bu eşikler kursun keyfî seçtiği sayılar değil; ölçüm yöntemi, kapsamı ve
istisnaları tanımlanmış ölçütlerdir. Sonraki ders bu eşiklerin hangi ölçütten geldiğini,
büyük metnin neden ayrı bir eşiğe tabi olduğunu, metin dışı ögelerin hangi kurala girdiğini
ve bir arayüzün tamamının bu eşiklere karşı nasıl toplu olarak denetlenebileceğini ele
alır.

### Karşıtlık ve Erişilebilirlik

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/karsitlik-ve-erisilebilirlik

Kontrast oranı formülünün tanımı ve sınırları, eşikten renge ters hesap, büyük metin sınırının süreksizliği ve katalog arayüzünün tamamının toplu denetimi.

Son iki derste 4.5 ve 3.0 sayıları her hesapta göründü. Bu sayılar kursun keyfî seçtiği
ölçütler değil; nasıl ölçüleceği, hangi ögeleri kapsadığı ve hangi durumlarda muaf tutulduğu
tanımlanmış eşiklerdir. Bu ders eşiklerin nereden geldiğini, hangi noktada tasarımı
kısıtladığını ve katalog arayüzünün tamamının bunlara karşı nasıl denetleneceğini ele alır.

Ders adındaki iki sözcük iki ayrı şeye işaret eder. **Karşıtlık**, Karşıtlık ve Vurgu dersinde
tanıtılan algısal ilkedir: bir ögeyi çevresinden ayıran fark. **Kontrast oranı** ise bu farkın
ölçülen niceliğidir. Karşıtlık bir tasarım aracıdır ve boyutta, ağırlıkta, biçimde ve konumda da
kurulabilir; kontrast oranı yalnızca iki rengin bağıl parlaklıkları arasındaki ilişkiyi verir.

#### Formül ve Sınırları

**Kontrast oranı (contrast ratio)** iki rengin bağıl parlaklıklarından hesaplanır:

$$
K = \frac{L_1 + 0.05}{L_2 + 0.05}
$$

Burada $L_1$ açık olanın, $L_2$ koyu olanın bağıl parlaklığıdır. Paydadaki 0.05 sabiti,
ekran yüzeyinden gelen ortam yansımasını temsil eder; bu sabit olmasaydı siyahın parlaklığı
sıfır olduğu için oran sonsuza giderdi.

```js
// esik.mjs — kontrast orani formulunun sinirlari, esikten renge ters hesap ve buyuk metin siniri

function kanal(v) {
  const s = v / 255;
  return s <= 0.04045 ? 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);
}
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("");

const SIYAH = [0, 0, 0];
const BEYAZ = [255, 255, 255];
console.log(`parlaklik(siyah) = ${parlaklik(SIYAH).toFixed(4)}   parlaklik(beyaz) = ${parlaklik(BEYAZ).toFixed(4)}`);
console.log(`kontrast(siyah, beyaz) = ${kontrast(SIYAH, BEYAZ).toFixed(2)}:1   (formulun ust siniri)`);
console.log(`kontrast(beyaz, beyaz) = ${kontrast(BEYAZ, BEYAZ).toFixed(2)}:1   (alt siniri)`);

// Ters hesap: beyaz zemin uzerinde bir esigi saglayan en yuksek bagil parlaklik
console.log("\nesik  izin verilen en yuksek bagil parlaklik  esdeger gri (0-255)");
function griden(l) {
  let alt = 0, ust = 255;
  for (let i = 0; i < 40; i++) {
    const orta = (alt + ust) / 2;
    if (parlaklik([orta, orta, orta]) < l) alt = orta; else ust = orta;
  }
  return (alt + ust) / 2;
}
for (const esik of [3.0, 4.5, 7.0]) {
  const enYuksek = 1.05 / esik - 0.05;
  console.log(`${esik.toFixed(1).padStart(4)}  ${enYuksek.toFixed(4).padStart(38)}  ${griden(enYuksek).toFixed(1).padStart(18)}`);
}

// Formul yalniz parlakligi gorur: farkli tonlarda ayni orani veren renkler
console.log("\nton  aciklik  onaltilik  bagil parlaklik  beyaza karsi");
const HEDEF = 1.05 / 4.5 - 0.05;
for (const ton of [8, 42, 145, 214, 268]) {
  let alt = 0, ust = 100;
  for (let i = 0; i < 40; i++) {
    const orta = (alt + ust) / 2;
    if (parlaklik(hslRgb(ton, 60, orta)) < HEDEF) alt = orta; else ust = orta;
  }
  const l = (alt + ust) / 2;
  const rgb = hslRgb(ton, 60, l);
  console.log(
    `${String(ton).padStart(3)} ${l.toFixed(2).padStart(8)}%  ${onaltilik(rgb)}  ${parlaklik(rgb).toFixed(4).padStart(15)}  ${kontrast(rgb, BEYAZ).toFixed(2).padStart(11)}:1`
  );
}

// WCAG "buyuk metin" siniri: 18pt ya da kalin 14pt
const PT_PX = 96 / 72;
console.log(`\n18pt = ${(18 * PT_PX).toFixed(2)} px, 14pt = ${(14 * PT_PX).toFixed(2)} px`);
const buyukMu = (px, agirlik) => px >= 18 * PT_PX || (agirlik >= 700 && px >= 14 * PT_PX);
console.log("px  agirlik  buyuk metin  gecerli esik");
for (const [px, ag] of [[16, 400], [18, 700], [19, 700], [20, 400], [23, 400], [24, 400], [31, 700]]) {
  const b = buyukMu(px, ag);
  console.log(`${String(px).padStart(2)} ${String(ag).padStart(8)}  ${(b ? "evet" : "hayir").padStart(11)}  ${(b ? "3.0" : "4.5").padStart(12)}`);
}
```

```
parlaklik(siyah) = 0.0000   parlaklik(beyaz) = 1.0000
kontrast(siyah, beyaz) = 21.00:1   (formulun ust siniri)
kontrast(beyaz, beyaz) = 1.00:1   (alt siniri)

esik  izin verilen en yuksek bagil parlaklik  esdeger gri (0-255)
 3.0                                  0.3000               148.9
 4.5                                  0.1833               118.7
 7.0                                  0.1000                89.0

ton  aciklik  onaltilik  bagil parlaklik  beyaza karsi
  8    51.12%  #cd4c38           0.1843         4.48:1
 42    35.91%  #927225           0.1828         4.51:1
145    33.21%  #22884c           0.1847         4.47:1
214    50.49%  #3577cd           0.1836         4.50:1
268    59.44%  #935ad6           0.1837         4.49:1

18pt = 24.00 px, 14pt = 18.67 px
px  agirlik  buyuk metin  gecerli esik
16      400        hayir           4.5
18      700        hayir           4.5
19      700         evet           3.0
20      400        hayir           4.5
23      400        hayir           4.5
24      400         evet           3.0
31      700         evet           3.0
```

Birinci blok formülün aralığını veriyor: en düşük 1:1, en yüksek 21:1. Bu aralık yüzde
değil oran ölçüsüdür; 10:1 ile 20:1 arasındaki fark, 2:1 ile 4:1 arasındaki farkla aynı
katsayıdır ama gözle bakıldığında aynı büyüklükte değildir. Oranın kendisi doğrusal bir
algı ölçüsü değildir.

İkinci blok ters hesabı veriyor ve tasarım tarafında doğrudan kullanılır. Beyaz zemin
üzerinde 4.5 eşiğini sağlayan bir metin renginin bağıl parlaklığı en fazla 0.1833 olabilir;
gri karşılığı 255 üzerinden yaklaşık 118.7. Bu, "metin rengi ne kadar açık olabilir"
sorusunun sayısal yanıtıdır ve bir rengi denemek yerine doğrudan üretmeye yarar.

Üçüncü blok formülün en önemli sınırını gösteriyor. Beş farklı ton, hepsi beyaza karşı
yaklaşık 4.5 kontrast üretiyor; ama bunu tutturdukları HSL açıklık değerleri yüzde 33.21 ile
yüzde 59.44 arasında dağılıyor. Formül tonu görmez, yalnızca bağıl parlaklığı görür. Bunun
iki sonucu var: aynı eşiği farklı tonlarda tutturmak farklı açıklık gerektirir ve iki renk
eşiği geçse bile birbirinden ayırt edilebilir olduklarını göstermez — bu, önceki dersteki
bulgunun genel biçimidir.

Dördüncü blok bir başka sınırı, bir süreksizliği gösteriyor. Ölçüt metni iki kovaya ayırır:
büyük ve büyük olmayan. Sınır 18 punto, kalın yazıda 14 puntodur; piksel karşılıkları 24 ve
18.67. Bunun sonucu, 18 piksellik kalın bir metnin 4.5 eşiğine, 19 piksellik kalın bir
metnin ise 3.0 eşiğine tabi olmasıdır. Bir piksellik boyut değişikliği gereken kontrastı
üçte bir oranında düşürüyor. Ölçüt sürekli bir işlev değil, basamaklı bir kuraldır.

#### Hangi Eşik Nereden Geliyor

Eşikler WCAG ölçütlerinde tanımlıdır. Dört ölçüt bu kursun kararlarını doğrudan bağlar:

- **1.4.1 Renk Kullanımı.** Renk; bilgi aktarmanın, bir eylemi belirtmenin, bir yanıt istemenin ya
  da görsel bir ögeyi ayırt etmenin tek görsel yolu olamaz. Önceki dersteki metin, simge ve konum
  kanalları bu ölçütün karşılığıdır. Bu ölçütün bir eşiği yoktur; bir yapı koşuludur.
- **1.4.3 Kontrast (En Az).** Metin ve metin görüntüleri için en az 4.5:1; büyük metin için
  3:1.
- **1.4.6 Kontrast (Gelişmiş).** Aynı ayrım için 7:1 ve büyük metinde 4.5:1. Bu daha yüksek
  düzeydir; katalog arayüzünün gövde metni nötr 900 basamağında 15.74:1 taşıdığı için zaten
  karşılanır, ancak ikincil metinde karşılanmaz.
- **1.4.11 Metin Dışı Kontrast.** Bir bileşeni ya da durumunu tanımlamak için gereken görsel
  bilgi ile komşu renkler arasında en az 3:1. Kenarlık, odak halkası ve simge çizgileri bu
  ölçüte girer.

İki muafiyet vardır ve ikisi de karar noktasıdır. Etkin olmayan bileşenler, yani devre dışı
bırakılmış denetimler, 1.4.3 kapsamı dışındadır. Salt süsleme amaçlı ve bilgi taşımayan
metin de kapsam dışıdır. Muafiyet bir izin belgesi değildir: bir metin okunmuyorsa ölçüt
onu muaf tutsa bile arayüz o bilgiyi vermiyordur.

#### Arayüzün Tamamını Denetlemek

Tek tek eşleşmelere bakmak yetmez, çünkü kaçırılan eşleşme fark edilmez. Denetim, arayüzde
gerçekten oluşan bütün ön plan–zemin çiftlerinin listelenmesiyle yapılır.

```js
// denetim.mjs — katalog arayuzunun tamaminin esiklere karsi toplu denetimi

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));
}
function kanal(v) {
  const s = v / 255;
  return s <= 0.04045 ? 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);
}

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 TON = { notr: [214, 8], ana: [214, 62], hata: [8, 68] };
const renk = (aile, b) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[b]);

const ROL = {
  "yuzey":            ["notr", "000"],
  "yuzey-ikincil":    ["notr", "050"],
  "kenarlik":         ["notr", "500"],
  "metin-birincil":   ["notr", "900"],
  "metin-ikincil":    ["notr", "600"],
  "metin-devredisi":  ["notr", "400"],
  "eylem-ana":        ["ana", "600"],
  "eylem-ana-uzeri":  ["notr", "000"],
  "odak-halkasi":     ["ana", "500"],
  "secili-zemin":     ["ana", "100"],
  "hata-metni":       ["hata", "600"],
};

const PT_PX = 96 / 72;
const buyukMu = (px, ag) => px >= 18 * PT_PX || (ag >= 700 && px >= 14 * PT_PX);

// tur: "metin" | "metin-disi"
const DENETIM = [
  ["sayfa basligi",        "metin-birincil",  "yuzey",         "metin", 31, 700],
  ["kayit basligi",        "metin-birincil",  "yuzey",         "metin", 20, 600],
  ["yazar adi",            "metin-ikincil",   "yuzey",         "metin", 16, 400],
  ["ustveri (raf kodu)",   "metin-ikincil",   "yuzey-ikincil", "metin", 13, 400],
  ["odunc al dugmesi",     "eylem-ana-uzeri", "eylem-ana",     "metin", 16, 600],
  ["arama alani yer tut.", "metin-devredisi", "yuzey",         "metin", 16, 400],
  ["devre disi dugme",     "metin-devredisi", "yuzey-ikincil", "metin", 16, 600],
  ["hata iletisi",         "hata-metni",      "yuzey",         "metin", 13, 400],
  ["alan kenarligi",       "kenarlik",        "yuzey",         "metin-disi", 0, 0],
  ["odak halkasi",         "odak-halkasi",    "yuzey",         "metin-disi", 0, 0],
  ["secili satir zemini",  "secili-zemin",    "yuzey",         "metin-disi", 0, 0],
  ["secili satirda yazar", "metin-ikincil",   "secili-zemin",  "metin", 16, 400],
];

console.log("oge                    on plan           zemin           tur         esik  olculen  sonuc");
let kalan = 0;
for (const [oge, on, zemin, tur, px, ag] of DENETIM) {
  const esik = tur === "metin-disi" ? 3.0 : buyukMu(px, ag) ? 3.0 : 4.5;
  const k = kontrast(renk(...ROL[on]), renk(...ROL[zemin]));
  const gecti = k >= esik;
  if (!gecti) kalan++;
  console.log(
    `${oge.padEnd(22)} ${on.padEnd(17)} ${zemin.padEnd(15)} ${tur.padEnd(11)} ${esik.toFixed(1).padStart(4)} ${k.toFixed(2).padStart(8)}  ${gecti ? "gecti" : "KALDI"}`
  );
}
console.log(`\ntoplam ${DENETIM.length} eslesme, ${kalan} tanesi esigi gecmiyor`);

// Kalanlar icin: ayni ailede esigi saglayan ilk basamak
console.log("\nkalan eslesme      simdiki basamak  gereken en acik basamak");
for (const [oge, on, zemin, tur, px, ag] of DENETIM) {
  const esik = tur === "metin-disi" ? 3.0 : buyukMu(px, ag) ? 3.0 : 4.5;
  if (kontrast(renk(...ROL[on]), renk(...ROL[zemin])) >= esik) continue;
  const [aile] = ROL[on];
  const zeminRgb = renk(...ROL[zemin]);
  const uygun = Object.keys(ACIKLIK).find((b) => kontrast(renk(aile, b), zeminRgb) >= esik);
  console.log(
    `${oge.padEnd(18)} ${(ROL[on][0] + "-" + ROL[on][1]).padEnd(16)} ${uygun ? aile + "-" + uygun + " (" + kontrast(renk(aile, uygun), zeminRgb).toFixed(2) + ":1)" : "ailede yok"}`
  );
}

// Secili satir zemini koyulastirilirsa uzerindeki metin ne olur?
console.log("\nsecili zemin  zemin/yuzey  uzerindeki metin (notr-600)  metin esigi 4.5");
for (const b of ["100", "200", "300", "400"]) {
  const zemin = renk("ana", b);
  const kz = kontrast(zemin, renk(...ROL["yuzey"]));
  const km = kontrast(renk("notr", "600"), zemin);
  console.log(
    `${("ana-" + b).padEnd(13)} ${kz.toFixed(2).padStart(11)}:1 ${km.toFixed(2).padStart(28)}:1  ${km >= 4.5 ? "gecti" : "KALDI"}`
  );
}
```

```
oge                    on plan           zemin           tur         esik  olculen  sonuc
sayfa basligi          metin-birincil    yuzey           metin        3.0    15.74  gecti
kayit basligi          metin-birincil    yuzey           metin        4.5    15.74  gecti
yazar adi              metin-ikincil     yuzey           metin        4.5     5.89  gecti
ustveri (raf kodu)     metin-ikincil     yuzey-ikincil   metin        4.5     5.51  gecti
odunc al dugmesi       eylem-ana-uzeri   eylem-ana       metin        4.5     6.50  gecti
arama alani yer tut.   metin-devredisi   yuzey           metin        4.5     2.74  KALDI
devre disi dugme       metin-devredisi   yuzey-ikincil   metin        4.5     2.56  KALDI
hata iletisi           hata-metni        yuzey           metin        4.5     6.51  gecti
alan kenarligi         kenarlik          yuzey           metin-disi   3.0     4.11  gecti
odak halkasi           odak-halkasi      yuzey           metin-disi   3.0     4.59  gecti
secili satir zemini    secili-zemin      yuzey           metin-disi   3.0     1.23  KALDI
secili satirda yazar   metin-ikincil     secili-zemin    metin        4.5     4.80  gecti

toplam 12 eslesme, 3 tanesi esigi gecmiyor

kalan eslesme      simdiki basamak  gereken en acik basamak
arama alani yer tut. notr-400         notr-600 (5.89:1)
devre disi dugme   notr-400         notr-600 (5.51:1)
secili satir zemini ana-100          ana-400 (3.05:1)

secili zemin  zemin/yuzey  uzerindeki metin (notr-600)  metin esigi 4.5
ana-100              1.23:1                         4.80:1  gecti
ana-200              1.53:1                         3.85:1  KALDI
ana-300              2.06:1                         2.86:1  KALDI
ana-400              3.05:1                         1.94:1  KALDI
```

On iki eşleşmenin üçü kalıyor ve üçünün çözümü birbirinden farklı. Denetimin değeri
başarısızlıkları bulmak değil, her birinin **hangi türden** bir başarısızlık olduğunu
göstermektir.

#### Üç Başarısızlık, Üç Farklı Karar

**Arama alanının yer tutucu metni** 2.74 kontrast oranıyla eşiği kaçırıyor. Tablo düzeltmeyi
veriyor: nötr 600 basamağı 5.89 üretir. Ancak bu düzeltme yeni bir sorun yaratır — yer tutucu
metin o basamakta gerçek girdi metninden ayırt edilemez hâle gelir ve kullanıcı alanın dolu
olduğunu sanar. Doğru karar rengi koyulaştırmak değil, yer tutucuyu etiket olarak kullanmayı
bırakmaktır. Alanın adı alanın üstünde kalıcı bir etiket olarak durur; yer tutucu ya kaldırılır ya
da yalnızca biçim örneği verir. Web'in Temelleri ve HTML kursundaki etiket–alan ilişkisi dersinde
aynı sonuca erişilebilirlik tarafından varılmıştı; burada aynı sonuca kontrast hesabından
varılıyor.

**Devre dışı düğme** 2.56 kontrast oranı taşıyor ve 1.4.3 ölçütü etkin olmayan bileşenleri
muaf tutuyor. Yani bu satır bir uygunluk ihlali değil. Buna rağmen bir karar noktasıdır:
devre dışı düğmenin üzerindeki metin okunamıyorsa, kullanıcı düğmenin ne yaptığını ve neden
kullanılamadığını bilemez. Katalog arayüzünde "Ödünç al" düğmesi kayıt rafta değilken devre
dışı kalıyor; kullanıcının okuması gereken şey tam da o düğmenin metni ve yanındaki
açıklamadır. Muafiyet, düşük kontrastı zorunlu kılmaz; yalnızca serbest bırakır. Karar,
düğmeyi devre dışı bırakmak yerine etkin tutup neden çalışmadığını söylemektir.

**Seçili satır zemini** 1.23 oranıyla kalıyor. Son tablo, koyulaştırarak düzeltmenin neden
işe yaramadığını gösteriyor: zemin ana 400 basamağına taşındığında 3.05 ile eşiği geçiyor,
ama üzerindeki ikincil metin 1.94'e düşerek çok daha ağır bir ihlal üretiyor. Zemini
koyulaştırmak bir sorunu diğeriyle takas ediyor.

Çözüm 1.4.11 ölçütünün kendi ifadesinde saklı: eşik, bir durumu tanımlamak için **gereken**
görsel bilgi için istenir. Seçili olma durumu yalnızca zemin rengiyle bildiriliyorsa zemin o
bilgiyi taşıyor demektir ve eşiğe tabidir. Duruma ikinci bir gösterge eklenirse — satırın
başında 3:1 eşiğini geçen bir dikey çubuk — zemin artık tek taşıyıcı olmaktan çıkar ve soluk
kalabilir. Önceki dersin kuralı burada eşiği karşılamanın yolu hâline geliyor: ikinci kanal
yalnızca dayanıklılık değil, uygunluk da üretir.

#### Denetimin Yeri

Bu denetimin sistem kurulurken yapılması, bileşenler yazıldıktan sonra yapılmasından
ucuzdur. Rol tablosu ile eşleşme listesi birlikte tutulduğunda denetim bir veri işleme
işine iner: girdi rol adlarıdır, çıktı geçen ve kalan eşleşmelerdir.

Eşleşme listesinin elle yazılması bir zayıflıktır — arayüze yeni bir eşleşme eklendiğinde
listeye eklenmezse denetim onu görmez. Tasarım tarafında yapılması gereken, listenin var
olması ve her yeni rol eşleşmesinin oraya yazılmasıdır.

#### Özet

- Kontrast oranı, iki bağıl parlaklığın 0.05 kaydırmalı oranıdır; aralığı 1:1 ile 21:1
  arasındadır ve doğrusal bir algı ölçüsü değildir.
- Eşikten renge ters hesap yapılabilir: beyaz zeminde 4.5 eşiği, bağıl parlaklığı en fazla
  0.1833 olan bir metin rengine izin verir.
- Formül tonu görmez; aynı oranı veren renkler çok farklı açıklık değerlerinde durur ve
  eşiği geçmeleri birbirlerinden ayırt edilebilir oldukları anlamına gelmez.
- Büyük metin sınırı basamaklı bir kuraldır; 18 ile 19 piksel arasında gereken kontrast
  4.5'ten 3.0'a düşer.
- Denetim tek tek değil, arayüzde gerçekten oluşan bütün ön plan–zemin çiftleri üzerinden
  yapılır; kaçırılan eşleşme fark edilmez.
- Her başarısızlığın çözümü rengi koyulaştırmak değildir: kimi durumda öge kaldırılır, kimi
  durumda ikinci bir gösterge eklenerek renk tek taşıyıcı olmaktan çıkarılır.

#### Sonraki Adım

Bu ders bütün eşleşmeleri tek bir zemin ailesine karşı denetledi: açık yüzeyler ve koyu
metin. Aynı arayüz koyu bir temada çalıştığında bu ilişkinin tamamı tersine döner. Ters
çevirme işlemi göründüğü kadar mekanik değildir; rampanın basamaklarını başa baş takas
etmek kontrast oranlarını korumaz ve işlevsel renkler koyu zeminde eşiği kaçırır. Sonraki
ders koyu temayı bir renk listesi olarak değil bir **dönüşüm** olarak kurar, dönüşümün
korunum koşulunu tanımlar ve dönüşümü aynı denetimden geçirir.

### Koyu Tema

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/koyu-tema

Basamak aynalamasının hiyerarşi oranını neden bozduğu, koyu temanın kontrast korunumu koşuluyla türetilmesi, doygunluğun serbest açıklığın bağımlı olması ve yükseltinin kontrast payını tüketmesi.

Önceki ders katalog arayüzünün bütün renk eşleşmelerini denetledi. Denetimin tamamı tek bir
varsayım üzerine kuruluydu: yüzeyler açık, metin koyu. Aynı arayüz koyu bir temada
çalıştığında bu ilişki tersine döner ve denetimin tamamı yeniden yapılmak zorundadır.

Koyu tema, renk sisteminin ikinci bir listesi değildir; birincisinden türetilen bir
**dönüşümdür**. Bu dersin sorusu dönüşümün kuralıdır: hangi büyüklük korunur, hangisi
serbest bırakılır ve dönüşümün doğru yapıldığı nasıl denetlenir?

#### Aynalama Neyi Korur, Neyi Bozar

En doğrudan dönüşüm, rampanın basamaklarını başa baş takas etmektir: 000 ile 900, 050 ile
800, 100 ile 700 yer değiştirir. Rol adları değişmez, yalnızca hangi basamağa baktıkları
değişir. Bu dönüşümün sonucunu ölçmek gerekir.

```js
// aynalama.mjs — basamaklarin basa bas takasiyla kurulan koyu temanin denetimi

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.04045 ? 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);
}

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 TON = { notr: [214, 8], ana: [214, 62], hata: [8, 68] };
const renk = (aile, b) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[b]);

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 ACIK_ROL = {
  "yuzey":           ["notr", "000"],
  "yuzey-ikincil":   ["notr", "050"],
  "kenarlik":        ["notr", "500"],
  "metin-birincil":  ["notr", "900"],
  "metin-ikincil":   ["notr", "600"],
  "eylem-ana":       ["ana", "600"],
  "eylem-ana-uzeri": ["notr", "000"],
  "secili-zemin":    ["ana", "100"],
  "hata-metni":      ["hata", "600"],
};
const KOYU_ROL = Object.fromEntries(
  Object.entries(ACIK_ROL).map(([ad, [aile, b]]) => [ad, [aile, AYNA[b]]])
);

console.log("rol               acik tema        koyu tema (aynalanmis)");
for (const ad of Object.keys(ACIK_ROL)) {
  const [aa, ab] = ACIK_ROL[ad];
  const [ka, kb] = KOYU_ROL[ad];
  console.log(
    `${ad.padEnd(17)} ${(aa + "-" + ab).padEnd(10)} ${onaltilik(renk(aa, ab))}  ${(ka + "-" + kb).padEnd(10)} ${onaltilik(renk(ka, kb))}`
  );
}

const ESLESME = [
  ["metin-birincil", "yuzey", 4.5],
  ["metin-ikincil", "yuzey", 4.5],
  ["metin-ikincil", "yuzey-ikincil", 4.5],
  ["kenarlik", "yuzey", 3.0],
  ["eylem-ana-uzeri", "eylem-ana", 4.5],
  ["eylem-ana", "yuzey", 3.0],
  ["hata-metni", "yuzey", 4.5],
  ["metin-ikincil", "secili-zemin", 4.5],
];

console.log("\non plan / zemin                  esik  acik tema  koyu tema  koyu sonuc");
for (const [on, zemin, esik] of ESLESME) {
  const ka = kontrast(renk(...ACIK_ROL[on]), renk(...ACIK_ROL[zemin]));
  const kk = kontrast(renk(...KOYU_ROL[on]), renk(...KOYU_ROL[zemin]));
  console.log(
    `${(on + " / " + zemin).padEnd(32)} ${esik.toFixed(1).padStart(4)} ${ka.toFixed(2).padStart(10)} ${kk.toFixed(2).padStart(10)}  ${kk >= esik ? "gecti" : "KALDI"}`
  );
}
```

```
rol               acik tema        koyu tema (aynalanmis)
yuzey             notr-000   #ffffff  notr-900   #212327
yuzey-ikincil     notr-050   #f7f7f8  notr-800   #34383d
kenarlik          notr-500   #757e8a  notr-300   #b7bcc2
metin-birincil    notr-900   #212327  notr-000   #ffffff
metin-ikincil     notr-600   #5e656e  notr-200   #d3d6d9
eylem-ana         ana-600    #275ea5  ana-200    #bdd3ef
eylem-ana-uzeri   notr-000   #ffffff  notr-900   #212327
secili-zemin      ana-100    #dee9f7  ana-700    #1e4980
hata-metni        hata-600   #ab3321  hata-200   #f2c2ba

on plan / zemin                  esik  acik tema  koyu tema  koyu sonuc
metin-birincil / yuzey            4.5      15.74      15.74  gecti
metin-ikincil / yuzey             4.5       5.89      10.78  gecti
metin-ikincil / yuzey-ikincil     4.5       5.51       8.09  gecti
kenarlik / yuzey                  3.0       4.11       8.23  gecti
eylem-ana-uzeri / eylem-ana       4.5       6.50      10.29  gecti
eylem-ana / yuzey                 3.0       6.50      10.29  gecti
hata-metni / yuzey                4.5       6.51       9.89  gecti
metin-ikincil / secili-zemin      4.5       4.80       6.20  gecti
```

Sekiz eşleşmenin hepsi eşiği geçiyor. Aynalama bir uygunluk sorunu üretmedi; üstelik çoğu
eşleşmede kontrast oranını **yükseltti**. Buradan aynalamanın doğru dönüşüm olduğu sonucu
çıkarılamaz, çünkü denetim yanlış soruyu soruyor.

Eşik denetimi her eşleşmeye tek tek bakar ve alt sınırı sorar. Hiyerarşi ise eşleşmeler
**arasındaki** ilişkiye bağlıdır. Açık temada birincil metin 15.74, ikincil metin 5.89
kontrast taşıyor; aralarındaki oran 2.67. Aynalanmış koyu temada birincil 15.74, ikincil
10.78; oran 1.46. İkinci düzey birinciye çok daha yakın duruyor.

Bu, Görsel Hiyerarşi dersinde ölçülen kontrast kanalının koyu temada zayıfladığı anlamına
gelir. Kanal ölçütü geçiyor ama ayrım gücünü kaybediyor. Aynalamanın koruduğu şey eşik
uygunluğu, bozduğu şey ise **oran yapısıdır**.

Nedeni bağıl parlaklık işlevinin doğrusal olmamasıdır. HSL açıklığındaki eşit adımlar
parlaklıkta eşit adımlar üretmez; ölçeğin koyu ucu parlaklık ekseninde sıkışıktır, açık ucu
ise seyrektir. Basamakları takas etmek, sıkışık ucu seyrek uca taşır ve aradaki bütün
mesafeler değişir.

#### Korunacak Büyüklük Kontrast Oranıdır

Doğru dönüşüm, basamak numaralarını değil **kontrast oranlarını** taşır. Her rol için açık
temada ölçülen oran hedef alınır ve koyu yüzey üzerinde aynı oranı üreten açıklık değeri
aranır.

Bu, önce bir karar gerektirir: koyu yüzey ne kadar koyu olacak? Yüzey koyulaştıkça üzerine
sığdırılabilecek kontrast payı büyür, ama pay sonsuz değildir.

```js
// korunum.mjs — koyu temanin acik temadaki kontrast oranlarini koruyacak bicimde turetilmesi

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.04045 ? 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);
}

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 TON = { notr: [214, 8], ana: [214, 62], hata: [8, 68] };
const renk = (aile, b) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[b]);

// 1) Koyu yuzey secimi: ne kadar koyu yuzey, ustunde ne kadar kontrast araligi birakir?
console.log("koyu yuzey adayi  onaltilik  bagil parlaklik  ustundeki en yuksek kontrast");
for (const [ad, rgb] of [
  ["saf siyah", [0, 0, 0]],
  ["notr-900", renk("notr", "900")],
  ["notr-800", renk("notr", "800")],
  ["notr-700", renk("notr", "700")],
]) {
  console.log(
    `${ad.padEnd(17)} ${onaltilik(rgb)}  ${parlaklik(rgb).toFixed(4).padStart(15)}  ${(1.05 / (parlaklik(rgb) + 0.05)).toFixed(2).padStart(27)}:1`
  );
}

// 2) Korunum: her rol icin acik temadaki kontrast oranini koyu yuzeyde yeniden uret.
const ACIK_YUZEY = renk("notr", "000");
const KOYU_YUZEY = renk("notr", "900");

const ROLLER = [
  { ad: "metin-birincil", aile: "notr", basamak: "900", doygunluk: 8 },
  { ad: "metin-ikincil",  aile: "notr", basamak: "600", doygunluk: 8 },
  { ad: "kenarlik",       aile: "notr", basamak: "500", doygunluk: 8 },
  { ad: "eylem-ana",      aile: "ana",  basamak: "600", doygunluk: 45 },
  { ad: "hata-metni",     aile: "hata", basamak: "600", doygunluk: 55 },
];

function aciklikAra(ton, doygunluk, zemin, hedef) {
  let alt = 0, ust = 100;
  for (let i = 0; i < 50; i++) {
    const orta = (alt + ust) / 2;
    if (kontrast(hslRgb(ton, doygunluk, orta), zemin) < hedef) alt = orta;
    else ust = orta;
  }
  return (alt + ust) / 2;
}

console.log("\nrol              acik hedef  koyu aciklik  doygunluk  koyu renk  olculen");
const koyuPalet = {};
for (const r of ROLLER) {
  const hedef = kontrast(renk(r.aile, r.basamak), ACIK_YUZEY);
  const ton = TON[r.aile][0];
  const l = aciklikAra(ton, r.doygunluk, KOYU_YUZEY, hedef);
  const rgb = hslRgb(ton, r.doygunluk, l);
  koyuPalet[r.ad] = rgb;
  console.log(
    `${r.ad.padEnd(16)} ${hedef.toFixed(2).padStart(8)}:1 ${l.toFixed(2).padStart(12)}% ${String(r.doygunluk).padStart(9)}%  ${onaltilik(rgb)}  ${kontrast(rgb, KOYU_YUZEY).toFixed(2).padStart(6)}:1`
  );
}

// 3) Hiyerarsi orani korundu mu? Birincil ve ikincil metin arasindaki fark.
const acikB = kontrast(renk("notr", "900"), ACIK_YUZEY);
const acikI = kontrast(renk("notr", "600"), ACIK_YUZEY);
const koyuB = kontrast(koyuPalet["metin-birincil"], KOYU_YUZEY);
const koyuI = kontrast(koyuPalet["metin-ikincil"], KOYU_YUZEY);
console.log(`\nacik tema: birincil ${acikB.toFixed(2)}:1, ikincil ${acikI.toFixed(2)}:1, oran ${(acikB / acikI).toFixed(3)}`);
console.log(`koyu tema: birincil ${koyuB.toFixed(2)}:1, ikincil ${koyuI.toFixed(2)}:1, oran ${(koyuB / koyuI).toFixed(3)}`);

// 4) Aynalanmis surumde ayni oran ne oluyordu?
const aynaB = kontrast(renk("notr", "000"), KOYU_YUZEY);
const aynaI = kontrast(renk("notr", "200"), KOYU_YUZEY);
console.log(`aynalama : birincil ${aynaB.toFixed(2)}:1, ikincil ${aynaI.toFixed(2)}:1, oran ${(aynaB / aynaI).toFixed(3)}`);

// 5) Doygunluk serbest, aciklik bagimli: ayni hedefi farkli doygunluklarda tutturmak
console.log("\neylem-ana icin hedef 6.50:1 -> doygunluk secimi ve cozulen aciklik");
for (const s of [62, 45, 30, 15]) {
  const l = aciklikAra(TON.ana[0], s, KOYU_YUZEY, 6.5);
  const rgb = hslRgb(TON.ana[0], s, l);
  console.log(
    `doygunluk ${String(s).padStart(2)}%  ->  aciklik ${l.toFixed(2).padStart(6)}%  ${onaltilik(rgb)}  ${kontrast(rgb, KOYU_YUZEY).toFixed(2)}:1`
  );
}

// 6) Koyu temada yukselti: ustteki yuzey daha acik olur.
console.log("\nyukselti       aciklik  onaltilik  yuzeye karsi  ustundeki en yuksek kontrast");
for (const [ad, hedefAyrim] of [["taban", 1.0], ["kart", 1.3], ["acilir katman", 1.7]]) {
  const l = hedefAyrim === 1.0 ? ACIKLIK["900"] : aciklikAra(TON.notr[0], TON.notr[1], KOYU_YUZEY, hedefAyrim);
  const rgb = hslRgb(TON.notr[0], TON.notr[1], l);
  console.log(
    `${ad.padEnd(14)} ${l.toFixed(2).padStart(7)}%  ${onaltilik(rgb)}  ${kontrast(rgb, KOYU_YUZEY).toFixed(3).padStart(12)}  ${(1.05 / (parlaklik(rgb) + 0.05)).toFixed(2).padStart(27)}:1`
  );
}
```

```
koyu yuzey adayi  onaltilik  bagil parlaklik  ustundeki en yuksek kontrast
saf siyah         #000000           0.0000                        21.00:1
notr-900          #212327           0.0167                        15.74:1
notr-800          #34383d           0.0390                        11.80:1
notr-700          #494e55           0.0752                         8.39:1

rol              acik hedef  koyu aciklik  doygunluk  koyu renk  olculen
metin-birincil      15.74:1        99.82%         8%  #ffffff   15.74:1
metin-ikincil        5.89:1        62.78%         8%  #989fa8    5.89:1
kenarlik             4.11:1        52.08%         8%  #7b848f    4.15:1
eylem-ana            6.50:1        68.22%        45%  #89a9d2    6.50:1
hata-metni           6.51:1        70.24%        55%  #dd9589    6.55:1

acik tema: birincil 15.74:1, ikincil 5.89:1, oran 2.670
koyu tema: birincil 15.74:1, ikincil 5.89:1, oran 2.672
aynalama : birincil 15.74:1, ikincil 10.78:1, oran 1.459

eylem-ana icin hedef 6.50:1 -> doygunluk secimi ve cozulen aciklik
doygunluk 62%  ->  aciklik  69.03%  #7fa9e1  6.49:1
doygunluk 45%  ->  aciklik  68.22%  #89a9d2  6.50:1
doygunluk 30%  ->  aciklik  67.38%  #93a9c5  6.54:1
doygunluk 15%  ->  aciklik  66.36%  #9ca7b6  6.46:1

yukselti       aciklik  onaltilik  yuzeye karsi  ustundeki en yuksek kontrast
taban            14.00%  #212327         1.000                        15.74:1
kart             21.60%  #33363b         1.298                        12.13:1
acilir katman    28.34%  #42484e         1.700                         9.26:1
```

#### Yüzey Seçimi Kontrast Payını Belirler

Birinci tablo, koyu temanın en temel kararını sayıya bağlıyor. Saf siyah yüzey üzerinde en
yüksek kontrast 21:1'e kadar çıkabilir; nötr 900 basamağında 15.74, nötr 700'de yalnızca
8.39.

Saf siyahın verdiği pay en geniş olanıdır, ama bedeli vardır. Saf siyah üzerine beyaz metin
21:1 üretir; bu, açık temadaki 15.74 değerinin belirgin biçimde üzerindedir ve dönüşümün
korunum koşulunu ihlal eder. Korunum isteniyorsa yüzey öyle seçilmelidir ki açık temanın en
yüksek oranı koyu temada da üretilebilsin, ama zorunlu olarak aşılmasın. Nötr 900 tam bu
noktadadır: üzerindeki en yüksek kontrast 15.74, açık temanın birincil metin oranıyla
birebir aynı.

İkinci tablo bunu doğruluyor. Birincil metin rolü için çözülen açıklık yüzde 99.82, yani
pratikte saf beyaz. Bu bir tesadüf değil, yüzey seçiminin doğrudan sonucudur: nötr 900
yüzeyde 15.74 oranını yalnızca beyaz üretebilir.

Üçüncü blok korunumun sağlandığını gösteriyor. Türetilmiş koyu temada birincil ve ikincil metin
arasındaki oran 2.672; açık temadaki 2.670 değeriyle iki ondalık basamağa kadar aynı. Aynalanmış
sürümde bu oran 1.459 idi. Hiyerarşi, dönüşümün ölçütü kontrast oranı olduğunda korunuyor, basamak
numarası olduğunda korunmuyor.

#### Doygunluk Serbest, Açıklık Bağımlıdır

Dördüncü tablo dönüşümün ikinci parametresini ele alıyor. Birincil eylem rengi için hedef
6.50:1; bu hedef dört farklı doygunlukta da tutturulabiliyor, yalnızca gereken açıklık
değişiyor: yüzde 66.36 ile yüzde 69.03 arasında.

Bunun anlamı, doygunluğun bir **serbest parametre** olmasıdır. Kontrast korunumu doygunluğu
kısıtlamaz; hangi doygunluk seçilirse seçilsin, açıklık ona göre çözülür ve hedef oran
yakalanır. Doygunluk kararı bu yüzden ayrı bir gerekçe ister.

Katalog arayüzünde koyu tema için doygunluk açık temadakinden düşük seçildi: ana ailede 62
yerine 45, hata ailesinde 68 yerine 55. Gerekçe, koyu zeminde yüksek doygunluklu ve yüksek
açıklıklı bir rengin çevresindeki koyu alanla kurduğu ilişkidir; aynı rengin açık zeminde
kapladığı algısal yer ile koyu zeminde kapladığı yer aynı değildir. Bu bir tercih kararıdır
ve dönüşümün geri kalanını etkilemez — tablo bunu gösteriyor: doygunluk düşürülse de oran
korunuyor.

Burada dikkat edilecek nokta, doygunluğun rol tablosunda değil rampa tanımında
değişmesidir. Koyu tema, rol adlarını hiç değiştirmez; yalnızca rollerin baktığı basamak
değerlerini değiştirir. Bileşenler `--eylem-ana` yazmaya devam eder ve tema geçişinden
haberdar olmaz. Dördüncü derste rol adlandırmasının bedeli burada geri ödenir.

#### Yükselti Kontrast Payını Tüketir

Beşinci tablo koyu temaya özgü bir kısıtı ölçüyor. Açık temada bir kartın taban yüzeyden
ayrılması gölgeyle yapılır. Koyu zeminde gölge görünmez, çünkü gölge zaten koyudur;
ayrım, üstteki yüzeyi **açmakla** kurulur.

Bunun bedeli hesaplanabilir. Taban yüzey nötr 900'de duruyor ve üzerinde 15.74 kontrast payı
var. Tabandan 1.3 oranıyla ayrılan bir kart yüzeyi yüzde 21.60 açıklığa çıkıyor ve üzerindeki
pay 12.13'e düşüyor. 1.7 oranıyla ayrılan bir açılır katmanda pay 9.26'ya iniyor.

Yani koyu temada her yükselti katmanı, üzerine yerleşecek metnin kullanabileceği kontrast
aralığından pay alır. Üç katmanlı bir arayüzde en üst katmandaki metin, taban katmandaki
metinle aynı oranı tutturamaz. Bu, açık temada karşılığı olmayan bir kısıttır: açık temada
yükselti beyaza doğru gider ve beyaz zaten üst sınırdır, dolayısıyla pay değişmez.

Pratik sonuç, koyu temada yükselti katmanı sayısının açık temadakinden az tutulmasıdır.
Katalog arayüzünde iki katman yeter: taban yüzey ve kayıt ayrıntısının açıldığı katman.
Üçüncü bir katman eklenirse, o katmandaki ikincil metin 5.89 oranını tutturamaz ve
hiyerarşi orada bozulur.

#### Tema Geçişinin Yazılı Biçimi

Dönüşüm, rol adlarını sabit tutup basamak atamalarını değiştirerek yazılır:

```css
:root {
  --yuzey: #ffffff;
  --metin-birincil: #212327;
  --metin-ikincil: #5e656e;
  --kenarlik: #757e8a;
  --eylem-ana: #275ea5;
}

@media (prefers-color-scheme: dark) {
  :root {
    --yuzey: #212327;
    --metin-birincil: #ffffff;
    --metin-ikincil: #989fa8;
    --kenarlik: #7b848f;
    --eylem-ana: #89a9d2;
  }
}
```

Değerler hesapla üretildiği için bu blok elle yazılmış bir liste değil, bir çıktıdır.
Sisteme yeni bir rol eklendiğinde koyu karşılığı tahmin edilmez; aynı korunum hesabı
yeniden çalıştırılır.

Tema seçimi kullanıcının işletim sistemi tercihinden okunur. Arayüzün ayrıca bir tema
düğmesi sunması gerekiyorsa, o düğmenin seçimi sistem tercihini geçersiz kılar ve seçim
saklanır; bu, tasarım kararı değil durum yönetimi kararıdır.

#### Özet

- Koyu tema ikinci bir renk listesi değil, açık temadan türetilen bir dönüşümdür.
- Basamakları başa baş takas etmek eşik uygunluğunu korur ama oran yapısını bozar; birincil
  ve ikincil metin arasındaki ayrım 2.67 katından 1.46 katına düşer.
- Doğru dönüşümün korunan büyüklüğü kontrast oranıdır; her rol için hedef oranı üreten
  açıklık değeri aranarak koyu palet türetilir.
- Koyu yüzeyin ne kadar koyu olacağı, üzerine sığdırılabilecek en yüksek kontrastı belirler;
  yüzey, açık temanın en yüksek oranını tam olarak karşılayacak biçimde seçilir.
- Doygunluk serbest parametredir; hangi doygunluk seçilirse seçilsin açıklık ona göre
  çözülür ve hedef oran yakalanır.
- Koyu temada yükselti, üstteki yüzeyi açmakla kurulur ve her katman kontrast payından pay
  alır; bu yüzden katman sayısı açık temadakinden az tutulur.

#### Sonraki Adım

Tipografi ve renk kararları bittiğinde katalog arayüzünün durağan görüntüsü tamamlanmış
olur: hangi metin hangi boyutta, hangi renk hangi rolde, iki temada da denetlenmiş.
Ancak arayüz durağan değildir. "Ödünç al" düğmesi üzerine gelindiğinde, klavyeyle
odaklanıldığında, basılı tutulduğunda ve kullanılamaz olduğunda farklı görünür. Bu
görünümlerin her biri ayrı bir tasarım kararıdır ve bugüne kadar hiçbiri tanımlanmadı.
Sonraki konu bileşenlerin durumlarını ele alır; ilk ders beş etkileşim durumunu tanımlar,
her birinin hangi kanalda ayrıldığını belirler ve odak göstergesinin neden kaldırılamaz
olduğunu ölçütle gösterir.

## Bileşen Durumları

Etkileşim durumları, yükleme ve boş durumlar, hata sunumu, simge kullanımı ve arayüz metninin tasarımın parçası olması.

### Etkileşim Durumları

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/etkilesim-durumlari

Beş etkileşim durumunun tanımı, durum değişikliğinin ölçülebilir eşiğe bağlanması, üzerine gelme ile seçili olmanın kanal çakışması ve odak göstergesinin alan ve kontrast hesabı.

Önceki konu katalog arayüzünün durağan görüntüsünü tamamladı: metin ölçekten bir basamak,
renk rol tablosundan bir değer alıyor ve iki temada da denetlenmiş durumda. Ancak arayüz
durağan değil. "Ödünç al" düğmesi imleç üzerine geldiğinde, klavyeyle odaklanıldığında,
basılı tutulduğunda ve kayıt rafta olmadığı için kullanılamaz olduğunda farklı görünür.

Bu görünümlerin her biri ayrı bir tasarım kararıdır ve hiçbiri tanımlanmadı.
Tanımlanmadıklarında kararlar bileşen yazılırken tek tek verilir; aynı arayüzdeki iki düğme
farklı biçimde tepki verir ve hangi tepkinin ne anlama geldiği öğrenilemez.

#### Beş Durum

Bir etkileşimli bileşenin taşıdığı durumlar şunlardır:

- **Varsayılan (default).** Bileşenin hiçbir etkileşim altında olmadığı görünümü. Diğer
  bütün durumlar buna göre tanımlanır.
- **Üzerine gelme (hover).** İmlecin bileşenin üzerinde olduğu durum. Bildirdiği
  bilgi şudur: burası tıklanabilir bir yer. Yalnızca imleçli girdilerde vardır.
- **Odak (focus).** Bileşenin klavye girdisini alacak öge olduğu durum. Bildirdiği bilgi
  şudur: tuşa basılırsa etkilenecek olan burasıdır.
- **Basılı (active).** Bileşenin o anda basılı tutulduğu durum. Bildirdiği bilgi, girdinin
  alındığıdır.
- **Devre dışı (disabled).** Bileşenin şu anda kullanılamayacağı durum.

Beş durumun ikisi sık karıştırılır. Üzerine gelme ile odak farklı girdi türlerinden
gelir ve farklı şeyler bildirir; birini diğerinin yerine kullanmak, klavyeyle gezen
kullanıcının arayüzde nerede olduğunu göremediği anlamına gelir. Ekran Boyutu ve Girdi Türü
dersinde gösterildiği gibi dokunmatik girdide üzerine gelme durumu hiç oluşmaz; odak ise
her girdi türünde oluşur. Bu yüzden üzerine gelme durumuna yalnızca **ek** bilgi
yüklenebilir, taşıyıcı bilgi yüklenemez.

Durumlar birleşebilir. Bir düğme aynı anda hem odaklı hem basılı olabilir; hem odaklı hem
üzerine gelinmiş olabilir. Bu yüzden durumlar birbirinin yerine geçen bir liste değil,
üst üste binen katmanlardır ve her biri **ayrı bir kanal** kullanmak zorundadır.

#### Durum Değişikliği Ölçülebilir Olmalı

Bir durum, varsayılandan ayırt edilemiyorsa yoktur. Ayrımın ölçüsü, önceki derslerde
kullanılan kontrast oranıdır; eşik ise bu kursun benimsediği 1.2 değeridir.

```js
// durumlar.mjs — etkilesim durumlarinin varsayilandan ayrilabilirligi ve metin kontrasti

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.04045 ? 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);
}

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 TON = { notr: [214, 8], ana: [214, 62] };
const renk = (aile, b) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[b]);

const AYRIM_ESIGI = 1.2;   // bu kursun benimsedigi en kucuk durum degisikligi orani
const METIN_ESIGI = 4.5;

const DOLGU_DUGME = [
  { durum: "varsayilan",    yuzey: ["ana", "600"],  metin: ["notr", "000"] },
  { durum: "uzerine gelme", yuzey: ["ana", "700"],  metin: ["notr", "000"] },
  { durum: "basili",        yuzey: ["ana", "800"],  metin: ["notr", "000"] },
  { durum: "devre disi",    yuzey: ["notr", "200"], metin: ["notr", "400"] },
];

const DUZ_DUGME = [
  { durum: "varsayilan",    yuzey: ["notr", "000"], metin: ["ana", "600"] },
  { durum: "uzerine gelme", yuzey: ["ana", "100"],  metin: ["ana", "700"] },
  { durum: "basili",        yuzey: ["ana", "200"],  metin: ["ana", "800"] },
  { durum: "devre disi",    yuzey: ["notr", "000"], metin: ["notr", "400"] },
];

function denetle(baslik, dizi) {
  console.log(`\n--- ${baslik} ---`);
  console.log("durum           yuzey    metin    metin/yuzey  yuzey ayrimi  metin ayrimi  tasiyan kanal");
  const t = dizi[0];
  for (const d of dizi) {
    const y = renk(...d.yuzey);
    const m = renk(...d.metin);
    const km = kontrast(m, y);
    const ay = kontrast(y, renk(...t.yuzey));
    const am = kontrast(m, renk(...t.metin));
    const kanallar = [];
    if (ay >= AYRIM_ESIGI) kanallar.push("yuzey");
    if (am >= AYRIM_ESIGI) kanallar.push("metin");
    console.log(
      `${d.durum.padEnd(15)} ${onaltilik(y)}  ${onaltilik(m)}  ${km.toFixed(2).padStart(9)}:1  ` +
        `${ay.toFixed(3).padStart(12)}  ${am.toFixed(3).padStart(12)}  ` +
        (d === t ? "-" : kanallar.length ? kanallar.join("+") : "AYRILMIYOR") +
        (km < METIN_ESIGI ? "  [metin esigi alti]" : "")
    );
  }
}

denetle("birincil dugme (dolgulu)", DOLGU_DUGME);
denetle("ikincil dugme (dolgusuz)", DUZ_DUGME);

// Sonuc listesi satiri: uzerine gelme ile secili olma ayni kanali paylasiyor
console.log("\n--- sonuc listesi satiri ---");
console.log("durum           yuzey    varsayilana karsi  uzerine gelmeye karsi");
const SATIR = [
  ["varsayilan", ["notr", "000"]],
  ["uzerine gelme", ["notr", "050"]],
  ["secili", ["ana", "100"]],
];
for (const [ad, bas] of SATIR) {
  const y = renk(...bas);
  console.log(
    `${ad.padEnd(15)} ${onaltilik(y)}  ${kontrast(y, renk("notr", "000")).toFixed(3).padStart(17)}  ${kontrast(y, renk("notr", "050")).toFixed(3).padStart(21)}`
  );
}

// Satir icin uzerine gelme yuzeyi hangi basamakta esigi gecer?
console.log("\nuzerine gelme adayi  varsayilana karsi  secili duruma karsi");
for (const b of ["050", "100", "200"]) {
  const y = renk("notr", b);
  console.log(
    `${("notr-" + b).padEnd(20)} ${kontrast(y, renk("notr", "000")).toFixed(3).padStart(17)}  ${kontrast(y, renk("ana", "100")).toFixed(3).padStart(20)}`
  );
}
```

```
--- birincil dugme (dolgulu) ---
durum           yuzey    metin    metin/yuzey  yuzey ayrimi  metin ayrimi  tasiyan kanal
varsayilan      #275ea5  #ffffff       6.50:1         1.000         1.000  -
uzerine gelme   #1e4980  #ffffff       9.05:1         1.393         1.000  yuzey
basili          #15335b  #ffffff      12.68:1         1.950         1.000  yuzey
devre disi      #d3d6d9  #969da6       1.88:1         4.454         2.738  yuzey+metin  [metin esigi alti]

--- ikincil dugme (dolgusuz) ---
durum           yuzey    metin    metin/yuzey  yuzey ayrimi  metin ayrimi  tasiyan kanal
varsayilan      #ffffff  #275ea5       6.50:1         1.000         1.000  -
uzerine gelme   #dee9f7  #1e4980       7.37:1         1.228         1.393  yuzey+metin
basili          #bdd3ef  #15335b       8.29:1         1.530         1.950  yuzey+metin
devre disi      #ffffff  #969da6       2.74:1         1.000         2.374  metin  [metin esigi alti]

--- sonuc listesi satiri ---
durum           yuzey    varsayilana karsi  uzerine gelmeye karsi
varsayilan      #ffffff              1.000                  1.071
uzerine gelme   #f7f7f8              1.071                  1.000
secili          #dee9f7              1.228                  1.147

uzerine gelme adayi  varsayilana karsi  secili duruma karsi
notr-050                         1.071                 1.147
notr-100                         1.204                 1.020
notr-200                         1.459                 1.189
```

Dolgulu düğmede üzerine gelme ve basılı durumları eşiği geçiyor (1.393 ve 1.950) ve ikisi de
tek kanalda, yüzeyde duruyor. Bu kabul edilebilir, çünkü ikisi de geçici durumlardır ve
bilgi taşımazlar.

Metin kontrastı her durumda korunuyor ve hatta yükseliyor: 6.50, 9.05, 12.68. Bu, yön
seçiminin sonucudur; dolgu koyulaştıkça beyaz metnin kontrastı artar. Ters yön seçilseydi
basılı durumda metin eşiği kaçırabilirdi.

Düz düğmede iki durum da iki kanalda birden ayrılıyor. Nedeni, dolgusuz düğmede yüzey
ayrımının zayıf kalmasıdır: 1.228, eşiğin hemen üstünde. Metin kanalının eşlik etmesi bu
zayıflığı karşılıyor.

Devre dışı durumu her iki düğmede de metin eşiğinin altında kalıyor. Karşıtlık ve
Erişilebilirlik dersinde görüldüğü gibi etkin olmayan bileşenler 1.4.3 kapsamı dışındadır,
yani bu bir ihlal değil. Ancak orada verilen karar burada da geçerlidir: kullanıcının
düğmenin ne yaptığını okuması gerekiyorsa, düğme devre dışı bırakılmaz.

#### Üzerine Gelme ile Seçili Olma Aynı Kanalı Paylaşamaz

Üçüncü tablo, sonuç listesi satırında iki ayrı sorunu birden gösteriyor.

Birincisi, üzerine gelme yüzeyi nötr 050'de duruyor ve varsayılana karşı yalnızca 1.071
kontrast üretiyor. Eşik 1.2, dolayısıyla bu durum ölçülebilir bir değişiklik üretmiyor.

İkincisi ve daha ağırı, seçili durumun üzerine gelme durumuna karşı 1.147 kontrast
taşıması. İki durum birbirinden ayrılmıyor: kullanıcı bir satırın seçili mi yoksa imlecin
altında mı olduğunu ayırt edemiyor.

Dördüncü tablo, sorunun yüzey kanalı içinde çözülemeyeceğini gösteriyor. Nötr 100 varsayılana
karşı eşiği geçiyor (1.204) ama seçili duruma karşı 1.020'ye düşüyor — iki durum tümüyle
birleşiyor. Nötr 200 varsayılana karşı rahat geçiyor (1.459) ama seçili duruma karşı 1.189
ile hâlâ eşiğin altında. Üç adayın hiçbiri iki koşulu birden sağlamıyor.

Bunun nedeni yapısaldır: iki durum da aynı eksende, yüzey açıklığında kodlanıyor ve iki
değer bir tarafta varsayılandan, diğer tarafta birbirinden ayrılmak zorunda. Eksen bu kadar
yükü taşımıyor.

Çözüm, iki durumu ayrı kanallara dağıtmaktır. Seçili olma kalıcı ve bilgi taşıyan bir
durumdur; satırın başına konan ve yüzeye karşı 3.0 eşiğini geçen dikey bir çubukla
işaretlenir. Üzerine gelme geçicidir; yüzey kanalında kalır ve nötr 100 basamağına
taşınarak eşiği geçer. İki durum farklı eksenlerde olduğu için aynı anda oluştuklarında da
ayırt edilebilirler. Karşıtlık ve Erişilebilirlik dersindeki seçili satır bulgusu aynı
ikinci kanalı istiyordu.

#### Odak Göstergesi Kaldırılamaz

Odak durumu diğer dördünden ayrılır, çünkü tek başına bir uygunluk koşuluna bağlıdır.
2.4.7 Odak Görünürlüğü ölçütü, klavyeyle kullanılabilen her ögenin görünür bir odak
göstergesi taşımasını ister. Gösterge kaldırılamaz; yalnızca değiştirilebilir.

Göstergenin nasıl olacağı iki ölçüye bağlıdır. Kontrast ölçüsü 1.4.11'den gelir: gösterge
komşu renklerinden en az 3:1 ile ayrılmalıdır. Alan ölçüsü 2.4.13 Odak Görünümü ölçütünden
gelir: göstergenin kapladığı alan, bileşenin 2 piksel kalınlığındaki çevresinin alanı kadar
olmalıdır.

```js
// odak.mjs — odak gostergesinin alan ve kontrast olcutlerine gore denetimi

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.04045 ? 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);
}

// Alan olcutu: gosterge alani, bilesenin 2 px kalinliginda cevresi kadar olmali.
const gereken = (w, h) => (w + 4) * (h + 4) - w * h;

// Halka alani: kalinlik k, bosluk (offset) o
function halkaAlani(w, h, k, o) {
  const disW = w + 2 * (o + k), disH = h + 2 * (o + k);
  const icW = w + 2 * o, icH = h + 2 * o;
  return disW * disH - icW * icH;
}

const BILESENLER = [
  { ad: "odunc al dugmesi", w: 132, h: 44 },
  { ad: "simge dugmesi", w: 24, h: 24 },
];
const ADAYLAR = [
  { ad: "1px, bosluksuz", k: 1, o: 0 },
  { ad: "2px, bosluksuz", k: 2, o: 0 },
  { ad: "2px, 2px bosluk", k: 2, o: 2 },
  { ad: "3px, 1px bosluk", k: 3, o: 1 },
];

for (const b of BILESENLER) {
  const g = gereken(b.w, b.h);
  console.log(`\n--- ${b.ad} (${b.w}x${b.h} px) --- gereken en kucuk alan: ${g} px2`);
  console.log("gosterge          alan (px2)  gereken alana orani  sonuc");
  for (const a of ADAYLAR) {
    const alan = halkaAlani(b.w, b.h, a.k, a.o);
    console.log(
      `${a.ad.padEnd(17)} ${String(alan).padStart(10)} ${(alan / g).toFixed(3).padStart(20)}  ${alan >= g ? "gecti" : "KALDI"}`
    );
  }
}

// Kontrast olcutu: halka hem sayfa yuzeyinden hem bilesenin dolgusundan ayrilmali.
const ACIKLIK = { "000": 100, 100: 92, 500: 50, 600: 40, 700: 31, 900: 14 };
const TON = { notr: [214, 8], ana: [214, 62] };
const renk = (aile, b) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[b]);

const YUZEY = renk("notr", "000");
const DOLGU = renk("ana", "600");
console.log("\nhalka rengi   yuzeye karsi  dolguya karsi  ikisi de >=3.0");
for (const [aile, bas] of [["ana", "500"], ["ana", "700"], ["notr", "900"], ["notr", "500"]]) {
  const h = renk(aile, bas);
  const ky = kontrast(h, YUZEY);
  const kd = kontrast(h, DOLGU);
  console.log(
    `${(aile + "-" + bas).padEnd(13)} ${ky.toFixed(2).padStart(12)}  ${kd.toFixed(2).padStart(13)}  ${ky >= 3 && kd >= 3 ? "gecti" : "KALDI"}`
  );
}

// Iki katmanli halka: ic katman yuzey renginde, dis katman koyu.
console.log("\niki katmanli halka (ic: yuzey, dis: notr-900)");
const ic = YUZEY, dis = renk("notr", "900");
console.log(`ic katman / dolgu : ${kontrast(ic, DOLGU).toFixed(2)}:1`);
console.log(`dis katman / yuzey: ${kontrast(dis, YUZEY).toFixed(2)}:1`);
console.log(`dis katman / ic   : ${kontrast(dis, ic).toFixed(2)}:1`);
console.log(`alan (132x44, ic 2px + dis 2px): ${halkaAlani(132, 44, 4, 0)} px2, gereken ${gereken(132, 44)} px2`);
```

```
--- odunc al dugmesi (132x44 px) --- gereken en kucuk alan: 720 px2
gosterge          alan (px2)  gereken alana orani  sonuc
1px, bosluksuz           356                0.494  KALDI
2px, bosluksuz           720                1.000  gecti
2px, 2px bosluk          752                1.044  gecti
3px, 1px bosluk         1116                1.550  gecti

--- simge dugmesi (24x24 px) --- gereken en kucuk alan: 208 px2
gosterge          alan (px2)  gereken alana orani  sonuc
1px, bosluksuz           100                0.481  KALDI
2px, bosluksuz           208                1.000  gecti
2px, 2px bosluk          240                1.154  gecti
3px, 1px bosluk          348                1.673  gecti

halka rengi   yuzeye karsi  dolguya karsi  ikisi de >=3.0
ana-500               4.59           1.41  KALDI
ana-700               9.05           1.39  KALDI
notr-900             15.74           2.42  KALDI
notr-500              4.11           1.58  KALDI

iki katmanli halka (ic: yuzey, dis: notr-900)
ic katman / dolgu : 6.50:1
dis katman / yuzey: 15.74:1
dis katman / ic   : 15.74:1
alan (132x44, ic 2px + dis 2px): 1472 px2, gereken 720 px2
```

Alan tablosu tek satırlık bir sonuç veriyor: bir piksellik halka ölçütün yarısını
karşılıyor. Oran her iki bileşende de yaklaşık aynı — 0.494 ve 0.481 — çünkü ölçüt zaten
bileşenin çevresine oranlıdır. İki piksellik halka ölçütü tam olarak karşılıyor; boşluk
eklendiğinde alan biraz daha büyüyor.

Kontrast tablosu daha sert bir sonuç veriyor. Dört halka rengi adayının **hiçbiri** iki
koşulu birden sağlamıyor. Nedeni, halkanın iki farklı komşusu olmasıdır: dışarıda açık
sayfa yüzeyi, içeride koyu düğme dolgusu. Yüzeyden ayrılmak için koyu olmak gerekiyor,
dolgudan ayrılmak için açık — tek bir renk ikisini birden yapamıyor.

Son blok çözümü veriyor. Halka iki katmanlı çizilir: içteki katman sayfa yüzeyi renginde,
dıştaki katman koyu. İç katman düğmenin dolgusuyla 6.50, dış katman sayfa yüzeyiyle 15.74
kontrast taşıyor; iki koşul da sağlanıyor. Toplam kalınlık 4 piksel olduğunda alan 1472
piksel kare, gereken 720'nin iki katından fazla.

Bu çözümün bir yan sonucu var: zemin ne olursa olsun katmanlardan biri mutlaka komşusundan
ayrılır. Odak göstergesini bileşen bileşen ayarlamak yerine tek kural olarak yazmayı bu
özellik mümkün kılar.

#### Durum Tablosu Bileşenin Sözleşmesidir

Bir bileşenin tasarımı, tek bir görünümle değil durum tablosuyla tamamlanır. Tablonun her
satırı bir durum, her sütunu bir kanaldır; boş bırakılan hücre, o durumun o kanalda
değişmediğini söyler.

Tablonun eksiksiz olması, tasarımın eksiksiz olması demektir. En sık atlanan satırlar odak
ve devre dışıdır; ikisi de bir etkileşim gerektirdiği için tasarım gözden geçirilirken
ekranda görünmez. Tablonun yazılı olması bu atlanmayı önler.

Tablodan çıkan ikinci sonuç, durumların **bileşene değil role** tanımlanmasıdır. Aynı durum
tanımı arama düğmesinde, filtre uygulama düğmesinde ve iade uzatma düğmesinde de geçerlidir;
rol düzeyinde tanımlandığında yeni bir düğme için karar yeniden verilmez.

#### Özet

- Etkileşimli bileşenlerin beş durumu vardır ve her biri farklı bir bilgi bildirir; üzerine
  gelme yalnızca imleçli girdide oluştuğu için taşıyıcı bilgi yüklenemez.
- Durum değişikliği ölçülebilir olmalıdır; varsayılandan ayrım bir eşiğe bağlanır ve hangi
  kanalda gerçekleştiği yazılır.
- Dolgu koyulaştırılarak kurulan durum geçişleri metin kontrastını yükseltir; ters yön
  seçildiğinde metin eşiği kaçırabilir.
- Üzerine gelme ile seçili olma aynı eksende kodlanamaz; yüzey açıklığı iki ayrımı birden
  taşıyamadığı için kalıcı durum ikinci bir kanala taşınır.
- Odak göstergesi kaldırılamaz; alan ölçütü bileşenin iki piksellik çevresi kadar alan,
  kontrast ölçütü ise komşularından 3:1 ayrım ister.
- Tek renkli bir halka iki farklı komşuyu birden karşılayamaz; iki katmanlı halka her zemin
  üzerinde çalışır ve tek kural olarak yazılabilir.

#### Sonraki Adım

Beş durumun tamamı bileşenin **hazır** olduğu varsayımına dayanıyordu. Ancak katalog
arayüzü verinin gelmesini bekliyor olabilir, arama hiçbir sonuç döndürmemiş olabilir ya da
kullanıcı henüz hiçbir şey aramamış olabilir. Bu üç durumda ekranda gösterilecek bir bileşen
yoktur ve tasarlanacak şey beklemenin ve boşluğun kendisidir. Sonraki ders yükleme
göstergesinin hangi süre eşiklerinde değiştiğini, iskelet gösteriminin ne zaman yanıltıcı
olduğunu ve boş durumun neden bir hata iletisi olmadığını ele alır.

### Yükleme ve Boş Durumlar

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/yukleme-ve-bos-durumlar

Bekleme süresinin geri bildirim sınıflarına ayrılması, gösterge gecikmesinin titremeye etkisi, iskelet gösteriminin yerleşim kayması puanıyla gerekçelendirilmesi ve boş durumun üç türü.

Önceki ders bir bileşenin beş etkileşim durumunu tanımladı. Beşi de tek bir varsayıma
dayanıyordu: bileşen ekranda vardır ve etkileşime hazırdır. Katalog arayüzünde bu varsayım
sık sık geçersizdir. Kullanıcı arama düğmesine bastı ve sonuçlar henüz gelmedi; arama
hiçbir kayıt döndürmedi; ya da kullanıcı sayfayı yeni açtı ve hiç arama yapmadı.

Bu üç durumda ekranda gösterilecek bir sonuç listesi yoktur. Tasarlanacak şey listenin
kendisi değil, listenin **yokluğudur**. Bu ders yokluğun iki biçimini ayırır: veri yolda
olduğu için oluşan yokluk ve veri olmadığı için oluşan yokluk. İkisi farklı bilgi verir ve
farklı çözüm ister.

#### Bekleme Bir Süre Aralığıdır

Yükleme durumu tek bir tasarım değildir; ne kadar sürdüğüne bağlı olarak farklı bir şey
gerektirir. Sürenin kendisi ölçülür ve karar ölçüme dayandırılır.

```js
// bekleme.mjs — olculen yanit surelerinin geri bildirim siniflarina dagitilmasi

// Yinelenebilir ornek uretimi: dogrusal esleyici uretec + log-normal donusum
function uretec(tohum) {
  let s = tohum >>> 0;
  return () => ((s = (1664525 * s + 1013904223) >>> 0) / 4294967296);
}
const rastgele = uretec(20260727);
function normal() {
  const u1 = Math.max(rastgele(), 1e-12), u2 = rastgele();
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}

// Katalog aramasinin yanit suresi (ms): medyani 220 ms, sag kuyruklu
const ornekler = Array.from({ length: 200 }, () =>
  Math.round(220 * Math.exp(0.9 * normal()))
).sort((a, b) => a - b);

const yuzdelik = (p) => ornekler[Math.min(ornekler.length - 1, Math.floor((p / 100) * ornekler.length))];
console.log(`ornek sayisi: ${ornekler.length}`);
console.log(`en kucuk ${ornekler[0]} ms   medyan ${yuzdelik(50)} ms   y90 ${yuzdelik(90)} ms   y99 ${yuzdelik(99)} ms   en buyuk ${ornekler[ornekler.length - 1]} ms`);

// Geri bildirim siniflari: her sinifin ust siniri ve gerektirdigi gosterim
const SINIFLAR = [
  { ad: "anlik",          ust: 100,    gosterim: "geri bildirim gerekmez" },
  { ad: "kesintisiz",     ust: 1000,   gosterim: "imlec/dugme durumu yeter" },
  { ad: "fark edilir",    ust: 10000,  gosterim: "yukleme gostergesi" },
  { ad: "uzun",           ust: Infinity, gosterim: "ilerleme + iptal" },
];

console.log("\nsinif        ust sinir(ms)  ornek sayisi  oran     gerekli gosterim");
for (const s of SINIFLAR) {
  const alt = SINIFLAR[SINIFLAR.indexOf(s) - 1]?.ust ?? 0;
  const n = ornekler.filter((v) => v > alt && v <= s.ust).length;
  console.log(
    `${s.ad.padEnd(12)} ${(s.ust === Infinity ? "-" : String(s.ust)).padStart(13)} ${String(n).padStart(13)}  ${(n / ornekler.length * 100).toFixed(1).padStart(5)}%   ${s.gosterim}`
  );
}

// Gosterge gecikmesi: gosterge kac istekte acilir, kacinda titreme uretir?
// Titreme olcutu: gosterge 200 ms'den kisa sure gorunur.
console.log("\ngecikme(ms)  gosterge acilan  titreyen  titreme orani (tum istekler)");
for (const gecikme of [0, 150, 300, 500]) {
  const acilan = ornekler.filter((v) => v > gecikme);
  const titreyen = acilan.filter((v) => v - gecikme < 200).length;
  console.log(
    `${String(gecikme).padStart(11)} ${String(acilan.length).padStart(16)} ${String(titreyen).padStart(9)} ${(titreyen / ornekler.length * 100).toFixed(1).padStart(28)}%`
  );
}

// En az gorunme suresi kurali: gosterge acildiysa en az 300 ms kalir.
// Bu, bazi isteklere yapay gecikme ekler; toplam maliyeti hesaplanir.
const GECIKME = 300, EN_AZ = 300;
let yapay = 0, etkilenen = 0;
for (const v of ornekler) {
  if (v <= GECIKME) continue;
  const gorunme = v - GECIKME;
  if (gorunme < EN_AZ) { yapay += EN_AZ - gorunme; etkilenen++; }
}
console.log(`\n300 ms gecikme + 300 ms en az gorunme:`);
console.log(`  yapay gecikme eklenen istek sayisi: ${etkilenen} / ${ornekler.length}`);
console.log(`  toplam eklenen sure: ${yapay} ms, istek basina ortalama: ${(yapay / ornekler.length).toFixed(1)} ms`);
```

```
ornek sayisi: 200
en kucuk 23 ms   medyan 228 ms   y90 722 ms   y99 2100 ms   en buyuk 2362 ms

sinif        ust sinir(ms)  ornek sayisi  oran     gerekli gosterim
anlik                  100            45   22.5%   geri bildirim gerekmez
kesintisiz            1000           147   73.5%   imlec/dugme durumu yeter
fark edilir          10000             8    4.0%   yukleme gostergesi
uzun                     -             0    0.0%   ilerleme + iptal

gecikme(ms)  gosterge acilan  titreyen  titreme orani (tum istekler)
          0              200        89                         44.5%
        150              126        59                         29.5%
        300               75        34                         17.0%
        500               41        20                         10.0%

300 ms gecikme + 300 ms en az gorunme:
  yapay gecikme eklenen istek sayisi: 47 / 200
  toplam eklenen sure: 7573 ms, istek basina ortalama: 37.9 ms
```

Sınıf sınırları insan algısıyla ilgili yerleşik eşiklerdir. Yüz milisaniyenin altındaki bir
yanıt anlık algılanır; bir saniyeye kadar olan yanıt akışı kesmez ama fark edilir; on
saniyeye kadar olan bekleme dikkatin dağılmasına yol açar; onun ötesi ise kullanıcının
başka bir işe geçtiği aralıktır.

Ölçüm bir tasarım kararını doğrudan veriyor: isteklerin yüzde 96'sı bir saniyenin altında
tamamlanıyor. Yani katalog aramasının **çoğunlukla** bir yükleme göstergesine ihtiyacı yok.
Gösterge her istekte açılırsa, gösterge kendisi bir gürültü kaynağı hâline gelir.

İkinci tablo bunu sayıya çeviriyor. Gösterge gecikmesiz açıldığında isteklerin yüzde 44.5'i
göstergenin 200 milisaniyeden kısa süre görünüp kaybolmasına yol açıyor; ekranda beliren ve
hemen kaybolan bir öge, bilgi vermek yerine dikkat çeker. Gecikme 300 milisaniyeye
çekildiğinde bu oran yüzde 17'ye, 500 milisaniyede yüzde 10'a iniyor.

Gecikme tek başına yetmez, çünkü gösterge açıldıktan hemen sonra da kapanabilir. İkinci
kural, gösterge bir kez açıldıysa en az bir süre görünür kalmasıdır. Üçüncü blok bunun
maliyetini veriyor: 300 milisaniyelik en az görünme kuralı 200 isteğin 47'sine yapay
gecikme ekliyor ve istek başına ortalama 37.9 milisaniye maliyet üretiyor. Bu maliyet
bilinçli ödenir: titremeyi ortadan kaldırmanın bedeli, bazı isteklerin biraz daha uzun
görünmesidir.

Dördüncü satırdaki "ilerleme + iptal" sınıfında hiç örnek yok. Bu, katalog aramasının o sınıfa
girmediği anlamına gelir; ancak arayüzün başka bir işlemi — toplu dışa aktarma gibi — o sınıfa
girerse ilerleme göstergesi ve iptal düğmesi zorunlu olur. Belirsiz bir gösterge, on saniyeyi aşan
işlemlerde kullanıcıya işlemin ilerleyip ilerlemediğini söylemez.

#### İskelet Gösterimi Yer Ayırır

Yükleme göstergesinin ikinci işi, gelecek içeriğin yerini tutmaktır. Küçük bir dönen
gösterge bu işi yapmaz: içerik geldiğinde altındaki her şey aşağı itilir ve kullanıcının
baktığı nokta kayar.

Bu kayma ölçülebilir. **Yerleşim kayması puanı (layout shift score)**, kayan ögelerin
etkilediği alanın görüntü alanına oranı ile ögelerin kat ettiği uzaklığın görüntü alanının
en büyük kenarına oranının çarpımıdır.

```js
// iskelet.mjs — iskelet gosteriminin satir sayisi ve yerlesim kaymasi puani

const GORUNTU = { genislik: 1280, yukseklik: 800 };
const BASLIK_Y = 96;      // ust baslik seridi
const SATIR_Y = 72;       // sonuc satiri yuksekligi
const ALTLIK_Y = 80;      // altlik seridi

// 1) Gorunur alana kac iskelet satiri sigar?
const alan = GORUNTU.yukseklik - BASLIK_Y;
console.log(`gorunur icerik alani: ${alan} px`);
console.log(`satir yuksekligi: ${SATIR_Y} px`);
console.log(`tam sigan satir sayisi: ${Math.floor(alan / SATIR_Y)}`);
console.log(`kismen gorunen satir: ${(alan % SATIR_Y) > 0 ? "var (" + (alan % SATIR_Y) + " px)" : "yok"}`);

// 2) Yerlesim kaymasi puani: etki kesri x uzaklik kesri
// Kararsiz oge: altlik seridi. Yukleme gorunumunden yuklenmis gorunume gecerken kayar.
function kaymaPuani(oncekiUst, sonrakiUst) {
  const g = GORUNTU.yukseklik;
  const gorunurAralik = (ust) => {
    const alt = Math.min(g, ust + ALTLIK_Y);
    return ust >= g ? [0, 0] : [Math.max(0, ust), alt];
  };
  const [o1, o2] = gorunurAralik(oncekiUst);
  const [s1, s2] = gorunurAralik(sonrakiUst);
  const birlesimUst = Math.min(o1 || Infinity, s1 || Infinity);
  const birlesimAlt = Math.max(o2, s2);
  const etkiKesri = (birlesimAlt - birlesimUst) / g;
  const uzaklik = Math.abs(sonrakiUst - oncekiUst);
  const uzaklikKesri = uzaklik / Math.max(GORUNTU.genislik, GORUNTU.yukseklik);
  return { etkiKesri, uzaklikKesri, puan: etkiKesri * uzaklikKesri, uzaklik };
}

const SATIR_SAYISI = 9; // yuklenmis listede gorunur satir sayisi
const yuklenmisAltlik = BASLIK_Y + SATIR_SAYISI * SATIR_Y;

const SENARYOLAR = [
  { ad: "donen gosterge (120 px)", yukseklik: 120 },
  { ad: "iskelet 5 satir", yukseklik: 5 * SATIR_Y },
  { ad: "iskelet 8 satir", yukseklik: 8 * SATIR_Y },
  { ad: "iskelet 9 satir", yukseklik: SATIR_SAYISI * SATIR_Y },
];

console.log("\nyukleme gorunumu          altlik once  altlik sonra  uzaklik  etki kesri  uzaklik kesri  kayma puani");
for (const s of SENARYOLAR) {
  const once = BASLIK_Y + s.yukseklik;
  const k = kaymaPuani(once, yuklenmisAltlik);
  console.log(
    `${s.ad.padEnd(25)} ${String(once).padStart(11)} ${String(yuklenmisAltlik).padStart(13)} ${String(k.uzaklik).padStart(8)} ` +
      `${k.etkiKesri.toFixed(4).padStart(11)} ${k.uzaklikKesri.toFixed(4).padStart(14)} ${k.puan.toFixed(4).padStart(12)}`
  );
}
console.log("\niyi kabul edilen ust sinir: 0.100");
```

```
gorunur icerik alani: 704 px
satir yuksekligi: 72 px
tam sigan satir sayisi: 9
kismen gorunen satir: var (56 px)

yukleme gorunumu          altlik once  altlik sonra  uzaklik  etki kesri  uzaklik kesri  kayma puani
donen gosterge (120 px)           216           744      528      0.7300         0.4125       0.3011
iskelet 5 satir                   456           744      288      0.4300         0.2250       0.0968
iskelet 8 satir                   672           744       72      0.1600         0.0563       0.0090
iskelet 9 satir                   744           744        0      0.0700         0.0000       0.0000

iyi kabul edilen ust sinir: 0.100
```

Dönen gösterge 0.3011 puan üretiyor, iyi kabul edilen üst sınırın üç katı. Nedeni,
göstergenin gerçek içeriğe göre çok kısa olması ve içerik geldiğinde altlığın 528 piksel
aşağı itilmesidir.

İskelet gösteriminin satır sayısı bu puanı doğrudan belirliyor. Beş satır 0.0968 ile sınırın hemen
altında kalıyor, sekiz satır 0.0090'a iniyor ve dokuz satırda kayma tümüyle ortadan kalkıyor.
Görünür alana tam olarak dokuz satır sığdığı için doğru sayı budur.

Buradan çıkan kural şudur: **iskelet, gelecek içeriğin sayısını değil, görünür alanın
alacağı satır sayısını taklit eder.** Arama otuz sonuç döndürecekse bile iskeletin otuz
satır çizmesi gereksizdir; görünür alandaki dokuz satır kaymayı sıfırlamaya yeter.

İskelet gösteriminin bir sınırı vardır ve gözden kaçırılır: iskelet, gelecek içeriğin
**yapısını** taahhüt eder. Dokuz satırlık bir iskelet çizilip ardından üç sonuç geldiğinde,
kullanıcı bir an için dokuz sonuç olduğunu sanmış olur. Bu yüzden iskelet yalnızca sonucun
geleceği kesin olduğunda kullanılır; sonuç sayısının sıfır olma olasılığı yüksek bir
aramada iskelet yerine yer tutan boş bir alan uygundur.

#### Boş Durum Bir Hata Değildir

Yüklemenin bittiği ama içeriğin olmadığı durum, üç ayrı nedenden doğar ve üçü farklı şey
söyler.

**İlk kullanım boşluğu.** Kullanıcı katalog sayfasını yeni açtı ve henüz arama yapmadı.
Burada eksik olan veri değil, kullanıcının eylemi. Doğru içerik bir açıklama ve bir
başlangıç yoludur: aramanın neyi kapsadığı ve hangi alanlarda arama yapılabildiği.

**Sonuçsuz arama boşluğu.** Kullanıcı arama yaptı, sistem çalıştı ve eşleşen kayıt yok. Burada
eksik olan veri. Doğru içerik, aramanın ne olduğunu tekrarlamak ve daraltmayı gevşetecek bir yol
sunmaktır: uygulanan filtrelerin listesi ve tek tek kaldırma seçeneği. Arama metnini tekrarlamak
bir ayrıntı değil, gerekliliktir; kullanıcının yazdığını yanlış yazdığını görmesinin tek yolu
odur.

**Temizlenmiş liste boşluğu.** Kullanıcı ödünç aldığı bütün kitapları iade etti ve "ödünç
aldıklarım" listesi boşaldı. Burada boşluk bir başarıdır. Doğru içerik ne açıklama ne
öneridir; durumu bildiren tek bir satır yeterlidir.

Üç durumun ortak yanlışı, boşluğu bir hata gibi sunmaktır. Hata rengi, uyarı simgesi ve
"bulunamadı" ifadesi kullanıcıya bir şeyin bozulduğunu söyler; oysa üç durumda da sistem
doğru çalışmıştır. İşlevsel Renkler dersindeki ayrım burada uygulanır: boş durum bilgi
sınıfına girer, hata sınıfına değil.

Boş durumun ikinci yaygın yanlışı, ekranı tümüyle boş bırakmaktır. Kullanıcı bu durumda
yüklemenin sürüp sürmediğini bilemez. Boş durum da bir tasarımdır ve bir metin taşımak
zorundadır; hangi metnin nasıl yazılacağı bu konunun son dersinin sorusudur.

#### Özet

- Bekleme tek bir durum değil, süreye göre farklı gösterim isteyen bir aralıktır; sınıf
  sınırları ölçülen sürelerle karşılaştırılır.
- Ölçüm çoğu isteğin gösterge eşiğinin altında tamamlandığını gösterirse, gösterge
  gecikmeli açılır; gecikme titreme oranını doğrudan düşürür.
- Gösterge açıldıktan sonra en az bir süre görünür kalır; bunun bedeli bazı isteklere
  eklenen yapay gecikmedir ve bu bedel hesaplanabilir.
- İskelet gösterimi yer ayırarak yerleşim kaymasını sıfırlar; doğru satır sayısı gelecek
  sonuç sayısı değil, görünür alana sığan satır sayısıdır.
- İskelet gelecek içeriğin yapısını taahhüt eder; sonuç gelmeme olasılığı yüksekse iskelet
  yanıltıcıdır.
- Boş durum üç ayrı nedenden doğar ve hiçbiri hata değildir; boşluk bilgi sınıfında sunulur
  ve her zaman bir metin taşır.

#### Sonraki Adım

Boş durum sistemin doğru çalıştığı bir sonuçtu. Sistemin doğru çalışmadığı durumlar ise
ayrı bir tasarım gerektirir: ISBN alanına geçersiz bir değer girildi, ödünç alma isteği
sunucuya ulaşamadı, kullanıcının ödünç alma hakkı doldu. Bu üçü aynı kutuda sunulamaz,
çünkü üçünün de kurtarma yolu farklıdır. Sonraki ders hatayı kaynağına ve kurtarılabilirliğine
göre sınıflandırır, iletinin nereye konacağını hata alanına olan uzaklıkla belirler ve
hangi hataların hiç gösterilmemesi gerektiğini tartışır.

### Hata ve Uyarı Durumları

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/hata-ve-uyari-durumlari

Hatanın kaynağa ve kurtarılabilirliğe göre sınıflandırılması, doğrulama anının ürettiği ileti sayısının hesabı, önlenebilir hataların ayıklanması ve iletinin yakınlık kuralıyla yerleştirilmesi.

Önceki ders boşluğun bir hata olmadığını gösterdi: sistem doğru çalışmış, sonuç
gelmemişti. Sistemin doğru çalışmadığı ya da kullanıcının verdiği girdinin kabul
edilemediği durumlar ise ayrı bir tasarım gerektirir.

Katalog arayüzünde üç ayrı örnek var: ISBN alanına geçersiz bir değer girildi, ödünç alma isteği
sunucuya ulaşamadı, kullanıcının ödünç alma hakkı doldu. Üçü de "hata" diye adlandırılır ama
üçünün kurtarma yolu farklıdır. Birincisinde kullanıcı girdiyi düzeltir; ikincisinde tekrar dener;
üçüncüsünde yapabileceği bir şey yoktur. Aynı kutuda sunulamazlar.

#### Hata Kaynağa ve Kurtarılabilirliğe Göre Ayrılır

Sınıflandırma iki soruya dayanır: hata nereden geldi ve kullanıcı bunu düzeltebilir mi?

- **Girdi hatası, kurtarılabilir.** Kullanıcının verdiği değer kabul edilmedi. Kurtarma yolu
  değeri düzeltmektir. İleti alanın yanında durur ve neyin beklendiğini söyler.
- **Durum hatası, kurtarılabilir.** İşlem yapılamaz çünkü sistemin durumu uygun değil —
  kayıt başkasında, hak dolmuş. Kurtarma yolu farklı bir eylemdir: rezervasyon, iade
  tarihini bekleme. İleti eylemin yanında durur ve alternatifi gösterir.
- **Geçici sistem hatası, kurtarılabilir.** İstek ulaşmadı ya da zaman aşımına uğradı.
  Kurtarma yolu tekrar denemektir. İleti işlemin yanında durur ve tekrar deneme yolunu
  içerir.
- **Kalıcı sistem hatası, kurtarılamaz.** Kullanıcının yapabileceği bir şey yok. İleti
  durumu bildirir ve başka bir yol gösterir; "tekrar deneyin" yazmaz, çünkü tekrar deneme
  işe yaramayacaktır.

Ayrımın pratik değeri iletinin içeriğini belirlemesidir. Kurtarılabilir bir hatada ileti
**kurtarma yolunu** taşımak zorundadır; taşımıyorsa kullanıcı için hata iletisi değil, yalnızca
bir engel bildirimidir. Kurtarılamaz bir hatada ise kurtarma yolu önermek yanıltıcıdır.

Uyarı, hatadan bu eksende ayrılır: uyarıda işlem sürebilir. "Bu kayıt başka şubede" bir
uyarıdır, çünkü kullanıcı yine de rezervasyon yapabilir. "Ödünç alma hakkınız doldu" bir
hatadır, çünkü işlem yapılamaz. Renk seçimi bu ayrımı izler, tersi değil.

#### Doğrulama Anı İleti Sayısını Belirler

Girdi hatalarında en önemli karar, doğrulamanın **ne zaman** çalıştığıdır. Aynı doğrulama
kuralı, farklı anlarda çalıştırıldığında farklı sayıda ileti üretir.

```js
// dogrulama.mjs — dogrulama aninin uretilen hata iletisi sayisina etkisi

// ISBN-13 saglama: ilk 12 basamak 1,3,1,3... agirlikli toplanir; kontrol basamagi tumler.
function isbnGecerli(dizgi) {
  if (!/^\d{13}$/.test(dizgi)) return false;
  const b = [...dizgi].map(Number);
  let toplam = 0;
  for (let i = 0; i < 12; i++) toplam += b[i] * (i % 2 === 0 ? 1 : 3);
  return (10 - (toplam % 10)) % 10 === b[12];
}

// Bir girdinin, tamamlanmadan once gecersizligi kesin mi?
function erkenGecersiz(onek) {
  return /[^0-9]/.test(onek) || onek.length > 13;
}

const OTURUMLAR = [
  { ad: "dogru yazim",     hedef: "9789750718533" },
  { ad: "son basamak yanlis", hedef: "9789750718534" },
  { ad: "harf karisti",    hedef: "97897507I8533" },
];

for (const o of OTURUMLAR) {
  const onekler = Array.from({ length: o.hedef.length }, (_, i) => o.hedef.slice(0, i + 1));

  // 1) Her tus vuruşunda dogrula
  const herTus = onekler.filter((p) => !isbnGecerli(p)).length;
  // 2) Yalnizca kesin gecersizlikte uyar, kalanini bekle
  const erken = onekler.filter((p) => erkenGecersiz(p)).length;
  // 3) Alandan cikista dogrula
  const cikista = isbnGecerli(o.hedef) ? 0 : 1;
  // 4) Gonderimde dogrula
  const gonderimde = cikista;

  console.log(
    `${o.ad.padEnd(20)} her tus: ${String(herTus).padStart(2)}   kesin gecersizlikte: ${String(erken).padStart(2)}   ` +
      `alandan cikista: ${cikista}   gonderimde: ${gonderimde}   sonuc: ${isbnGecerli(o.hedef) ? "gecerli" : "gecersiz"}`
  );
}

// Kesin gecersizlik hangi karakterde saptanabilir?
console.log("\noturum               ilk kesin gecersiz onek uzunlugu  toplam uzunluk");
for (const o of OTURUMLAR) {
  const onekler = Array.from({ length: o.hedef.length }, (_, i) => o.hedef.slice(0, i + 1));
  const i = onekler.findIndex(erkenGecersiz);
  console.log(
    `${o.ad.padEnd(20)} ${(i === -1 ? "yok" : String(i + 1)).padStart(33)} ${String(o.hedef.length).padStart(15)}`
  );
}

// Katalog kayitlarinin ISBN alanlari gercekten gecerli mi?
console.log("\nkayit isbn      gecerli");
for (const isbn of ["9789750718533", "9789944882101", "9786055829476"]) {
  console.log(`${isbn}   ${isbnGecerli(isbn)}`);
}
```

```
dogru yazim          her tus: 12   kesin gecersizlikte:  0   alandan cikista: 0   gonderimde: 0   sonuc: gecerli
son basamak yanlis   her tus: 13   kesin gecersizlikte:  0   alandan cikista: 1   gonderimde: 1   sonuc: gecersiz
harf karisti         her tus: 13   kesin gecersizlikte:  5   alandan cikista: 1   gonderimde: 1   sonuc: gecersiz

oturum               ilk kesin gecersiz onek uzunlugu  toplam uzunluk
dogru yazim                                        yok              13
son basamak yanlis                                 yok              13
harf karisti                                         9              13

kayit isbn      gecerli
9789750718533   true
9789944882101   true
9786055829476   true
```

Birinci satır kararı tek başına veriyor. Kullanıcı ISBN'i **doğru** yazdı ve her tuş
vuruşunda doğrulayan bir arayüz ona on iki hata iletisi gösterdi. Hiçbiri yanlış değildi:
her ara adım gerçekten geçersiz bir ISBN'di. Ama hiçbiri de doğru değildi, çünkü kullanıcı
henüz yazmayı bitirmemişti.

Buradan çıkan kural şudur: **bir girdi tamamlanmadan geçersizliği kesin değilse
doğrulanmaz.** Alandan çıkışta doğrulama, doğru yazımda sıfır, yanlış yazımda bir ileti
üretiyor; istenen davranış budur.

Üçüncü satır kuralın istisnasını gösteriyor. Harf karışan oturumda geçersizlik dokuzuncu
karakterde kesinleşiyor; ondan sonra yazılacak hiçbir karakter değeri geçerli yapamaz.
Kesinleşmiş bir geçersizliği tamamlanmayı bekleyerek saklamak, kullanıcıyı dört karakter
daha boşuna yazdırmak demektir. Kural bu yüzden iki parçalıdır: kesinleşmemiş geçersizlik
beklenir, kesinleşmiş geçersizlik anında bildirilir.

Bu ayrım doğrulama kuralının biçimini de belirler. Bir alanın kuralı yalnızca "geçerli mi"
sorusunu değil, "bu önek geçerli bir değere uzayabilir mi" sorusunu da yanıtlayabilmelidir.

#### En İyi Hata İletisi Gösterilmeyenidir

Bazı hatalar hata değildir; girdinin biçimsel gürültüsüdür ve arayüz onları sessizce
düzeltebilir.

```js
// kurtarma.mjs — onlenebilir hatalarin ayiklanmasi ve iletinin yerlestirilmesi

function isbnGecerli(dizgi) {
  if (!/^\d{13}$/.test(dizgi)) return false;
  const b = [...dizgi].map(Number);
  let toplam = 0;
  for (let i = 0; i < 12; i++) toplam += b[i] * (i % 2 === 0 ? 1 : 3);
  return (10 - (toplam % 10)) % 10 === b[12];
}
// Bicimsel gurultuyu temizle: bosluk, tire, uzun tire, ayrac noktalari
const normalle = (s) => s.replace(/[\s\-‐-―.]/g, "");

const GIRDILER = [
  "9789750718533",
  "978-975-07-1853-3",
  " 9789944882101 ",
  "978 605 5829 47 6",
  "9789750718534",
  "97897507I8533",
];

console.log("girdi                    ham gecerli  normalize                gecerli  sinif");
let onlenebilir = 0, gercek = 0;
for (const g of GIRDILER) {
  const n = normalle(g);
  const ham = isbnGecerli(g), son = isbnGecerli(n);
  let sinif;
  if (ham) sinif = "sorunsuz";
  else if (son) { sinif = "ONLENEBILIR"; onlenebilir++; }
  else { sinif = "gercek hata"; gercek++; }
  console.log(
    `${JSON.stringify(g).padEnd(24)} ${String(ham).padStart(11)}  ${n.padEnd(24)} ${String(son).padStart(7)}  ${sinif}`
  );
}
console.log(`\nonlenebilir hata: ${onlenebilir} / ${GIRDILER.length}   gercek hata: ${gercek} / ${GIRDILER.length}`);

// Iletinin yerlestirilmesi: yakinlik kurali (grup ici bosluk < gruplar arasi bosluk)
const ETIKET_Y = 20, ALAN_Y = 44, ILETI_Y = 18;
const GRUP_ICI = 4;      // etiket-alan ve alan-ileti araligi
const GRUPLAR_ARASI = 24; // iki alan grubu arasindaki aralik

const alanlar = ["Baslik", "Yazar", "ISBN", "Raf kodu"];
let y = 0;
const konum = {};
for (const a of alanlar) {
  konum[a] = { etiket: y, alan: y + ETIKET_Y + GRUP_ICI };
  y = konum[a].alan + ALAN_Y + GRUPLAR_ARASI;
}
console.log("\nalan       etiket y  alan y  alan alt kenari");
for (const a of alanlar) {
  console.log(`${a.padEnd(10)} ${String(konum[a].etiket).padStart(8)} ${String(konum[a].alan).padStart(7)} ${String(konum[a].alan + ALAN_Y).padStart(16)}`);
}

const hataliAlan = "ISBN";
const altKenar = konum[hataliAlan].alan + ALAN_Y;
const sonrakiEtiket = konum["Raf kodu"].etiket;
console.log(`\nileti alanin altina konursa:`);
console.log(`  iletiye uzaklik (kendi alanindan): ${GRUP_ICI} px`);
console.log(`  iletinin alt kenari: ${altKenar + GRUP_ICI + ILETI_Y} px`);
console.log(`  sonraki alanin etiketine uzaklik: ${sonrakiEtiket - (altKenar + GRUP_ICI + ILETI_Y)} px`);
console.log(`  yakinlik kurali (kendi alanina daha yakin): ${GRUP_ICI < sonrakiEtiket - (altKenar + GRUP_ICI + ILETI_Y)}`);

console.log(`\nileti formun ustunde ozet olarak konursa:`);
console.log(`  ozetten hatali alana dikey uzaklik: ${konum[hataliAlan].alan} px`);
console.log(`  arada kalan alan sayisi: ${alanlar.indexOf(hataliAlan)}`);

// Yakinlik kurali saglanmiyorsa: gruplar arasi aralik en az ne olmali?
const KAT = 3; // ileti kendi alanina, sonraki gruba gore en az bu kat daha yakin olmali
const gerekenAralik = GRUP_ICI + ILETI_Y + KAT * GRUP_ICI;
console.log(`\nileti bloguyla birlikte alanin altinda kaplanan yer: ${GRUP_ICI + ILETI_Y} px`);
console.log(`gruplar arasi aralik en az: ${gerekenAralik} px  (simdiki: ${GRUPLAR_ARASI} px)`);
console.log(`yeni aralikta sonraki etikete uzaklik: ${gerekenAralik - GRUP_ICI - ILETI_Y} px, kendi alanina ${GRUP_ICI} px, oran ${(gerekenAralik - GRUP_ICI - ILETI_Y) / GRUP_ICI}`);

// Ileti gorununce sonraki alanlar kayiyor mu? Yer ayrilirsa kaymaz.
const kayma = GRUP_ICI + ILETI_Y;
console.log(`\nileti icin yer ayrilmazsa sonraki her alan ${kayma} px asagi kayar`);
console.log(`dort alanli formda toplam kayan alan sayisi (ISBN hatali): ${alanlar.length - 1 - alanlar.indexOf("ISBN")}`);
console.log(`yer ayrilirsa kayma: 0 px, bedeli: her alanin altinda surekli ${kayma} px bos yer`);
```

```
girdi                    ham gecerli  normalize                gecerli  sinif
"9789750718533"                 true  9789750718533               true  sorunsuz
"978-975-07-1853-3"            false  9789750718533               true  ONLENEBILIR
" 9789944882101 "              false  9789944882101               true  ONLENEBILIR
"978 605 5829 47 6"            false  9786055829476               true  ONLENEBILIR
"9789750718534"                false  9789750718534              false  gercek hata
"97897507I8533"                false  97897507I8533              false  gercek hata

onlenebilir hata: 3 / 6   gercek hata: 2 / 6

alan       etiket y  alan y  alan alt kenari
Baslik            0      24               68
Yazar            92     116              160
ISBN            184     208              252
Raf kodu        276     300              344

ileti alanin altina konursa:
  iletiye uzaklik (kendi alanindan): 4 px
  iletinin alt kenari: 274 px
  sonraki alanin etiketine uzaklik: 2 px
  yakinlik kurali (kendi alanina daha yakin): false

ileti formun ustunde ozet olarak konursa:
  ozetten hatali alana dikey uzaklik: 208 px
  arada kalan alan sayisi: 2

ileti bloguyla birlikte alanin altinda kaplanan yer: 22 px
gruplar arasi aralik en az: 34 px  (simdiki: 24 px)
yeni aralikta sonraki etikete uzaklik: 12 px, kendi alanina 4 px, oran 3

ileti icin yer ayrilmazsa sonraki her alan 22 px asagi kayar
dort alanli formda toplam kayan alan sayisi (ISBN hatali): 1
yer ayrilirsa kayma: 0 px, bedeli: her alanin altinda surekli 22 px bos yer
```

Altı girdinin üçü, yalnızca ayraç taşıdığı için geçersiz görünüyor. Ayraçlar temizlendiğinde
üçü de geçerli. Bu üç girdi için hata iletisi göstermek, kullanıcıya çözebileceği bir sorun
vermez; onun zaten doğru olan girdisini reddeder.

Kural şu biçimde yazılır: **arayüzün kabul edebileceği bir girdi için hata gösterilmez.**
Ayraç, baştaki ve sondaki boşluk, büyük–küçük harf farkı bu sınıfa girer. Sessiz düzeltmenin
sınırı ise anlamı değiştirmemektir; bir basamağı düzeltmek sessizce yapılamaz, çünkü hangi
basamağın yanlış olduğu bilinmez.

#### İletinin Yeri Yakınlık Kuralıyla Denetlenir

İkinci tablo grubu iletinin nereye konacağını ölçüyor ve form düzeninde bir kusur buluyor.

İleti alanın 4 piksel altına konduğunda, iletinin alt kenarı 274 pikselde bitiyor ve bir
sonraki alanın etiketi 276 pikselde başlıyor. Aradaki boşluk 2 piksel. Yakınlık ve Gruplama
dersinde kurulan kural burada ihlal ediliyor: ileti, ait olduğu alandan 4, ait olmadığı
alandan 2 piksel uzakta. Ekranda okunduğunda ileti bir sonraki alana ait görünür.

Düzeltme hesaplanabilir. İleti bloğu alanın altında 22 piksel yer kaplıyor; iletinin kendi
alanına en az üç kat daha yakın olması istendiğinde gruplar arası aralık 34 piksele
çıkmalı. Yeni aralıkta ileti kendi alanına 4, sonraki etikete 12 piksel uzaklıkta oluyor.

Form üstündeki özet, yakınlık kuralını hiç sağlamaz: özet ile hatalı alan arasında 208
piksel ve iki alan var. Bu, özetin yanlış olduğu anlamına gelmez; özet farklı bir iş yapar
— kaç hata olduğunu bildirir ve her birine bir bağlantı verir. Ancak özet, alan yanındaki
iletinin **yerini tutmaz**. İkisi birlikte kullanılır: özet sayfanın başında, ileti alanın
yanında.

Son blok bir yan etkiyi gösteriyor. İleti göründüğünde altındaki alanlar 22 piksel aşağı
kayar; bu, önceki dersteki yerleşim kayması sorununun küçük ölçekli hâlidir. Kullanıcı
iletiyi okurken tıklamak istediği alan yer değiştirir. Çözüm, ileti için baştan yer
ayırmaktır; bedeli her alanın altında sürekli duran 22 piksellik boşluktur. Bu bedel
ödenir, çünkü kayan bir formda yanlış alana tıklamak yeni bir hata üretir.

#### Özet

- Hata kaynağına ve kurtarılabilirliğine göre sınıflandırılır; sınıf, iletinin içeriğini ve
  konumunu belirler.
- Kurtarılabilir hatada ileti kurtarma yolunu taşımak zorundadır; kurtarılamaz hatada
  kurtarma önermek yanıltıcıdır.
- Uyarı ile hata, işlemin sürüp süremediğine göre ayrılır; renk seçimi bu ayrımı izler.
- Tamamlanmamış bir girdi, geçersizliği kesinleşmedikçe doğrulanmaz; her tuş vuruşunda
  doğrulamak doğru yazımda bile onlarca ileti üretir. Geçersizlik kesinleştiği anda ise
  bildirilir; doğrulama kuralı "bu önek geçerli bir değere uzayabilir mi" sorusunu
  yanıtlayabilmelidir.
- Arayüzün kabul edebileceği bir girdi için hata gösterilmez; biçimsel gürültü sessizce
  temizlenir, anlam değiştiren düzeltme yapılmaz.
- İletinin konumu yakınlık kuralıyla denetlenir ve ileti için baştan yer ayrılır.

#### Sonraki Adım

Hata iletisinin yanında bir simge kullanmak yaygın bir karardır ve bu ders o kararı
gerekçelendirmedi. İşlevsel Renkler dersinde renge eşlik edecek ikinci kanal olarak simge
sayılmıştı, ancak simgenin kendisi ne zaman anlam taşır, ne zaman yalnızca yer kaplar
sorusu açık kaldı. Sonraki ders simgeyi anlam taşıyan ve süsleyici olarak ikiye ayırır,
ayrımın erişilebilirlik ağacındaki karşılığını gösterir ve simgenin tek başına metnin
yerini tutup tutamayacağını tartışır.

### Simge Kullanımı

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/simge-kullanimi

Anlam taşıyan ve süsleyici simge ayrımı, erişilebilir adın küçük bir modelle hesaplanması, simge kümesinin çokanlamlılık denetimi ve ölçek ile çizgi kalınlığının tutarlılığı.

Önceki ders hata iletisinin yanına konan simgeyi gerekçelendirmeden bıraktı. İşlevsel
Renkler dersinde simge, renge eşlik edecek ikinci kanal olarak sayılmıştı; ancak simgenin
kendisinin ne zaman bilgi taşıdığı, ne zaman yalnızca yer kapladığı sorulmadı.

Bu ders simgeyi tek bir ayrım üzerinden ele alır: simge bir bilgi taşıyor mu, yoksa yanındaki
metnin taşıdığı bilgiyi görsel olarak destekliyor mu? Ayrım estetik değildir; işaretlemede
farklı sonuçlar üretir ve yanlış tarafa düşen bir simge ya bilgiyi tümüyle kaybettirir ya
da aynı bilgiyi iki kez okutur.

#### İki Sınıf, İki Farklı Sonuç

**Anlam taşıyan simge (semantic icon)**, tek başına bir bilgi ya da eylem bildirir.
Yanında metin yoktur. Katalog arayüzünde arama alanının sonundaki büyüteç, kayıt satırının
sonundaki kaldırma simgesi bu sınıfa girer. Bu simgelerin bir **adı** olmak zorundadır;
adsız bir simge düğmesi, klavye ve ekran okuyucu için varlığı olmayan bir düğmedir.

**Süsleyici simge (decorative icon)**, yanındaki metnin bildirdiği şeyi görsel olarak
tekrar eder. Hata iletisinin başındaki ünlem simgesi bu sınıfa girer; ileti zaten "ISBN
numarası doğrulanamadı" diyor. Bu simgelerin adı olmamalıdır; olursa aynı bilgi iki kez
duyurulur.

Ayrımın sınavı basit bir soruyla yapılır: **simge kaldırılırsa bilgi kaybolur mu?**
Kaybolursa anlam taşıyor, kaybolmuyorsa süslüyor. Aynı simge iki yerde iki farklı sınıfa
düşebilir; büyüteç, yalnız durduğunda anlam taşır, "Ara" yazan bir düğmenin içinde
süsleyicidir.

#### Erişilebilir Ad Hesaplanabilir

Bir ögenin erişilebilir adı, tanımlı bir sıra izlenerek türetilir. Sıra kısaca şudur:
`aria-labelledby` başvurusu, sonra `aria-label` değeri, sonra ögenin içeriğinden üretilen
metin, en son `title` özniteliği. Bu sıra bir modelle canlandırılabilir ve simge kurulumları
tek tek denetlenebilir.

```js
// erisilebilir-ad.mjs — simge tasiyan dugmelerin erisilebilir adinin hesaplanmasi

// Kucuk bir agac modeli: her dugum bir etiket, oznitelik kumesi, metin ve cocuklar tasir.
const dugum = (etiket, oznitelik = {}, cocuklar = [], metin = "") => ({ etiket, oznitelik, cocuklar, metin });

// Icerikten ad uretebilen etiketler
const ICERIKTEN_AD = new Set(["button", "a", "th", "label", "legend", "summary"]);

function metinTopla(d, ref) {
  if (d.oznitelik["aria-hidden"] === "true") return "";
  if (d.etiket === "svg") {
    if (d.oznitelik.role === "img") {
      const baslik = d.cocuklar.find((c) => c.etiket === "title");
      return baslik ? baslik.metin : "";
    }
    return "";
  }
  if (d.etiket === "img") return d.oznitelik.alt ?? "";
  const parcalar = [d.metin, ...d.cocuklar.map((c) => metinTopla(c, ref))];
  return parcalar.filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
}

function erisilebilirAd(d, sozluk = {}) {
  // 1) aria-labelledby
  if (d.oznitelik["aria-labelledby"]) {
    const adlar = d.oznitelik["aria-labelledby"]
      .split(/\s+/)
      .map((id) => (sozluk[id] ? metinTopla(sozluk[id], sozluk) : ""))
      .filter(Boolean);
    if (adlar.length) return { ad: adlar.join(" "), kaynak: "aria-labelledby" };
  }
  // 2) aria-label
  if (d.oznitelik["aria-label"] && d.oznitelik["aria-label"].trim()) {
    return { ad: d.oznitelik["aria-label"].trim(), kaynak: "aria-label" };
  }
  // 3) icerikten ad
  if (ICERIKTEN_AD.has(d.etiket)) {
    const m = metinTopla(d, sozluk);
    if (m) return { ad: m, kaynak: "icerik" };
  }
  // 4) title oznitelii (son care)
  if (d.oznitelik.title && d.oznitelik.title.trim()) {
    return { ad: d.oznitelik.title.trim(), kaynak: "title (son care)" };
  }
  return { ad: "", kaynak: "yok" };
}

const simge = (oznitelik = {}, cocuklar = []) => dugum("svg", oznitelik, cocuklar);
const baslik = (metin) => dugum("title", {}, [], metin);

const ORNEKLER = [
  ["yalin simge", dugum("button", {}, [simge()])],
  ["aria-label + gizli simge", dugum("button", { "aria-label": "Odunc al" }, [simge({ "aria-hidden": "true" })])],
  ["role=img + title", dugum("button", {}, [simge({ role: "img" }, [baslik("Odunc al")])])],
  ["gizli simge + metin", dugum("button", {}, [simge({ "aria-hidden": "true" }), dugum("span", {}, [], "Odunc al")])],
  ["adlandirilmis simge + metin", dugum("button", {}, [simge({ role: "img" }, [baslik("Odunc al")]), dugum("span", {}, [], "Odunc al")])],
  ["yalnizca title", dugum("button", { title: "Odunc al" }, [simge()])],
];

console.log("kurulum                       erisilebilir ad        kaynak            sonuc");
for (const [ad, agac] of ORNEKLER) {
  const s = erisilebilirAd(agac);
  let sonuc;
  if (!s.ad) sonuc = "ADSIZ";
  else if (/^(.+) \1$/.test(s.ad)) sonuc = "YINELENMIS";
  else if (s.kaynak.startsWith("title")) sonuc = "zayif";
  else sonuc = "gecerli";
  console.log(`${ad.padEnd(29)} ${JSON.stringify(s.ad).padEnd(22)} ${s.kaynak.padEnd(17)} ${sonuc}`);
}
```

```
kurulum                       erisilebilir ad        kaynak            sonuc
yalin simge                   ""                     yok               ADSIZ
aria-label + gizli simge      "Odunc al"             aria-label        gecerli
role=img + title              "Odunc al"             icerik            gecerli
gizli simge + metin           "Odunc al"             icerik            gecerli
adlandirilmis simge + metin   "Odunc al Odunc al"    icerik            YINELENMIS
yalnizca title                "Odunc al"             title (son care)  zayif
```

İlk satır en sık yapılan hatadır: simge düğmenin içine konur ve başka hiçbir şey yazılmaz.
Hesaplanan ad boş; düğme ekranda görünür ama adı yoktur. Klavyeyle gezen kullanıcı düğmeye
odaklandığında ne olduğunu bilemez.

Beşinci satır ters yöndeki hatadır. Simge hem adlandırılmış hem de yanında metin var; ad
"Odunc al Odunc al" olarak hesaplanıyor. Anlam taşıyan bir simgenin süsleyici olması
gereken yerde adlandırılması bu sonucu verir.

Altıncı satır çalışıyor ama zayıf. `title` özniteliği ad zincirinin son halkasıdır ve
yalnızca imleç üzerinde durduğunda görünür; dokunmatik girdide hiç görünmez, klavyeyle
erişimde güvenilir değildir. Adı `title` ile vermek, adı vermemekle vermek arasında bir
yerdedir.

İkinci, üçüncü ve dördüncü satırlar geçerli kurulumlardır ve aralarındaki seçim
duruma bağlıdır. Simge yalnız duruyorsa ikinci ya da üçüncü; simgenin yanında metin varsa
dördüncü, yani simge gizlenir ve ad metinden gelir.

#### Simge Kümesi Kendi İçinde Tutarlı Olmalı

Tek tek simgeler doğru işaretlense bile küme düzeyinde bir tutarlılık sorunu kalabilir: aynı
biçimin iki farklı anlamı taşıması ya da birbirine yakın iki anlamın aynı biçimle
çizilmesi.

```js
// simge-kumesi.mjs — simge kumesinin tutarlilik denetimi ve olcek/cizgi kalinligi hesabi

// Katalog arayuzunde kullanilan simgelerin anlam-bicim eslesmesi
const KUME = [
  { anlam: "arama",            bicim: "buyutec" },
  { anlam: "filtre",           bicim: "huni" },
  { anlam: "odunc al",         bicim: "asagi-ok-kutu" },
  { anlam: "indir",            bicim: "asagi-ok-kutu" },
  { anlam: "kaldir",           bicim: "cop-kutusu" },
  { anlam: "sil",              bicim: "carpi" },
  { anlam: "kapat",            bicim: "carpi" },
  { anlam: "listeye ekle",     bicim: "arti" },
  { anlam: "yeni kayit",       bicim: "arti-daire" },
  { anlam: "hata",             bicim: "unlem-daire" },
  { anlam: "uyari",            bicim: "unlem-ucgen" },
  { anlam: "bilgi",            bicim: "i-daire" },
  { anlam: "basari",           bicim: "onay-daire" },
];

// 1) Ayni bicim birden fazla anlam tasiyor mu?
const bicimden = {};
for (const s of KUME) (bicimden[s.bicim] ??= []).push(s.anlam);
console.log("bicim            anlamlar                        durum");
for (const [b, anlamlar] of Object.entries(bicimden)) {
  console.log(
    `${b.padEnd(16)} ${anlamlar.join(", ").padEnd(31)} ${anlamlar.length > 1 ? "COKANLAMLI" : "tekil"}`
  );
}

// 2) Yakin anlamlar farkli bicimlerle mi cizilmis?
const YAKIN = [["kaldir", "sil"], ["odunc al", "indir"], ["listeye ekle", "yeni kayit"]];
console.log("\nyakin anlam cifti           bicimler                         durum");
for (const [a, b] of YAKIN) {
  const ba = KUME.find((s) => s.anlam === a).bicim;
  const bb = KUME.find((s) => s.anlam === b).bicim;
  console.log(
    `${(a + " / " + b).padEnd(27)} ${(ba + " / " + bb).padEnd(32)} ${ba === bb ? "AYRILMIYOR" : "ayriliyor"}`
  );
}

// 3) Olcek: kutu buyudukce cizgi kalinligi orantili buyurse gorsel agirlik korunur mu?
const TABAN_KUTU = 24, TABAN_CIZGI = 1.5;
console.log("\nkutu  orantili cizgi  yuvarlanmis  cizgi/kutu  taban orana gore sapma");
for (const kutu of [14, 16, 18, 20, 24, 28, 32]) {
  const orantili = (TABAN_CIZGI * kutu) / TABAN_KUTU;
  const yuvarlanmis = Math.round(orantili * 2) / 2; // yarim piksel adimlarina yuvarla
  const oran = yuvarlanmis / kutu;
  const taban = TABAN_CIZGI / TABAN_KUTU;
  console.log(
    `${String(kutu).padStart(4)} ${orantili.toFixed(3).padStart(15)} ${yuvarlanmis.toFixed(2).padStart(12)} ${oran.toFixed(4).padStart(11)} ${((oran / taban - 1) * 100).toFixed(2).padStart(22)}%`
  );
}

// 4) Simge dugmesinin dokunma hedefi: 24 px simge, 44 px hedef
const SIMGE = 24, HEDEF = 44;
console.log(`\nsimge ${SIMGE} px, dokunma hedefi ${HEDEF} px -> her kenarda dolgu: ${(HEDEF - SIMGE) / 2} px`);
console.log(`simge alaninin hedefe orani: ${((SIMGE * SIMGE) / (HEDEF * HEDEF) * 100).toFixed(1)}%`);

// 5) Simge cizgisinin yuzeyden ayrilmasi: metin disi kontrast esigi 3.0
function kanal(v) { const s = v / 255; return s <= 0.04045 ? 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);
}
const coz = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
console.log("\nsimge rengi  yuzeye karsi  esik 3.0");
for (const hex of ["#b7bcc2", "#969da6", "#757e8a", "#5e656e"]) {
  const k = kontrast(coz(hex), [255, 255, 255]);
  console.log(`${hex}      ${k.toFixed(2).padStart(8)}:1  ${k >= 3 ? "gecti" : "KALDI"}`);
}
```

```
bicim            anlamlar                        durum
buyutec          arama                           tekil
huni             filtre                          tekil
asagi-ok-kutu    odunc al, indir                 COKANLAMLI
cop-kutusu       kaldir                          tekil
carpi            sil, kapat                      COKANLAMLI
arti             listeye ekle                    tekil
arti-daire       yeni kayit                      tekil
unlem-daire      hata                            tekil
unlem-ucgen      uyari                           tekil
i-daire          bilgi                           tekil
onay-daire       basari                          tekil

yakin anlam cifti           bicimler                         durum
kaldir / sil                cop-kutusu / carpi               ayriliyor
odunc al / indir            asagi-ok-kutu / asagi-ok-kutu    AYRILMIYOR
listeye ekle / yeni kayit   arti / arti-daire                ayriliyor

kutu  orantili cizgi  yuvarlanmis  cizgi/kutu  taban orana gore sapma
  14           0.875         1.00      0.0714                  14.29%
  16           1.000         1.00      0.0625                   0.00%
  18           1.125         1.00      0.0556                 -11.11%
  20           1.250         1.50      0.0750                  20.00%
  24           1.500         1.50      0.0625                   0.00%
  28           1.750         2.00      0.0714                  14.29%
  32           2.000         2.00      0.0625                   0.00%

simge 24 px, dokunma hedefi 44 px -> her kenarda dolgu: 10 px
simge alaninin hedefe orani: 29.8%

simge rengi  yuzeye karsi  esik 3.0
#b7bcc2          1.91:1  KALDI
#969da6          2.74:1  KALDI
#757e8a          4.11:1  gecti
#5e656e          5.89:1  gecti
```

İlk tablo iki çokanlamlılık buluyor. "Çarpı" biçimi hem silme hem kapatma anlamını
taşıyor; bu ikisi geri alınabilirlik açısından taban tabana zıt eylemlerdir. İkinci
çakışma daha ağır: "aşağı ok kutu" biçimi hem ödünç almayı hem indirmeyi gösteriyor.

İkinci tablo çakışmanın hangi durumda gerçekten sorun olduğunu ayırıyor. Kaldırma ile
silme yakın anlamlardır ve farklı biçimlerle çizilmiş — sorun yok. Ödünç alma ile indirme
ise hem yakın anlamlı hem aynı biçimli; kullanıcı hangi düğmenin kayıt ayrıntısını
indirdiğini, hangisinin kitabı ödünç aldığını simgeden okuyamaz.

Çokanlamlılık her zaman kusur değildir. Çarpı biçiminin iki anlamı, göründükleri bağlamla
ayrılır: kapatma çarpısı bir katmanın köşesinde, silme çarpısı bir liste ögesinin yanında
durur. Bağlam ayrımı üretiyorsa çakışma kabul edilebilir. Ödünç alma ile indirme aynı
satırda yan yana durduğu için bağlam ayrım üretmiyor; orada biçimlerden biri değişmelidir.

Üçüncü tablo ölçek sorununu ölçüyor. Çizgi kalınlığı kutu boyutuyla orantılı büyütülüp
yarım piksele yuvarlandığında, çizgi–kutu oranı taban orandan yüzde 11 ile yüzde 20 arasında
sapıyor. Sapma yönü de düzensiz: 18 pikselde çizgi inceliyor, 20 pikselde kalınlaşıyor.
Aynı kümedeki iki simge yan yana konduğunda biri diğerinden ağır görünür.

Çözüm, tipografik ölçekteki çözümün aynısıdır: **ara boyut üretilmez.** Simge kümesi
yalnızca sapması sıfır olan boyutlarda çizilir — burada 16, 24 ve 32 — ve arada bir boyut
gerektiğinde ölçek değil, en yakın basamak seçilir.

Son iki blok iki sınırı veriyor. Yirmi dört piksellik bir simge, kırk dört piksellik dokunma
hedefinin yalnızca yüzde 29.8'ini kaplıyor; geri kalanı görünmez dolgudur ve tıklanabilir alanın
simgeden büyük olması gerektiğini hatırlatır. Simge çizgisinin rengi ise metin dışı kontrast
eşiğine tabidir: nötr 300 ve 400 basamakları eşiği geçmiyor, bu yüzden simge en açık nötr 500
basamağında çizilebilir.

#### Simge Metnin Yerini Tutmaz

Yaygın bir tasarım tercihi, yer kazanmak için metin etiketlerini kaldırıp yalnızca simge
bırakmaktır. Bu tercih bir bilgiyi öğrenilmiş bir sözleşmeye devreder ve sözleşmenin var
olup olmadığı simge simge değişir.

Büyüteç ile arama arasındaki bağ yerleşiktir. Buna karşılık "aşağı ok kutu" ile ödünç alma
arasında yerleşik bir bağ yoktur; o bağ bu arayüzün kendi icadıdır ve kullanıcı onu ancak
kullanarak öğrenir. Yerleşik olmayan bir bağın metinsiz kullanılması, kullanıcıyı deneme
yanılmaya zorlar.

Ölçüt şu biçimde yazılabilir: **simge, ancak taşıdığı anlam arayüzün dışında da yerleşikse
metinsiz kullanılabilir.** Yerleşik değilse simgenin yanında metin durur; simge o zaman
süsleyici sınıfa geçer ve gizlenir.

İki durumda metin kaldırılabilir. Birincisi, eylem geri alınabilir ve yanlış tıklamanın
bedeli düşükse. İkincisi, aynı eylem arayüzde başka bir yerde metinli olarak da sunuluyorsa;
simge o zaman bir kısayoldur, tek yol değildir.

#### Özet

- Simgeler anlam taşıyan ve süsleyici olarak ikiye ayrılır; sınav, simge kaldırıldığında
  bilginin kaybolup kaybolmadığıdır.
- Anlam taşıyan simgenin bir erişilebilir adı olmak zorundadır; süsleyici simge
  gizlenmelidir, aksi hâlde aynı bilgi iki kez duyurulur.
- Erişilebilir ad tanımlı bir sırayla hesaplanır; adı `title` özniteliğine bırakmak zayıf
  bir çözümdür.
- Simge kümesi çokanlamlılık açısından denetlenir; çakışma ancak bağlam ayrım üretiyorsa
  kabul edilebilir.
- Çizgi kalınlığı her boyutta orantılı kalmaz; küme yalnızca sapması sıfır olan boyutlarda
  çizilir ve ara boyut üretilmez.
- Simge yalnızca taşıdığı anlam arayüzün dışında yerleşikse metinsiz kullanılabilir.

#### Sonraki Adım

Bu ders simgenin metnin yerini tutmadığını gösterdi ve tartışmayı metne devretti. Arayüzdeki
metin — düğme etiketleri, alan adları, boş durum cümleleri, hata iletileri — bugüne kadar
her derste geçti ama hiçbirinde tasarım kararı olarak ele alınmadı. Sonraki ders arayüz
metnini tasarımın kendisi olarak ele alır: eylem etiketlerinin nasıl seçildiğini, hata
iletisinin hangi üç parçadan oluştuğunu ve metnin uzunluğunun düzeni nasıl kısıtladığını
hesapla gösterir.

### Mikro Metin

Source: https://academia.sh/tr/kurslar/arayuz-temelleri/mikro-metin

Arayüz metninin bir düzen kısıtı olarak hesaplanması, eylem etiketlerinin genişlik bütçesi, başlık kırpmasının ön ek çakışması üretmesi ve hata iletisinin üç parçası.

Önceki ders simgenin metnin yerini tutmadığını gösterdi ve tartışmayı metne devretti.
Arayüzdeki metin — düğme etiketleri, alan adları, boş durum cümleleri, hata iletileri — bu
kursun her dersinde geçti ama hiçbirinde tasarım kararı olarak ele alınmadı. Metin, tasarım
bittikten sonra doldurulacak bir yer değildir; düzenin en sert kısıtlarından biridir.

**Mikro metin (microcopy)**, arayüzün işlevsel metinleridir: bir eylemi adlandıran,
bir durumu bildiren, bir alanı açıklayan kısa parçalar. Bu ders üç sorusunu ele alır:
etiket ne kadar yer kaplar, kırpma neyi bozar ve bir hata iletisi hangi parçalardan oluşur?

#### Etiket Bir Genişlik Bütçesidir

Bir düğmenin genişliği metninden türer. Aynı eylemi adlandıran farklı ifadeler farklı
genişlikler üretir ve bu genişlikler eylem şeridine sığmak zorundadır.

```js
// etiket.mjs — eylem etiketlerinin genisligi ve eylem seridine sigmasi

// Aday ailenin ilerleme genislikleri (em = 1000 birim); Satir Uzunlugu dersindeki tablo.
const G = {
  " ": 260, a: 556, b: 574, c: 500, ç: 500, d: 574, e: 545, f: 340, g: 574,
  ğ: 574, h: 560, i: 244, ı: 244, j: 244, k: 520, l: 244, m: 856, n: 560,
  o: 570, ö: 570, p: 574, r: 366, s: 480, ş: 480, t: 358, u: 560, ü: 560,
  v: 508, y: 508, z: 470, A: 660, B: 660, C: 686, Ç: 686, D: 720, E: 610,
  G: 740, H: 730, I: 280, İ: 280, K: 660, M: 890, N: 730, O: 760, Ö: 760,
  P: 610, R: 660, S: 620, Ş: 620, T: 620, U: 720, Ü: 720, V: 660, Y: 620,
  Z: 600, ",": 260, ".": 260, ":": 260, "-": 340, "'": 200,
};
const VARSAYILAN = 560;
const BOYUT = 16, DOLGU = 16, ARALIK = 8;

const genislik = (s) => ([...s].reduce((t, c) => t + (G[c] ?? VARSAYILAN), 0) / 1000) * BOYUT;
const dugmeGenisligi = (s) => genislik(s) + 2 * DOLGU;

const ADAYLAR = [
  ["uzun", ["Ödünç al", "Listeye ekle", "Rezervasyon oluştur"]],
  ["kisa", ["Ödünç al", "Listeye ekle", "Rezerve et"]],
  ["cok kisa", ["Al", "Ekle", "Rezerve et"]],
];

const SERIT = 360; // kayit ayrinti sutununun dar ekranda kalan genisligi

console.log("etiket kumesi  etiketler                                    dugme genislikleri        toplam  360 px");
for (const [ad, etiketler] of ADAYLAR) {
  const genislikler = etiketler.map(dugmeGenisligi);
  const toplam = genislikler.reduce((a, b) => a + b, 0) + ARALIK * (etiketler.length - 1);
  console.log(
    `${ad.padEnd(14)} ${etiketler.join(" / ").padEnd(44)} ${genislikler.map((v) => v.toFixed(0)).join(" ").padStart(22)} ${toplam.toFixed(0).padStart(7)}  ${toplam <= SERIT ? "sigiyor" : "TASIYOR"}`
  );
}

// Ayni eylemin farkli yazimlari arasindaki genislik farki
console.log("\neylem etiketi          metin genisligi  dugme genisligi");
for (const e of ["Ödünç al", "Ödünç alın", "Bu kaydı ödünç al", "Rezervasyon oluştur", "Rezerve et"]) {
  console.log(`${e.padEnd(22)} ${genislik(e).toFixed(1).padStart(15)} ${dugmeGenisligi(e).toFixed(1).padStart(16)}`);
}

// Esit genislikli dugme kullanilirsa: en genis etiket herkesi belirler
const kume = ADAYLAR[0][1];
const enGenis = Math.max(...kume.map(dugmeGenisligi));
const esitToplam = enGenis * kume.length + ARALIK * (kume.length - 1);
console.log(`\nesit genislikli dugme secilirse: her dugme ${enGenis.toFixed(0)} px, toplam ${esitToplam.toFixed(0)} px`);
console.log(`degisken genislikte toplam: ${(kume.map(dugmeGenisligi).reduce((a, b) => a + b, 0) + ARALIK * 2).toFixed(0)} px`);
console.log(`esit genislige gecmenin bedeli: ${(esitToplam - (kume.map(dugmeGenisligi).reduce((a, b) => a + b, 0) + ARALIK * 2)).toFixed(0)} px`);
```

```
etiket kumesi  etiketler                                    dugme genislikleri        toplam  360 px
uzun           Ödünç al / Listeye ekle / Rezervasyon oluştur             96 118 179     409  TASIYOR
kisa           Ödünç al / Listeye ekle / Rezerve et                     96 118 109     339  sigiyor
cok kisa       Al / Ekle / Rezerve et                                    46 63 109     234  sigiyor

eylem etiketi          metin genisligi  dugme genisligi
Ödünç al                          64.2             96.2
Ödünç alın                        77.1            109.1
Bu kaydı ödünç al                127.5            159.5
Rezervasyon oluştur              146.7            178.7
Rezerve et                        76.8            108.8

esit genislikli dugme secilirse: her dugme 179 px, toplam 552 px
degisken genislikte toplam: 409 px
esit genislige gecmenin bedeli: 143 px
```

Birinci tablo metnin bir düzen kısıtı olduğunu gösteriyor. "Rezervasyon oluştur" ifadesi
tek başına 179 piksellik bir düğme üretiyor; üç düğme yan yana konduğunda toplam 409
piksele çıkıyor ve 360 piksellik şeride sığmıyor. Aynı üç eylem "Rezerve et" ifadesiyle
339 piksele iniyor ve sığıyor.

Buradan çıkan sonuç, kısaltmanın bir üslup tercihi değil bir ölçü kararı olmasıdır.
Kısaltma yapılırken kaybedilecek bilgi de hesaba katılır: "Rezervasyon oluştur" ile
"Rezerve et" aynı eylemi adlandırır, "Al" ise adlandırmaz — neyin alındığı bilgisi
kaybolur.

İkinci tablo Türkçenin özgül bir kısıtını ölçüyor. "Ödünç al" 64.2 piksel, aynı eylemin
nazik biçimi "Ödünç alın" 77.1 piksel, nesneli biçimi "Bu kaydı ödünç al" 127.5 piksel.
Ekleme ve nesne, genişliği iki katına çıkarabiliyor. Eylem etiketlerinde emir kipinin
kısa biçimi seçilir; bu bir nezaket kararı değil, 63 piksellik bir bütçe kararıdır.

Üçüncü blok bir düzen alışkanlığının bedelini veriyor. Eşit genişlikli düğmeler seçildiğinde
en geniş etiket bütün düğmeleri belirliyor ve toplam 552 piksele, yani değişken genişliğin
1.35 katına çıkıyor. 143 piksellik bu fark dar ekranda karşılanamaz. Eşit genişlik, ancak
etiketler zaten birbirine yakınsa ve şeritte yer varsa seçilir.

#### Kırpma Ayırt Ediciliği Yok Edebilir

Sonuç listesindeki kayıt başlıkları sütuna sığmadığında kırpılır. Kırpmanın görünür bedeli
bilginin bir kısmının kaybolmasıdır; görünmeyen bedeli ise iki farklı kaydın **aynı**
görünür hâle gelmesidir.

```js
// kirpma.mjs — baslik kirpilmasi, on ek cakismasi ve hata iletisinin uc parcasi

const G = {
  " ": 260, a: 556, b: 574, c: 500, ç: 500, d: 574, e: 545, f: 340, g: 574,
  ğ: 574, h: 560, i: 244, ı: 244, j: 244, k: 520, l: 244, m: 856, n: 560,
  o: 570, ö: 570, p: 574, r: 366, s: 480, ş: 480, t: 358, u: 560, ü: 560,
  v: 508, y: 508, z: 470, A: 660, B: 660, C: 686, Ç: 686, D: 720, E: 610,
  G: 740, H: 730, I: 280, İ: 280, K: 660, M: 890, N: 730, O: 760, Ö: 760,
  P: 610, R: 660, S: 620, Ş: 620, T: 620, U: 720, Ü: 720, V: 660, Y: 620,
  Z: 600, ",": 260, ".": 260, ":": 260, "-": 340, "'": 200, "…": 800,
};
const VARSAYILAN = 560;
const genislik = (s, px) => ([...s].reduce((t, c) => t + (G[c] ?? VARSAYILAN), 0) / 1000) * px;

function kirp(s, px, sinir) {
  if (genislik(s, px) <= sinir) return s;
  const nokta = genislik("…", px);
  let sonuc = "";
  for (const c of s) {
    if (genislik(sonuc + c, px) + nokta > sinir) break;
    sonuc += c;
  }
  return sonuc + "…";
}

const BASLIKLAR = [
  "Işık ve Gölge",
  "Türkiye Cumhuriyeti Tarihi Araştırmaları Cilt I",
  "Türkiye Cumhuriyeti Tarihi Araştırmaları Cilt II",
  "Ağ Protokolleri El Kitabı",
];
const BOYUT = 20; // kayit basligi, olcekte 1. basamak

console.log(`kayit basligi boyutu: ${BOYUT} px`);
for (const sutun of [200, 280, 360, 520]) {
  const kirpilmis = BASLIKLAR.map((b) => kirp(b, BOYUT, sutun));
  const benzersiz = new Set(kirpilmis);
  console.log(`\nsutun ${sutun} px  (benzersiz baslik: ${benzersiz.size} / ${BASLIKLAR.length})`);
  for (let i = 0; i < BASLIKLAR.length; i++) {
    const k = kirpilmis[i];
    const cakisan = kirpilmis.filter((x) => x === k).length > 1;
    console.log(`  ${JSON.stringify(k).padEnd(52)} ${genislik(k, BOYUT).toFixed(0).padStart(4)} px  ${cakisan ? "CAKISIYOR" : k === BASLIKLAR[i] ? "tam" : "kirpildi"}`);
  }
}

// Hata iletisinin uc parcasi: ne oldu, neden, ne yapilmali
const PARCALAR = [
  { ad: "ne oldu", metin: "ISBN numarası doğrulanamadı" },
  { ad: "neden", metin: "Son basamak kontrol basamağıyla uyuşmuyor" },
  { ad: "ne yapilmali", metin: "Numarayı kitabın arka kapağından yeniden yazın" },
];
const ILETI_BOYUT = 13, SATIR_SINIRI = 525;
console.log("\nparca          metin genisligi (13 px)  tek satira sigar (525 px)");
let toplam = 0;
for (const p of PARCALAR) {
  const g = genislik(p.metin, ILETI_BOYUT);
  toplam += g;
  console.log(`${p.ad.padEnd(14)} ${g.toFixed(1).padStart(23)}  ${g <= SATIR_SINIRI ? "evet" : "hayir"}`);
}
const tumu = PARCALAR.map((p) => p.metin).join(". ");
console.log(`ucu birlestirilmis: ${genislik(tumu, ILETI_BOYUT).toFixed(1)} px, satir sayisi: ${Math.ceil(genislik(tumu, ILETI_BOYUT) / SATIR_SINIRI)}`);
```

```
kayit basligi boyutu: 20 px

sutun 200 px  (benzersiz baslik: 3 / 4)
  "Işık ve Gölge"                                       115 px  tam
  "Türkiye Cumhuriyeti…"                                198 px  CAKISIYOR
  "Türkiye Cumhuriyeti…"                                198 px  CAKISIYOR
  "Ağ Protokolleri El Ki…"                              196 px  kirpildi

sutun 280 px  (benzersiz baslik: 3 / 4)
  "Işık ve Gölge"                                       115 px  tam
  "Türkiye Cumhuriyeti Tarihi A…"                       274 px  CAKISIYOR
  "Türkiye Cumhuriyeti Tarihi A…"                       274 px  CAKISIYOR
  "Ağ Protokolleri El Kitabı"                           214 px  tam

sutun 360 px  (benzersiz baslik: 3 / 4)
  "Işık ve Gölge"                                       115 px  tam
  "Türkiye Cumhuriyeti Tarihi Araştırmal…"              354 px  CAKISIYOR
  "Türkiye Cumhuriyeti Tarihi Araştırmal…"              354 px  CAKISIYOR
  "Ağ Protokolleri El Kitabı"                           214 px  tam

sutun 520 px  (benzersiz baslik: 4 / 4)
  "Işık ve Gölge"                                       115 px  tam
  "Türkiye Cumhuriyeti Tarihi Araştırmaları Cilt I"     408 px  tam
  "Türkiye Cumhuriyeti Tarihi Araştırmaları Cilt II"    414 px  tam
  "Ağ Protokolleri El Kitabı"                           214 px  tam

parca          metin genisligi (13 px)  tek satira sigar (525 px)
ne oldu                          179.1  evet
neden                            269.7  evet
ne yapilmali                     284.4  evet
ucu birlestirilmis: 746.8 px, satir sayisi: 2
```

İki cilt, üç sütun genişliğinde de aynı kırpılmış başlığa iniyor. Dört kayıt ekranda üç
benzersiz başlık olarak görünüyor; kullanıcı hangi cildi ödünç aldığını listeden okuyamıyor.
Sütun 520 piksele çıktığında çakışma ortadan kalkıyor.

Bu bulgunun çözümü sütunu genişletmek değil. Genişletmek her zaman mümkün değildir ve
başlıklar her zaman daha da uzayabilir. Çözüm, kırpmanın **ayırt edici kısmı** koruyacak
biçimde yapılmasıdır: cilt bilgisi başlıktan ayrılıp ayrı bir üstveri alanına taşınır ve
kırpılmayan bir alanda gösterilir. Kırpma kararının önkoşulu, hangi bilginin ayırt edici
olduğunu bilmektir.

İkinci kural, kırpılan metnin tam hâline erişilebilir kalmasıdır. Kırpma bir sunum
kararıdır; kaydın erişilebilir adı kırpılmamış başlığı taşımaya devam eder.

#### Hata İletisi Üç Parçadan Oluşur

Son tablo hata iletisinin yapısını ölçüyor. İyi bir ileti üç soruyu yanıtlar: ne oldu,
neden oldu, ne yapılmalı. Üçü ayrı ayrı 525 piksellik satır sınırının altında; üçü tek bir
cümlede birleştirildiğinde 746.8 piksele çıkıyor ve iki satır oluyor.

İki satır bir kusur değildir, ama parçaların tek bir metin bloğuna sıkıştırılması kusurdur. Ayrı
tutulduklarında her parça farklı bir tipografik düzeye yerleştirilebilir: "ne oldu" kısmı
ağırlıkla öne çıkarılırken "neden" ve "ne yapılmalı" ikincil metin düzeyinde durur. Kullanıcı
sorunu tanıyorsa yalnız ilk parçayı okur; tanımıyorsa devam eder.

Üç parçanın hepsi her zaman gerekmez. "Neden" parçası, kullanıcının nedeni tahmin
edemeyeceği durumlarda yazılır; ISBN'in kontrol basamağı böyle bir durumdur. "Ne yapılmalı"
parçası ise Hata ve Uyarı Durumları dersinde konan kurala göre zorunludur: hata
kurtarılabilirse kurtarma yolu iletide bulunur.

#### Metnin Değişmeyen Kuralları

Kursun through-line'ı boyunca biriken mikro metin kararları birkaç kuralda toplanır.

**Eylem etiketi eylemi adlandırır.** "Tamam" ve "Gönder" gibi genel sözcükler, bir onay
kutusunda kullanıcının neyi onayladığını söylemez. "Ödünç al" ile "Vazgeç" çifti, hangi
düğmenin ne yapacağını metnin kendisinden okutur. Bu, aynı zamanda kullanıcının iletiyi
okumadan karar verebildiği tek kurulumdur.

**Etiket arayüzün diliyle değil kullanıcının diliyle yazılır.** "Kayıt oluştur" bir veri
tabanı eylemidir; "Kitap ekle" kullanıcının yaptığı iştir. Katalog arayüzünün sözlüğü
kütüphanenin sözlüğüdür.

**Aynı şey her yerde aynı adla anılır.** Bir yerde "ödünç alma", başka bir yerde "emanet",
üçüncü bir yerde "çıkış" yazılırsa kullanıcı üç ayrı işlem olduğunu sanır. Yinelenme ve
Tutarlılık dersinde görsel için konan kural metin için de geçerlidir.

**Yer tutucu etiket değildir.** Karşıtlık ve Erişilebilirlik dersinde hesaplanmıştı: yer
tutucu metin kontrast eşiğini geçemediği için etiket işini yapamaz. Alanın adı alanın
üstünde durur; yer tutucu yalnızca biçim örneği verir.

#### Özet

- Mikro metin bir düzen kısıtıdır; etiketin genişliği hesaplanır ve eylem şeridinin
  bütçesiyle karşılaştırılır.
- Türkçede ek ve nesne etiketi belirgin biçimde uzatır; eylem etiketlerinde kısa emir
  biçimi bir ölçü kararıdır.
- Eşit genişlikli düğmeler en geniş etikete bağlanır ve toplam genişliği belirgin biçimde
  artırır; dar şeritte karşılanamaz.
- Kırpma yalnızca bilgi kesmez, iki kaydı ekranda özdeşleştirebilir; ayırt edici bilgi
  kırpılan alandan çıkarılır ve kırpılan metnin tam hâli erişilebilir kalır.
- Hata iletisi ne olduğu, neden olduğu ve ne yapılması gerektiği parçalarından oluşur;
  parçalar ayrı tutulur ve ayrı tipografik düzeylere yerleştirilir.
- Etiket eylemi adlandırır, kullanıcının diliyle yazılır ve aynı şey her yerde aynı adla
  anılır.

#### Kurs Kapanışı

Bu kurs tek bir iddiayla açılmıştı: her görsel karar bir işlev üstlenir ve
gerekçelendirilebiliyorsa tasarımdır. Yirmi iki ders boyunca kütüphane katalog arayüzü bu
iddianın sınandığı yer oldu.

Dört konu birbirinin üzerine bindi. **Görsel İlkeler** dikkatin nasıl sıralandığını,
ögelerin nasıl gruplandığını ve tutarlılığın öğrenilebilirliği nasıl ürettiğini kurdu.
**Düzen ve Aralık** bu ilkeleri bir iskelete oturttu: ızgara, aralık ölçeği, yoğunluk
kararı ve ekran boyutuyla girdi türünün getirdiği kısıtlar. **Tipografi ve Renk** iskeletin
içine giren metni ve yüzeyi ölçülebilir hâle getirdi — yazı tipi ölçüleri, tipografik
ölçek, satır uzunluğu, renk rolleri, kontrast eşikleri ve tema dönüşümü. **Bileşen
Durumları** ise durağan görüntüyü zaman içinde davranan bir arayüze çevirdi: etkileşim
durumları, bekleme, boşluk, hata, simge ve metin.

Kurs boyunca yinelenen üç kural her konuda yeniden ortaya çıktı. Birincisi, **kritik ayrım
en az iki kanalda ifade edilir**; bu kural hiyerarşide boyut ve ağırlık, işlevsel renkte
renk ve metin, bileşen durumlarında yüzey ve konum olarak göründü. İkincisi, **karar bir
sayıya bağlandığında tartışma denetime dönüşür**; eşikler keyfî seçilse bile denetlenebilir
oldukları için işe yaradılar. Üçüncüsü, **bir sorunun en ucuz çözümü çoğu zaman öge
eklemek değil, öge silmektir**; düzey sayısını azaltmak, kenarlığı kaldırmak, düğmeyi devre
dışı bırakmak yerine nedeni yazmak bu kuralın örnekleriydi.

Kursun kapsamadığı bir şey var ve sınırı orada çizilmelidir. Buradaki bütün kararlar
"arayüz ne yapmalı" sorusunun **görsel** yanıtıydı. Arayüzün hangi görevleri desteklemesi
gerektiği, kullanıcının o görevleri hangi sırayla yaptığı ve hangi bilgiye ne zaman ihtiyaç
duyduğu bu kursun dışında kaldı. Katalog arayüzünde "arama alanı, sonuç listesi, kayıt
ayrıntısı, ödünç alma" akışı baştan verilmişti; niçin bu akış olduğu sorulmadı.

M15/K02 Kullanıcı Deneyimi ve Davranış Tasarımı bu soruyla açılır. Kullanıcı
araştırmasından tasarım kararına giden zinciri kurar: gözlem, görüşme ve anket
yöntemlerinin ne zaman seçildiğini, gerçek veriye dayalı kullanıcı profillerinin nasıl
kurulduğunu ve bilgi mimarisiyle akış tasarımının görev tamamlamayı nasıl kolaylaştırdığını
ele alır. Bu kursun ölçtüğü şey bir ekranın içiydi; sonraki kursun ölçtüğü şey ekranlar
arasındaki yol olacak.

