---
title: 'Düzen Sistemleri ve Duyarlı Tasarım'
source: 'https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim'
language: tr
updated: '2026-08-17T04:41:00+00:00'
license: 'CC BY-SA 4.0'
lessons: 23
outcomes:
    - 'Esnek kutu ve ızgara sistemleriyle iki boyutlu düzen kurar'
    - 'Duyarlı tasarımı içerik odaklı kesme noktalarıyla planlar'
    - 'Biçem mimarisini ölçeklenebilir bir yöntemle düzenler'
---

# Düzen Sistemleri ve Duyarlı Tasarım

Esnek kutu ve ızgara ile iki boyutlu yerleşim kurmak, kesme noktalarını içerikten türetmek, hareketi zamanlama ve erişilebilirlik ölçütleriyle yazmak ve biçem katmanını ölçeklenebilir bir mimariye oturtmak.

## Esnek Kutu ve Izgara

Tek eksende dağıtım yapan esnek kutu ile iki eksende hat tanımlayan ızgaranın modelleri; esneme hesabının aritmetiği, otomatik yerleştirme kuralları ve iki modelin arasındaki seçim ölçütü.

### Esnek Kutu Modeli

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/esnek-kutu-modeli

Esnek kutu bağlamının kurulması, ana ve çapraz eksenin yön bildiriminden türetilmesi, boş alanın ana eksende dağıtılması ve çapraz eksende hizalama.

CSS ile Görsel Sunum kursu normal akışın kurallarını kurdu: blok kutular blok ekseninde
yığılır, satır içi kutular satır kutularına yerleştirilir. Kutuları yan yana dizmek için o
kursta kaydırma ve konumlandırma gösterildi; ikisi de kutuyu akıştan çıkarır ve
kapsayanının yüksekliğini hesaplayamaz duruma getirir.

Bu ders, kutuları yan yana dizmek için tanımlanmış olan **düzen modelini (layout model)**
ele alır. Esnek kutu, akıştan çıkarma yoluna gitmez; kapsayanın içinde yeni bir yerleşim
kuralı kurar ve boş alanı çocuklar arasında bölüştürür.

#### Esnek Kutu Bağlamı

Bir kutuya `display: flex` bildirildiğinde kutunun **kendisi** akışta blok kutu olarak
kalır; değişen şey **çocuklarının** yerleştirilme kuralıdır. Bu kutu artık bir **esnek
kapsayıcıdır (flex container)** ve doğrudan çocukları **esnek ögelerdir (flex item)**.

Üç sonuç hemen doğar:

1. Çocukların `display` değeri **bloklaştırılır**. Bir `span` çocuk, satır içi kutu olmaktan
   çıkar ve esnek öge olur; genişlik ve dikey dış boşluk alabilir.
2. Kutular arasındaki dış boşluk **çökmez**. Akış Düzeni dersindeki çökme kuralı yalnızca
   blok düzeninde tanımlıdır; esnek kutu bağlamında iki komşu boşluk toplanır.
3. Etiketle sarılmamış metin de bir esnek öge olur. Ögeye adı olmadığı için **anonim
   kutu** denir ve seçiciyle hedeflenemez.

Üçüncü madde, düzenin bozulduğu durumlarda ilk bakılacak yerlerden biridir: kapsayıcının
içindeki bir satır sonu ve girinti boşluk üretmez, ama gerçek bir metin parçası öge sayılır.

#### İki Eksen

Esnek kutu **tek eksenli** bir modeldir. Ögeler tek bir doğrultuda dizilir ve boş alan o
doğrultuda bölüştürülür. Bu doğrultuya **ana eksen (main axis)**, ona dik olana **çapraz
eksen (cross axis)** denir.

Ana ekseni `flex-direction` belirler:

| Bildirim | Ana eksen | Ana başlangıç | Çapraz eksen |
|---|---|---|---|
| `row` | satır içi eksen | satır içi başlangıç | blok eksen |
| `row-reverse` | satır içi eksen | satır içi bitiş | blok eksen |
| `column` | blok eksen | blok başlangıç | satır içi eksen |
| `column-reverse` | blok eksen | blok bitiş | satır içi eksen |

Tablodaki adlandırma bilinçli olarak "yatay" ve "dikey" demiyor. Satır içi eksen, metnin
aktığı doğrultudur ve belgenin yazma yönüne bağlıdır; sağdan sola yazılan bir belgede
`row` yönündeki ilk öge sağda başlar. Esnek kutu bildirimleri bu doğrultuları soyut olarak
adlandırdığı için aynı biçem dosyası yön değiştiğinde de doğru çalışır.

Ana eksenin iki ucu **ana başlangıç** ve **ana bitiş**, çapraz eksenin iki ucu **çapraz
başlangıç** ve **çapraz bitiş** olarak anılır. Hizalama bildirimlerinin tamamı bu dört uca
göre okunur.

#### Boş Alanın Dağıtımı

Ögelerin ana eksendeki ölçüleri toplandığında kapsayıcıdan küçük kalıyorsa geriye **boş
alan** kalır. `justify-content` bildirimi bu alanın nereye konacağını söyler.

Aşağıdaki program, üç ögeli bir satırda altı dağıtım değerini hesaplar. Kapsayıcı 720
birim, aralar 16 birimdir.

```js
// eksen.mjs — bir esnek satirda ana eksen dagitimi ve capraz eksen hizalamasi
const KAPSAYICI = 720;
const ARALIK = 16;
const kutular = [
  { ad: "arama", ana: 180, capraz: 40 },
  { ad: "tarih", ana: 140, capraz: 32 },
  { ad: "durum", ana: 120, capraz: 56 },
];

const toplamAna = kutular.reduce((t, k) => t + k.ana, 0);
const araToplam = ARALIK * (kutular.length - 1);
const bosAlan = KAPSAYICI - toplamAna - araToplam;
console.log(`kapsayici=${KAPSAYICI}  kutular=${toplamAna}  aralik=${araToplam}  bos alan=${bosAlan}`);

// ana eksende dagitim: her deger, bos alani farkli yerlere koyar
function anaEksen(deger) {
  const n = kutular.length;
  let bas = 0, ek = 0;
  if (deger === "flex-start") { bas = 0; ek = 0; }
  if (deger === "flex-end") { bas = bosAlan; ek = 0; }
  if (deger === "center") { bas = bosAlan / 2; ek = 0; }
  if (deger === "space-between") { bas = 0; ek = bosAlan / (n - 1); }
  if (deger === "space-around") { ek = bosAlan / n; bas = ek / 2; }
  if (deger === "space-evenly") { ek = bosAlan / (n + 1); bas = ek; }
  const yerler = [];
  let x = bas;
  for (const k of kutular) {
    yerler.push({ ad: k.ad, bas: x, son: x + k.ana });
    x += k.ana + ARALIK + ek;
  }
  return yerler;
}

console.log("\n--- ana eksen (justify-content) ---");
for (const d of ["flex-start", "flex-end", "center", "space-between", "space-around", "space-evenly"]) {
  const y = anaEksen(d);
  const metin = y.map((k) => `${k.ad} ${k.bas.toFixed(1)}..${k.son.toFixed(1)}`).join("  ");
  console.log(d.padEnd(14), metin);
}

// capraz eksende hizalama: satir yuksekligi en uzun kutuyla belirlenir
const SATIR = Math.max(...kutular.map((k) => k.capraz));
console.log(`\n--- capraz eksen (align-items), satir yuksekligi=${SATIR} ---`);
for (const d of ["flex-start", "flex-end", "center", "stretch"]) {
  const y = kutular.map((k) => {
    if (d === "stretch") return `${k.ad} 0..${SATIR}`;
    const bas = d === "flex-start" ? 0 : d === "flex-end" ? SATIR - k.capraz : (SATIR - k.capraz) / 2;
    return `${k.ad} ${bas.toFixed(1)}..${(bas + k.capraz).toFixed(1)}`;
  });
  console.log(d.padEnd(12), y.join("  "));
}
```

```
kapsayici=720  kutular=440  aralik=32  bos alan=248

--- ana eksen (justify-content) ---
flex-start     arama 0.0..180.0  tarih 196.0..336.0  durum 352.0..472.0
flex-end       arama 248.0..428.0  tarih 444.0..584.0  durum 600.0..720.0
center         arama 124.0..304.0  tarih 320.0..460.0  durum 476.0..596.0
space-between  arama 0.0..180.0  tarih 320.0..460.0  durum 600.0..720.0
space-around   arama 41.3..221.3  tarih 320.0..460.0  durum 558.7..678.7
space-evenly   arama 62.0..242.0  tarih 320.0..460.0  durum 538.0..658.0

--- capraz eksen (align-items), satir yuksekligi=56 ---
flex-start   arama 0.0..40.0  tarih 0.0..32.0  durum 0.0..56.0
flex-end     arama 16.0..56.0  tarih 24.0..56.0  durum 0.0..56.0
center       arama 8.0..48.0  tarih 12.0..44.0  durum 0.0..56.0
stretch      arama 0..56  tarih 0..56  durum 0..56
```

Boş alan 248 birimdir ve altı satır bu 248 birimi altı ayrı biçimde yerleştirir.

`flex-start`, `flex-end` ve `center` boş alanı **tek parça** bırakır; yalnızca yerini
değiştirir. Kalan üçü boş alanı **böler** ve payların nereye düştüğü ayrışır:

- `space-between` iki uca hiç pay ayırmaz, alanı ögeler arasına eşit böler:
  $248 / 2 = 124$ birim ek boşluk.
- `space-around` her ögeye kendi payını verir, pay ögenin iki yanına yarımlanır:
  $248 / 3 \approx 82{,}67$; kenarlarda yarım pay $\approx 41{,}33$, aralarda iki yarım
  pay bitişir ve $\approx 82{,}67$ olur.
- `space-evenly` bütün boşlukları eşitler: $248 / 4 = 62$ birim, kenarda da arada da aynı.

Üç değerin de orta ögeyi aynı yere koyması rastlantı değil: dağıtım simetriktir ve üç öge
ortasında bir öge varsa o öge merkezde kalır. Ayrım kenarlardadır.

Sıra bir uyarı gerektirir: `flex-start` ve `flex-end` yerine `start`/`end` yazımı da
tanımlıdır ve ikisi çoğu durumda aynı sonucu verir. Aynı biçem dosyası içinde tek bir yazım
seçilir; bu kurs boyunca `flex-` önekli biçim kullanılacaktır.

#### Çapraz Eksende Hizalama

Çapraz eksende dağıtacak boş alan yoktur; her öge kendi başına hizalanır. `align-items`
kapsayıcıda tüm ögeler için, `align-self` tek bir ögede yazılır ve kapsayıcıdaki değeri
ezer.

Çıktının ikinci bloğu dört değeri gösteriyor. Satır yüksekliği en uzun ögeyle belirleniyor
(56 birim) ve diğer ikisi bu yüksekliğin içinde konumlanıyor. `flex-start` üste,
`flex-end` alta, `center` ortaya çeker.

Dördüncü değer olan `stretch` diğerlerinden farklıdır ve **varsayılandır**: öge, çapraz
eksendeki ölçüsü bildirilmemişse satırın tamamına **gerdirilir**. Çıktıda üç ögenin de
`0..56` olması budur. Ögeye çapraz eksende bir ölçü bildirilirse gerdirme uygulanmaz;
bildirilen ölçü korunur ve öge çapraz başlangıca hizalanır.

Gerdirmenin pratik değeri, yan yana duran kutuların **eşit yükseklikte** olmasıdır. Farklı
uzunlukta metin taşıyan üç kart, hiçbir yükseklik bildirilmeden aynı boyda görünür; bu,
akış düzeninde ancak sabit yükseklik yazarak elde edilebilirdi.

Beşinci bir değer olan `baseline`, ögeleri kendi ilk metin satırlarının taban çizgisine
göre hizalar. Farklı yazı boyutlarındaki başlıkları yan yana koyarken üst kenar hizası
yerine yazı hizası isteniyorsa bu değer kullanılır.

#### Aralık ve Otomatik Boşluk

Ögeler arasındaki mesafe iki yolla verilebilir. Birincisi ögelere dış boşluk yazmaktır ve
ilk ile son ögede istenmeyen kenar boşluğu üretir. İkincisi kapsayıcıya **aralık (gap)**
bildirmektir: `gap` yalnızca ögelerin **arasına** uygulanır, kenarlara dokunmaz.

Yukarıdaki hesapta ara toplamının $16 \times 2 = 32$ olması bundandır: üç öge, iki ara.

İkinci bir araç, ögeye `margin` değeri olarak `auto` yazmaktır. Otomatik dış boşluk, ana
eksendeki boş alanın **tamamını yutar** ve `justify-content` uygulanmadan önce çalışır. Bir
gezinme çubuğunda son ögeyi karşı uca itmenin en doğrudan yolu budur:

```css
/* duzen.css — 1. adım: gezinme çubuğu tek eksende */
.gezinme > ul {
  display: flex;
  gap: 1.5rem;
  align-items: center;
}

.gezinme .istasyon-durumu {
  margin-inline-start: auto;
}
```

`margin-inline-start: auto` bildirimi, durum rozetinden önceki bütün boş alanı o ögeye
verir ve rozet satırın sonuna yapışır. Aynı sonuç `justify-content: space-between` ile de
elde edilebilir, ama o bildirim **bütün** aralara pay dağıtır; burada istenen tek bir
noktada kırılmadır.

Bu dosya, CSS ile Görsel Sunum kursunda kurulan `istasyon.css` dosyasının yanına konur.
Görsel bildirimler orada kalır, düzen bildirimleri burada toplanır; ayrımın gerekçesi
kursun son konusunda ele alınacaktır.

#### Satır Kırma

Varsayılan olarak esnek ögeler **tek satırda** kalır; toplam ölçü kapsayıcıyı aşsa bile
yeni satıra geçmez, ögeler daralır. `flex-wrap: wrap` bildirimi bu davranışı değiştirir ve
sığmayan ögeler yeni bir satıra iner.

Satır kırma açıldığında üçüncü bir hizalama bildirimi anlam kazanır: `align-content`,
**satırların** çapraz eksende nasıl dağıtılacağını söyler ve değer kümesi
`justify-content` ile aynıdır. Tek satır varsa bu bildirimin etkisi yoktur.

Buradaki ayrım sık karıştırılır ve tek cümleyle özetlenebilir: `align-items` bir satırın
**içindeki ögeleri**, `align-content` kapsayıcının içindeki **satırları** hizalar.

#### Özet

- `display: flex` kutunun kendisini değil çocuklarının yerleştirilme kuralını değiştirir;
  çocuklar bloklaşır, dış boşlukları çökmez ve sarmalanmamış metin anonim öge olur.
- Esnek kutu tek eksenlidir: `flex-direction` ana ekseni belirler, çapraz eksen ona diktir
  ve iki eksen de yazma yönüne göre soyut adlandırılır.
- `justify-content` ana eksendeki boş alanı yerleştirir; `flex-start`, `flex-end` ve
  `center` alanı tek parça bırakır, `space-between`, `space-around` ve `space-evenly` onu
  farklı paylaşım kurallarıyla böler.
- Çapraz eksende varsayılan `stretch` ögeleri satır yüksekliğine gerdirir; hizalama
  değerleri yalnızca çapraz ölçü bildirildiğinde ya da gerdirme kapatıldığında görünür.
- `gap` yalnızca aralara uygulanır, `margin: auto` ana eksendeki boş alanın tamamını yutar
  ve dağıtım bildiriminden önce çalışır.

#### Sonraki Adım

Bu dersin hesabında ögelerin ana eksendeki ölçüleri sabit alındı ve boş alan hep artı
çıktı. Gerçek bir düzende ölçüler içerikten gelir ve kapsayıcı daraldığında boş alan eksiye
düşer. O durumda hangi öge ne kadar daralır, fazla alan hangi ögeye gider? Sonraki ders bu
paylaşımı yöneten üç bildirimi ve aritmetiğini ele alır.

### Esneklik Özellikleri

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/esneklik-ozellikleri

Temel ölçünün nereden geldiği, artı boş alanın büyüme katsayılarına göre bölünmesi, eksi boş alanın ölçekli daralma katsayılarıyla paylaştırılması ve en küçük ölçüye dayanan ögelerin dondurulması.

Bir önceki ders, ögelerin ana eksendeki ölçülerini sabit aldı ve boş alanı hep artı buldu.
Gerçek bir süzgeç çubuğunda ölçüler içerikten gelir; kapsayıcı daraldığında üç alanın
toplamı kapsayıcıyı aşar ve boş alan eksiye düşer.

Bu ders, o paylaşımı yöneten üç bildirimi ele alır: **temel ölçü (flex basis)**, **büyüme
katsayısı (flex grow factor)** ve **daralma katsayısı (flex shrink factor)**. Üçü birlikte
tek bir soruyu yanıtlar: kapsayıcı ile ögelerin toplamı arasındaki fark hangi ögeye ne
oranda yazılır?

#### Üç Bildirim ve Kısayolu

`flex-basis` ögenin **dağıtımdan önceki** ana eksen ölçüsüdür. Hesap bu değerden başlar;
büyüme ve daralma bu değerin üzerine eklenir ya da bu değerden düşülür.

`flex-grow` artı boş alandan alınacak payın katsayısıdır. Varsayılanı sıfırdır: bir ögeye
katsayı verilmemişse temel ölçüsünde kalır.

`flex-shrink` eksi boş alandan üstlenilecek payın katsayısıdır. Varsayılanı birdir: bir
ögeye katsayı verilmemişse yine de daralır. İki varsayılanın asimetrik olması bilinçlidir —
taşma, kendiliğinden büyümekten daha zararlıdır.

Üçü tek bildirimde yazılır ve kısayolun açılımı, tek başına yazılan değerlerden farklıdır:

```js
// kisayol.mjs — flex kisayolunun uc bildirime acilimi
const kisayollar = [
  ["flex: 1", "1 1 0%"],
  ["flex: 2", "2 1 0%"],
  ["flex: auto", "1 1 auto"],
  ["flex: initial", "0 1 auto"],
  ["flex: none", "0 0 auto"],
  ["flex: 0 0 12rem", "0 0 12rem"],
];
for (const [yazim, acilim] of kisayollar) {
  console.log(`${yazim.padEnd(16)} -> flex-grow flex-shrink flex-basis = ${acilim}`);
}
```

```
flex: 1          -> flex-grow flex-shrink flex-basis = 1 1 0%
flex: 2          -> flex-grow flex-shrink flex-basis = 2 1 0%
flex: auto       -> flex-grow flex-shrink flex-basis = 1 1 auto
flex: initial    -> flex-grow flex-shrink flex-basis = 0 1 auto
flex: none       -> flex-grow flex-shrink flex-basis = 0 0 auto
flex: 0 0 12rem  -> flex-grow flex-shrink flex-basis = 0 0 12rem
```

Dikkat edilecek yer ilk satırdır: `flex: 1` yazımı temel ölçüyü **sıfıra** çeker. Tek başına
`flex-grow: 1` yazmak temel ölçüyü `auto` bırakır. İki yazım aynı görünür ama farklı sonuç
verir; farkın sayısal karşılığı dersin sonunda hesaplanacaktır.

#### Temel Ölçü Nereden Gelir

`flex-basis: auto` bir yönlendirmedir, bir ölçü değil: öge, ana eksendeki **ölçü
bildirimine** (satır yönünde `width`, sütun yönünde `height`) bakar. O da `auto` ise ölçü
**içerikten** gelir.

Sıra şudur:

1. `flex-basis` bir uzunluk ya da yüzde taşıyorsa temel ölçü odur.
2. `flex-basis: auto` ise ana eksendeki ölçü bildirimine bakılır.
3. O da `auto` ise ögenin **en büyük içerik ölçüsü (max-content size)** kullanılır: metin
   hiç sarılmadan tek satıra yazıldığında kaplayacağı genişlik.

Temel ölçü `box-sizing` değerine tabidir. CSS ile Görsel Sunum kursunda kurulan
`border-box` bildirimi yürürlükteyse iç boşluk ve kenarlık temel ölçünün içindedir; aksi
durumda üstüne eklenir ve toplam taşar.

#### Dağıtımın Aritmetiği

Aşağıdaki program, önceki dersin süzgeç çubuğunu beş farklı kapsayıcı genişliğinde çözer.
Ögelerin temel ölçüleri, büyüme ve daralma katsayıları ile en küçük ölçüleri sabittir;
değişen tek şey kapsayıcıdır.

```js
// esneme.mjs — esnek ogelerin buyume ve daralma paylarinin hesabi
const ARALIK = 16;

// her ogenin: temel olcu, buyume katsayisi, daralma katsayisi, en kucuk olcu
const ogeler = [
  { ad: "arama", temel: 180, buyume: 2, daralma: 1, enKucuk: 120 },
  { ad: "tarih", temel: 140, buyume: 1, daralma: 1, enKucuk: 100 },
  { ad: "durum", temel: 120, buyume: 1, daralma: 3, enKucuk: 64 },
];

// CSS'in "esnek uzunluklarin cozulmesi" adimi: dondurulmus ogelerle yinelemeli dagitim
function coz(kapsayici) {
  const n = ogeler.length;
  const bosAlanHam = kapsayici - ogeler.reduce((t, o) => t + o.temel, 0) - ARALIK * (n - 1);
  const buyutuluyor = bosAlanHam > 0;

  const durum = ogeler.map((o) => ({ ...o, olcu: o.temel, donmus: false }));
  let tur = 0;

  while (true) {
    tur += 1;
    const esnek = durum.filter((d) => !d.donmus);
    if (esnek.length === 0) break;

    const kullanilan = durum.reduce((t, d) => t + (d.donmus ? d.olcu : d.temel), 0);
    const kalan = kapsayici - kullanilan - ARALIK * (n - 1);

    if (buyutuluyor) {
      const toplamKatsayi = esnek.reduce((t, d) => t + d.buyume, 0);
      if (toplamKatsayi === 0) break;
      for (const d of esnek) d.olcu = d.temel + kalan * (d.buyume / toplamKatsayi);
    } else {
      // daralmada katsayi temel olcuyle olceklenir: kucuk kutu az daralir
      const toplamAgirlik = esnek.reduce((t, d) => t + d.daralma * d.temel, 0);
      if (toplamAgirlik === 0) break;
      for (const d of esnek) d.olcu = d.temel + kalan * ((d.daralma * d.temel) / toplamAgirlik);
    }

    // en kucuk olcunun altina inen oge dondurulur ve dagitim yeniden yapilir
    const ihlal = esnek.filter((d) => d.olcu < d.enKucuk);
    if (ihlal.length === 0) break;
    for (const d of ihlal) { d.olcu = d.enKucuk; d.donmus = true; }
    if (tur > 10) break;
  }

  return { bosAlanHam, tur, durum };
}

const bicim = (x) => x.toFixed(1).padStart(6);

console.log("temel olculer:", ogeler.map((o) => `${o.ad}=${o.temel}`).join("  "), ` aralik=${ARALIK}x2`);
console.log("katsayilar:   ", ogeler.map((o) => `${o.ad} buyume=${o.buyume} daralma=${o.daralma} en kucuk=${o.enKucuk}`).join("  |  "));

for (const kapsayici of [720, 560, 480, 400, 320]) {
  const { bosAlanHam, tur, durum } = coz(kapsayici);
  const toplam = durum.reduce((t, d) => t + d.olcu, 0) + ARALIK * (ogeler.length - 1);
  console.log(`\nkapsayici=${kapsayici}  ham bos alan=${bosAlanHam}  tur=${tur}`);
  for (const d of durum) {
    const pay = d.olcu - d.temel;
    const isaret = pay >= 0 ? "+" : "-";
    console.log(`  ${d.ad.padEnd(6)} olcu=${bicim(d.olcu)}  pay=${isaret}${Math.abs(pay).toFixed(1).padStart(5)}  ${d.donmus ? "dondu (en kucuk olcu)" : ""}`);
  }
  console.log(`  toplam (aralik dahil) = ${toplam.toFixed(1)}`);
}
```

```
temel olculer: arama=180  tarih=140  durum=120  aralik=16x2
katsayilar:    arama buyume=2 daralma=1 en kucuk=120  |  tarih buyume=1 daralma=1 en kucuk=100  |  durum buyume=1 daralma=3 en kucuk=64

kapsayici=720  ham bos alan=248  tur=1
  arama  olcu= 304.0  pay=+124.0  
  tarih  olcu= 202.0  pay=+ 62.0  
  durum  olcu= 182.0  pay=+ 62.0  
  toplam (aralik dahil) = 720.0

kapsayici=560  ham bos alan=88  tur=1
  arama  olcu= 224.0  pay=+ 44.0  
  tarih  olcu= 162.0  pay=+ 22.0  
  durum  olcu= 142.0  pay=+ 22.0  
  toplam (aralik dahil) = 560.0

kapsayici=480  ham bos alan=8  tur=1
  arama  olcu= 184.0  pay=+  4.0  
  tarih  olcu= 142.0  pay=+  2.0  
  durum  olcu= 122.0  pay=+  2.0  
  toplam (aralik dahil) = 480.0

kapsayici=400  ham bos alan=-72  tur=1
  arama  olcu= 160.9  pay=- 19.1  
  tarih  olcu= 125.2  pay=- 14.8  
  durum  olcu=  81.9  pay=- 38.1  
  toplam (aralik dahil) = 400.0

kapsayici=320  ham bos alan=-152  tur=3
  arama  olcu= 124.0  pay=- 56.0  
  tarih  olcu= 100.0  pay=- 40.0  dondu (en kucuk olcu)
  durum  olcu=  64.0  pay=- 56.0  dondu (en kucuk olcu)
  toplam (aralik dahil) = 320.0
```

Beş bloğun her birinde son satır aynı doğrulamayı yapar: ölçülerin toplamı, aralıklar
dahil, kapsayıcıya eşittir. Esnek kutunun sözü budur; nasıl tuttuğu değere göre değişir.

#### Artı Boş Alan Katsayılara Bölünür

İlk üç blokta boş alan artıdır ve pay yalnızca **büyüme katsayılarının oranına** bakar.
Kapsayıcı 720 iken 248 birimlik alan $2 : 1 : 1$ oranında bölünür: $248 \times 2/4 = 124$ ve
$248 \times 1/4 = 62$.

Buradaki sık yanılgı, payın ögenin kendi ölçüsüyle orantılı sanılmasıdır. Değildir. `tarih`
ile `durum` farklı temel ölçülere sahip (140 ve 120) ama katsayıları eşit olduğu için ikisi
de 62 birim alır; aradaki 20 birimlik fark dağıtımdan sonra da korunur.

Üçüncü blok bir sınır durumunu gösterir: kapsayıcı 480 iken boş alan yalnızca 8 birimdir ve
paylar 4 ile 2'ye iner. Katsayı oranı aynı kalır, bölünen küçülür.

Katsayı toplamı birden küçükse boş alanın tamamı dağıtılmaz. Üç ögenin her birine
`flex-grow: 0.2` verilirse toplam 0,6 olur ve boş alanın yalnızca yüzde altmışı paylaşılır;
kalanı kapsayıcının sonunda boşluk olarak durur.

#### Eksi Boş Alan Ölçekli Katsayılara Bölünür

Dördüncü blokta boş alan eksidir ve hesap değişir. Daralma payı, katsayının **temel ölçüyle
çarpımına** göre belirlenir:

$$
\text{ağırlık}_i = \text{daralma}_i \times \text{temel}_i
$$

Kapsayıcı 400 iken ağırlıklar $1 \times 180 = 180$, $1 \times 140 = 140$ ve
$3 \times 120 = 360$; toplam 680. Eksi 72 birim bu oranda bölünür:
$-72 \times 360/680 \approx -38{,}1$ payı `durum` ögesine yazılır.

Ölçekleme keyfi değildir. Katsayılar ölçekten bağımsız kullanılsaydı, eşit katsayılı iki
ögeden küçük olanı büyük olanla aynı mutlak miktarda daralır ve önce sıfıra inerdi. Ölçekli
katsayı, daralmayı **yüzdesel olarak** dengeler: her öge kendi ölçüsüyle orantılı bir yük
üstlenir, katsayı bu yükü ağırlaştırır ya da hafifletir.

`durum` ögesinin daralma katsayısının üç olması bir tasarım kararıdır: durum rozeti,
metnini kaybetmeden en çok daralabilen alandır. Arama alanı için tersi geçerlidir; oraya
`flex-shrink: 0` yazılsaydı hiç daralmaz ve bütün yük diğer ikisine binerdi.

Aralık daralmaz. Hesapta 32 birim her satırda sabittir; `gap` değeri esneme dağıtımına
girmez, kapsayıcıdan baştan düşülür.

#### En Küçük Ölçü Dağıtımı Yeniden Başlatır

Son blok üç turda çözülüyor ve bunun nedeni çıktının son sütununda yazılı.

Birinci turda oranlı dağıtım `durum` ögesini 39,5 birime indirir; bu, ögenin en küçük
ölçüsü olan 64'ün altındadır. Öge o değere sabitlenir ve **dondurulur**: artık dağıtıma
katılmaz, ölçüsü kapsayıcıdan düşülür. İkinci tur kalan iki öge için tekrarlanır ve bu kez
`tarih` ögesi 98'e inerek kendi alt sınırını ihlal eder; o da donar. Üçüncü turda tek esnek
öge kalmıştır ve kalan eksiklik tümüyle ona yazılır: `arama` 124 birimde durur.

Bu yineleme, esnek kutunun tanımlı algoritmasıdır. İki sonucu vardır. Birincisi, bir ögenin
alt sınıra dayanması diğerlerinin payını **artırır**; düzenin daralma davranışı doğrusal
değildir. İkincisi, bütün ögeler alt sınıra dayanırsa toplam kapsayıcıyı aşar ve öge taşar
— esnek kutu sözünü tutamaz.

Alt sınırın nereden geldiği ayrı bir konudur. Esnek ögede `min-width` değeri `auto` ise,
sınır ögenin **en küçük içerik ölçüsüne (min-content size)** eşitlenir: metnin bölünebildiği
en dar hâl, yani en uzun sözcüğün genişliği. Uzun bir sözcük taşıyan bir ögenin beklenenden
geniş kalmasının nedeni budur ve çözümü `min-width: 0` yazarak sınırı kaldırmaktır.

#### Sıfır Temel ile İçerik Temeli

Dersin başında bırakılan fark, aynı kapsayıcıda iki kez hesaplanabilir:

```js
// temel.mjs — ayni katsayilarla iki farkli temel olcu
const KAP = 600, ARALIK = 16;
const adlar = ["arama", "tarih", "durum"];
const icerik = [180, 140, 120];
const katsayi = [2, 1, 1];

for (const kip of ["0%", "auto"]) {
  const temeller = kip === "0%" ? [0, 0, 0] : icerik;
  const bos = KAP - temeller.reduce((a, b) => a + b, 0) - ARALIK * 2;
  const toplam = katsayi.reduce((a, b) => a + b, 0);
  const olculer = temeller.map((t, i) => t + bos * (katsayi[i] / toplam));
  console.log(`flex-basis: ${kip.padEnd(5)} -> ${olculer.map((o, i) => `${adlar[i]}=${o.toFixed(1)}`).join("  ")}`);
}
```

```
flex-basis: 0%    -> arama=284.0  tarih=142.0  durum=142.0
flex-basis: auto  -> arama=244.0  tarih=172.0  durum=152.0
```

Temel ölçü sıfır olduğunda kapsayıcının **tamamı** dağıtılır ve son ölçüler yalnızca
katsayı oranını yansıtır: eşit katsayılı iki öge eşit genişliktedir. Temel ölçü `auto`
olduğunda önce içerik ölçüleri ayrılır, **artakalan** dağıtılır; içerik farkları sonuca
taşınır.

Seçim ölçütü şudur: ögeler arasında içerikten bağımsız bir oran isteniyorsa temel ölçü
sıfır, içerik farkları korunarak boş alan paylaşılacaksa `auto` yazılır. Süzgeç çubuğunda
ikincisi doğrudur; alanların doğal genişlikleri anlam taşır.

```css
/* duzen.css — 2. adım: ölçüm süzgeci */
.olcum-suzgeci {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  align-items: end;
}

.olcum-suzgeci .arama  { flex: 2 1 180px; min-inline-size: 120px; }
.olcum-suzgeci .tarih  { flex: 1 1 140px; }
.olcum-suzgeci .durum  { flex: 1 3 120px; }
```

#### Özet

- `flex-basis` dağıtımdan önceki ölçüdür; `auto` değeri ana eksendeki ölçü bildirimine, o
  da `auto` ise en büyük içerik ölçüsüne düşer.
- `flex` kısayolu tek sayıyla yazıldığında temel ölçüyü sıfıra çeker; tek başına yazılan
  `flex-grow` bildirimi temel ölçüyü `auto` bırakır ve iki yazım farklı sonuç verir.
- Artı boş alan yalnızca büyüme katsayılarının oranına göre bölünür; ögelerin temel ölçüleri
  paya girmez ve aradaki fark dağıtımdan sonra korunur.
- Eksi boş alan, katsayının temel ölçüyle çarpımına göre bölünür; ölçekleme, küçük ögelerin
  büyüklerden önce tükenmesini engeller.
- En küçük ölçüsünün altına inen öge o değerde dondurulur ve dağıtım kalan ögelerle
  yeniden çalışır; esnek ögede `min-width: auto` sınırı en küçük içerik ölçüsüdür.

#### Sonraki Adım

Esnek kutu tek eksende çalışır: bir doğrultuda dağıtır, diğerinde yalnızca hizalar. Süzgeç
çubuğu için bu yeterlidir, ama ölçüm sayfasının bütünü iki eksende hizalanmış bölgeler
ister — künye, çizelge ve yan sütun birbirinin hem satırına hem sütununa göre yerleşmelidir.
Sonraki ders, satır ve sütunları önceden tanımlayan ikinci düzen modelini kurar.

### Izgara Modeli

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/izgara-modeli

Izgara bağlamının kurulması, şerit ile hat ayrımı, pay biriminin boş alanı bölmesi, en küçük içerik ölçüsünün pay üzerindeki gizli alt sınırı ve alanların adlandırılması.

Esnek kutu tek eksende dağıtır: bir doğrultuda paylaştırır, diğerinde yalnızca hizalar. Bir
süzgeç çubuğu için bu yeterlidir. Ölçüm sayfasının bütünü ise iki eksende hizalanmış
bölgeler ister: künye, ölçüm çizelgesi ve yan sütun birbirinin hem satırına hem sütununa
göre yerleşmelidir.

Bu ders, satır ve sütunları **önceden** tanımlayan düzen modelini kurar. Esnek kutuda
yerleşim ögelerin ölçülerinden doğar; ızgarada önce boş bir çizgi düzeni tanımlanır ve
ögeler bu düzene yerleştirilir.

#### Izgara Bağlamı

Bir kutuya `display: grid` bildirildiğinde kutu **ızgara kapsayıcısı (grid container)**
olur; doğrudan çocukları **ızgara ögesi (grid item)** olarak yerleştirilir. Esnek kutudaki
gibi kapsayıcının kendisi akışta blok kutu olarak kalır, çocukların `display` değeri
bloklaşır ve dış boşlukları çökmez.

Ayrım, kapsayıcıya ne bildirildiğindedir. Esnek kutuda tek bir yön bildirilir; ızgarada iki
eksenin **şerit tanımı** yazılır:

```css
.istasyon-duzeni {
  display: grid;
  grid-template-columns: 240px 2fr 1fr;
  grid-template-rows: auto 1fr;
  gap: 24px;
}
```

#### Şerit, Hat ve Hücre

Dört kavram birbirinden ayrı tutulur.

**Şerit (grid track)** iki komşu çizgi arasındaki bant: bir sütun ya da bir satır.
Yukarıdaki bildirim üç sütun şeridi ve iki satır şeridi tanımlar.

**Hat (grid line)** şeritleri ayıran çizgi. Üç sütun şeridi **dört** hat üretir; hatlar
başlangıçtan itibaren 1'den numaralanır. Aynı hatlar sondan başlayarak eksi değerlerle de
anılır: son hat $-1$, bir öncesi $-2$. Bir ögeyi ızgaranın sonuna kadar uzatmak için şerit
sayısını bilmek gerekmez, $-1$ yeterlidir.

**Hücre (grid cell)** bir satır şeridi ile bir sütun şeridinin kesişimi; ızgaranın en küçük
birimi.

**Alan (grid area)** dört hatla sınırlanan dikdörtgen bölge; bir ya da daha çok hücreyi
kapsar. Bir ögenin yerleşimi her zaman bir alandır.

Hatlara ad verilebilir ve ad, köşeli ayraç içinde şerit tanımının arasına yazılır:

```css
.istasyon-duzeni {
  display: grid;
  grid-template-columns: [kenar-bas] 240px [icerik-bas] 2fr 1fr [kenar-son];
}
```

Adlandırılmış hat, sayı yerine kullanılabilir ve şerit sayısı değiştiğinde yerleşim
bildirimlerinin yeniden yazılmasını gereksiz kılar.

#### Pay Birimi Boş Alanı Böler

`fr`, ızgaraya özgü bir birimdir: **pay birimi (fractional unit)**. Bir uzunluk değildir;
sabit şeritler ve aralıklar düşüldükten sonra **kalan alanın** kaç paya bölüneceğini söyler.

```js
// serit.mjs — fr biriminin bos alani boluse ve gizli alt siniri
const ARALIK = 24;

// serit tanimi: sabit uzunluklar once ayrilir, kalan fr paylarina bolunur
const seritler = [
  { yazim: "240px", sabit: 240, fr: 0 },
  { yazim: "2fr", sabit: 0, fr: 2 },
  { yazim: "1fr", sabit: 0, fr: 1 },
];

const araToplam = ARALIK * (seritler.length - 1);
const sabitToplam = seritler.reduce((t, s) => t + s.sabit, 0);
const frToplam = seritler.reduce((t, s) => t + s.fr, 0);

function paylar(kapsayici) {
  const bos = kapsayici - sabitToplam - araToplam;
  return seritler.map((s) => s.sabit + (s.fr / frToplam) * bos);
}

const KAP = 960;
console.log(`grid-template-columns: ${seritler.map((s) => s.yazim).join(" ")}   gap: ${ARALIK}px   kapsayici: ${KAP}px`);
console.log(`sabit=${sabitToplam}  aralik=${araToplam}  bolunecek bos alan=${KAP - sabitToplam - araToplam}  fr toplami=${frToplam}`);
paylar(KAP).forEach((o, i) => console.log(`  ${seritler[i].yazim.padEnd(6)} -> ${o.toFixed(1)}px`));

console.log("\n--- fr, kapsayicinin degil bos alanin payidir ---");
for (const ekSabit of [0, 240, 480]) {
  const bir = (KAP - ekSabit - araToplam) / 3;
  console.log(`sabit serit ${String(ekSabit).padStart(3)}px -> 1fr = ${bir.toFixed(1)}px  (2fr = ${(2 * bir).toFixed(1)}px)`);
}

// dar kapsayicida fr payi, seridin en kucuk icerik olcusunun altina duser
const DAR = 600;
const enKucukIcerik = [90, 180, 160];   // her seridin en uzun bolunemez icerigi
console.log(`\n--- dar kapsayici (${DAR}px): 1fr = minmax(auto, 1fr) ---`);
let toplam = 0;
paylar(DAR).forEach((pay, i) => {
  const alt = seritler[i].fr > 0 ? enKucukIcerik[i] : 0;
  const uygulanan = Math.max(pay, alt);
  toplam += uygulanan;
  console.log(`  ${seritler[i].yazim.padEnd(6)} pay=${pay.toFixed(1).padStart(6)}  en kucuk icerik=${String(alt).padStart(3)}  uygulanan=${uygulanan.toFixed(1).padStart(6)}`);
});
console.log(`  seritler+aralik = ${(toplam + araToplam).toFixed(1)}px, kapsayici = ${DAR}px, tasma = ${(toplam + araToplam - DAR).toFixed(1)}px`);

console.log(`\n--- ayni kapsayicida minmax(0, 1fr) ---`);
let toplam2 = 0;
paylar(DAR).forEach((pay, i) => {
  toplam2 += pay;
  const ad = seritler[i].fr > 0 ? `minmax(0, ${seritler[i].fr}fr)` : seritler[i].yazim;
  console.log(`  ${ad.padEnd(16)} uygulanan=${pay.toFixed(1).padStart(6)}`);
});
console.log(`  seritler+aralik = ${(toplam2 + araToplam).toFixed(1)}px, tasma = ${(toplam2 + araToplam - DAR).toFixed(1)}px`);
```

```
grid-template-columns: 240px 2fr 1fr   gap: 24px   kapsayici: 960px
sabit=240  aralik=48  bolunecek bos alan=672  fr toplami=3
  240px  -> 240.0px
  2fr    -> 448.0px
  1fr    -> 224.0px

--- fr, kapsayicinin degil bos alanin payidir ---
sabit serit   0px -> 1fr = 304.0px  (2fr = 608.0px)
sabit serit 240px -> 1fr = 224.0px  (2fr = 448.0px)
sabit serit 480px -> 1fr = 144.0px  (2fr = 288.0px)

--- dar kapsayici (600px): 1fr = minmax(auto, 1fr) ---
  240px  pay= 240.0  en kucuk icerik=  0  uygulanan= 240.0
  2fr    pay= 208.0  en kucuk icerik=180  uygulanan= 208.0
  1fr    pay= 104.0  en kucuk icerik=160  uygulanan= 160.0
  seritler+aralik = 656.0px, kapsayici = 600px, tasma = 56.0px

--- ayni kapsayicida minmax(0, 1fr) ---
  240px            uygulanan= 240.0
  minmax(0, 2fr)   uygulanan= 208.0
  minmax(0, 1fr)   uygulanan= 104.0
  seritler+aralik = 600.0px, tasma = 0.0px
```

İlk blok hesabın sırasını gösteriyor: 960 birimlik kapsayıcıdan önce sabit şerit (240) ve
iki aralık (48) düşülür, kalan 672 birim üç paya bölünür. `2fr` iki pay alır (448), `1fr`
bir pay (224).

İkinci blok sık yapılan bir okuma hatasını düzeltiyor. `1fr` kapsayıcının üçte biri değildir;
**boş alanın** üçte biridir. Sabit şerit büyüdükçe aynı `1fr` bildiriminin karşılığı 304'ten 144'e
iner. Şerit tanımının kendisi değişmemiştir; bölünen değişmiştir.

Pay toplamı birden küçükse boş alanın tamamı dağıtılmaz: `0.5fr 0.5fr` yazımı boş alanın
yarısını kullanır, kalanı ızgaranın sonunda durur. Bu davranış, aynı bildirimin esnek
kutudaki karşılığıyla tutarlıdır.

#### Payın Gizli Alt Sınırı

Üçüncü blok, ızgara tanımlarındaki en sık taşma nedenini gösteriyor. Kapsayıcı 600 birime
indiğinde son şeridin payı 104 birim hesaplanır, ama şeridin içinde bölünemeyen 160 birimlik
bir içerik vardır — uzun bir ölçüm kodu ya da bir tablo. Şerit 160'ta durur ve toplam
kapsayıcıyı 56 birim aşar.

Nedeni şudur: `1fr` yazımı, tam açılımıyla `minmax(auto, 1fr)` demektir. Alt sınır `auto`
olduğunda şerit, içeriğinin **en küçük içerik ölçüsünün** altına inemez. Pay bu sınırın
altına düşerse sınır kazanır.

Dördüncü blok çözümü veriyor: alt sınır açıkça sıfıra çekilirse şerit paya uyar ve taşma
ortadan kalkar. Bedeli, içeriğin şerit içinde taşabilmesidir; taşma yönetimi CSS ile Görsel
Sunum kursunda kurulan bildirimlerle o şeritte ayrıca ele alınır.

Bu, esnek kutudaki `min-width: auto` kısıtının ızgaradaki karşılığıdır. İki modelde de
sorunun kaynağı aynıdır: içerik ölçüsü, hesaplanan paydan büyük olduğunda kısıt kazanır.

#### Yineleme ve Aralık

Aynı şerit tanımını elle tekrarlamak yerine `repeat()` yazılır. İlginç olan, yineleme
sayısının bir sayı yerine iki anahtar sözcükle verilebilmesidir: `auto-fill` ve `auto-fit`.
İkisi de kapsayıcıya sığan en çok şeridi üretir; ayrım, üretilen şeritlerin **boş kalması**
durumundadır.

```js
// yineleme.mjs — auto-fill ve auto-fit ile uretilen serit sayisi
const EN_KUCUK = 220;   // minmax(220px, 1fr)
const ARALIK = 24;
const OGE_SAYISI = 5;

// kapsayiciya sigan en cok serit: n*enKucuk + (n-1)*aralik <= genislik
function seritSayisi(genislik) {
  const n = Math.floor((genislik + ARALIK) / (EN_KUCUK + ARALIK));
  return Math.max(1, n);
}

const olcu = (genislik, n) => (genislik - ARALIK * (n - 1)) / n;

console.log("repeat(auto-fill | auto-fit, minmax(220px, 1fr))   gap: 24px   oge sayisi: 5");
console.log("genislik  serit  auto-fill olcu   dolu serit  auto-fit olcu");
for (const g of [320, 500, 760, 1000, 1240, 1600]) {
  const n = seritSayisi(g);
  const dolu = Math.min(n, OGE_SAYISI);
  const fill = olcu(g, n);
  const fit = olcu(g, dolu);
  console.log(
    String(g).padStart(8) +
    String(n).padStart(7) +
    (fill.toFixed(1) + "px").padStart(16) +
    String(dolu).padStart(12) +
    (fit.toFixed(1) + "px").padStart(15)
  );
}

console.log("\n--- sinir: serit sayisinin arttigi tam genislikler ---");
for (let n = 1; n <= 6; n++) {
  const esik = n * EN_KUCUK + (n - 1) * ARALIK;
  console.log(`${n} serit icin gereken en az genislik: ${esik}px`);
}

console.log("\n--- ayni hesap sabit serit sayisiyla: repeat(4, minmax(220px, 1fr)) ---");
for (const g of [500, 760, 1000]) {
  const n = 4;
  const pay = olcu(g, n);
  const uygulanan = Math.max(pay, EN_KUCUK);
  const tasma = uygulanan * n + ARALIK * (n - 1) - g;
  console.log(`genislik ${String(g).padStart(4)} -> pay ${pay.toFixed(1)}px, uygulanan ${uygulanan.toFixed(1)}px, tasma ${tasma.toFixed(1)}px`);
}
```

```
repeat(auto-fill | auto-fit, minmax(220px, 1fr))   gap: 24px   oge sayisi: 5
genislik  serit  auto-fill olcu   dolu serit  auto-fit olcu
     320      1         320.0px           1        320.0px
     500      2         238.0px           2        238.0px
     760      3         237.3px           3        237.3px
    1000      4         232.0px           4        232.0px
    1240      5         228.8px           5        228.8px
    1600      6         246.7px           5        300.8px

--- sinir: serit sayisinin arttigi tam genislikler ---
1 serit icin gereken en az genislik: 220px
2 serit icin gereken en az genislik: 464px
3 serit icin gereken en az genislik: 708px
4 serit icin gereken en az genislik: 952px
5 serit icin gereken en az genislik: 1196px
6 serit icin gereken en az genislik: 1440px

--- ayni hesap sabit serit sayisiyla: repeat(4, minmax(220px, 1fr)) ---
genislik  500 -> pay 107.0px, uygulanan 220.0px, tasma 452.0px
genislik  760 -> pay 172.0px, uygulanan 220.0px, tasma 192.0px
genislik 1000 -> pay 232.0px, uygulanan 232.0px, tasma 0.0px
```

`minmax(a, b)` bir şeridin alt ve üst sınırını verir: şerit `a` değerinin altına inmez, `b`
değerinin üstüne çıkmaz. `minmax(220px, 1fr)` yazımı "en az 220 birim, kalanı pay olarak
al" demektir ve yineleme sayısı bu alt sınırdan türetilir.

Şerit sayısı doğrudan çıkar: $n$ şerit için gereken en az genişlik
$n \times 220 + (n-1) \times 24$ birimdir ve kapsayıcı bu değeri aştıkça sayı artar. İkinci
blok bu eşikleri veriyor; birinci bloktaki sayılar tam olarak eşiklerle uyumludur — 1000
birimlik kapsayıcı dört şerit alır, çünkü dördüncü eşik 952'dir ve beşinci eşik 1196'dır.

Ayrım son satırda görünür. 1600 birimlik kapsayıcı altı şerit alacak kadar geniştir ama
yerleştirilecek beş öge vardır. `auto-fill` altıncı şeridi **boş olarak korur**; ögeler 246,7
birimde kalır ve sağda boş bir sütun durur. `auto-fit` boş şeridi **çökertir**; şerit ve
aralığı ortadan kalkar, beş öge kalan alanı paylaşır ve 300,8 birime çıkar.

Seçim ölçütü içerikle ilgilidir. Kart genişliğinin tutarlı kalması isteniyorsa ve ögeler
farklı sayfalarda değişiyorsa `auto-fill` doğrudur: aynı kart her sayfada aynı genişliktedir.
Ögelerin kapsayıcıyı her durumda doldurması isteniyorsa `auto-fit` doğrudur.

Üçüncü blok, sabit yineleme sayısının neden dar ekranlarda taştığını gösteriyor:
`repeat(4, …)` yazımı dört şeridi koşulsuz üretir, alt sınır 220 birimde tutar ve 500
birimlik bir kapsayıcıda 452 birim taşma verir. Yineleme anahtar sözcükleri bu koşulu
kapsayıcıdan okur; bir sonraki konuda ele alınacak ortam sorgularına gerek kalmadan şerit
sayısını uyarlarlar.

#### Alanların Adlandırılması

Şerit tanımı ızgarayı kurar; ögelerin nereye düşeceği ayrı bir bildirimdir. En okunur yolu,
alanlara ad vermek ve yerleşimi kapsayıcıda metin olarak çizmektir:

```css
/* duzen.css — 3. adım: sayfa ızgarası */
.istasyon-duzeni {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  grid-template-rows: auto auto 1fr;
  grid-template-areas:
    "kunye     kunye"
    "olcumler  yan-bilgi"
    "konum     yan-bilgi";
  gap: var(--aralik-1);
}

.kunye        { grid-area: kunye; }
.olcum-bolumu { grid-area: olcumler; }
.konum-bolumu { grid-area: konum; }
.yan-bilgi    { grid-area: yan-bilgi; }
```

Dizgilerin her satırı bir satır şeridine, her sütunu bir sütun şeridine karşılık gelir. Aynı
adın iki hücrede geçmesi, alanın o hücreleri kapsadığını söyler; `yan-bilgi` iki satır
boyunca uzanır. Ad yerine nokta yazılırsa hücre boş bırakılır.

Bu yazımın değeri, düzenin biçem dosyasında **görünür** olmasıdır: satırların hizası
ızgaranın kendisini çizer. Ögelerin belgedeki sırası ile ızgaradaki yeri birbirinden
bağımsızdır — bu ayrımın erişilebilirlik sonuçları sonraki derste ele alınacaktır.

#### Özet

- `display: grid` iki eksende şerit tanımı alır; şerit iki hat arasındaki bant, hücre iki
  şeridin kesişimi, alan ise dört hatla sınırlanan dikdörtgen bölgedir.
- Hatlar baştan 1'den, sondan $-1$'den numaralanır; ada bağlanabilir ve ad, şerit sayısı
  değiştiğinde yerleşim bildirimlerini korur.
- `fr` bir uzunluk değil pay birimidir: sabit şeritler ve aralıklar düşüldükten sonra kalan
  alanı böler, dolayısıyla kapsayıcının değil boş alanın oranını verir.
- `1fr` açılımıyla `minmax(auto, 1fr)` olduğu için şerit en küçük içerik ölçüsünün altına
  inmez; taşmayı önlemek için alt sınır `minmax(0, 1fr)` ile açıkça sıfırlanır.
- `repeat(auto-fill, …)` sığan bütün şeritleri üretir ve boş olanları korur, `auto-fit` boş
  şeritleri çökerterek kalan ögelere dağıtır.

#### Sonraki Adım

Şerit tanımı ızgarayı kurdu, adlandırılmış alanlar da yerleşimi çizdi. Ama alan tanımı
yazılmamış ögeler nereye düşer? Kapsayıcının içinde tanımlanandan çok öge varsa ne olur?
Sonraki ders, ögeleri hatlara göre açıkça yerleştiren bildirimleri ve boşta kalanları
yerleştiren otomatik algoritmayı ele alır.

### Izgara Yerleşimi

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/izgara-yerlesimi

Ögelerin hat numaralarıyla açık yerleştirilmesi, otomatik yerleştirme algoritmasının imleç kuralı, seyrek ve yoğun kiplerin farkı, örtük şeritlerin oluşumu ve görsel sıra ile belge sırasının ayrılması.

Bir önceki ders ızgarayı kurdu ve alanları adlandırdı. Adlandırılmış alan yazımı yerleşimi
okunur kılar ama her düzen için uygun değildir: sayıları önceden bilinmeyen ölçüm kartları
tek tek adlandırılamaz.

Bu ders iki soruyu yanıtlar. Bir öge hatlara göre nasıl açıkça yerleştirilir ve yerleşim
bildirimi yazılmamış ögeler hangi kurala göre dağıtılır?

#### Hat Numaralarına Çözülme

Bir ögenin yerleşimi iki eksende dört hatla verilir: `grid-column` sütun hatlarını,
`grid-row` satır hatlarını bildirir. Her biri eğik çizgiyle ayrılmış başlangıç ve bitiş
alır.

```js
// hat.mjs — grid-column yaziminin hat numaralarina cozulmesi
const SUTUN = 4;              // 4 serit -> 5 hat
const SON_HAT = SUTUN + 1;    // -1 ile anilan hat

// eksi numarali hat sondan sayilir: -1 -> SON_HAT, -2 -> SON_HAT-1
const hat = (n) => (n > 0 ? n : SON_HAT + 1 + n);

function coz(yazim) {
  const [bas, son] = yazim.split("/").map((p) => p.trim());
  let basHat = hat(Number(bas));
  let sonHat;

  if (son === undefined) sonHat = basHat + 1;                    // tek deger: bir serit
  else if (son.startsWith("span")) sonHat = basHat + Number(son.split(" ")[1]);
  else sonHat = hat(Number(son));

  if (sonHat < basHat) [basHat, sonHat] = [sonHat, basHat];      // ters yazim duzeltilir
  return { basHat, sonHat, serit: sonHat - basHat };
}

console.log(`${SUTUN} sutun serit -> hatlar 1..${SON_HAT} (ayni hatlar -${SON_HAT}..-1)`);
for (const y of ["1 / 3", "2 / -1", "1 / span 2", "-3 / -1", "3", "4 / 2"]) {
  const { basHat, sonHat, serit } = coz(y);
  console.log(`grid-column: ${y.padEnd(12)} -> hat ${basHat} ile hat ${sonHat} arasi, ${serit} serit`);
}

console.log("\n--- eksi ve arti numaralarin esitligi ---");
for (let n = 1; n <= SON_HAT; n++) {
  console.log(`hat ${n} = hat ${n - SON_HAT - 1}`);
}
```

```
4 sutun serit -> hatlar 1..5 (ayni hatlar -5..-1)
grid-column: 1 / 3        -> hat 1 ile hat 3 arasi, 2 serit
grid-column: 2 / -1       -> hat 2 ile hat 5 arasi, 3 serit
grid-column: 1 / span 2   -> hat 1 ile hat 3 arasi, 2 serit
grid-column: -3 / -1      -> hat 3 ile hat 5 arasi, 2 serit
grid-column: 3            -> hat 3 ile hat 4 arasi, 1 serit
grid-column: 4 / 2        -> hat 2 ile hat 4 arasi, 2 serit

--- eksi ve arti numaralarin esitligi ---
hat 1 = hat -5
hat 2 = hat -4
hat 3 = hat -3
hat 4 = hat -2
hat 5 = hat -1
```

Beş yazım biçimi ve tek bir kural: her bildirim iki hat numarasına çözülür ve aradaki şerit sayısı
farktır.

`2 / -1` yazımı ikinci hattan son hatta uzanır. Şerit sayısı bildirimde yazılı değildir;
ızgara altı sütuna çıkarsa aynı bildirim beş şerit kaplar. Sondan numaralama, düzenin şerit
sayısından bağımsız yazılmasını sağlar.

`span` anahtar sözcüğü hat yerine **uzunluk** bildirir: `1 / span 2` başlangıçtan iki şerit
gider. Başlangıç hattı bilinmeyen bir ögede yalnızca `span 2` yazmak da geçerlidir; o zaman
öge otomatik yerleştirilir ama iki şerit kaplar.

Son satır bir ayrıntıyı gösteriyor: bitiş hattı başlangıçtan küçükse ikisi yer değiştirir.
`4 / 2` yazımı hata değildir, ikinci ve dördüncü hatlar arasına çözülür.

Dört bildirimin kısayolu `grid-area` özelliğidir ve sırası alışılmışın dışındadır:
satır başlangıcı, sütun başlangıcı, satır bitişi, sütun bitişi. Bir önceki derste
`grid-area: kunye` yazımıyla alan adı verilmişti; aynı özellik ad yerine dört hat da alır.

#### Otomatik Yerleştirme

Yerleşim bildirimi olmayan ögeler **otomatik yerleştirme (auto-placement)** algoritmasıyla
dağıtılır. Algoritma bir imleç tutar, ögeleri belge sırasına göre gezer ve her ögeyi
imleçten itibaren sığdığı ilk boşluğa koyar.

`grid-auto-flow` bildirimi hangi eksende ilerleneceğini söyler: `row` değeri satırı
doldurup alt satıra iner, `column` değeri sütunu doldurup yandaki sütuna geçer. Üçüncü bir
değer olan `dense`, imlecin geri gitmesine izin verir.

```js
// yerlestirme.mjs — otomatik yerlestirme algoritmasinin seyrek ve yogun kipleri
const SUTUN = 3;

// yay = kac sutun serit kaplayacagi
const ogeler = [
  { ad: "sicaklik", yay: 1 },
  { ad: "nem", yay: 2 },
  { ad: "ruzgar", yay: 2 },
  { ad: "basinc", yay: 1 },
  { ad: "yagis", yay: 1 },
  { ad: "kar", yay: 3 },
  { ad: "gunes", yay: 1 },
  { ad: "toprak", yay: 2 },
];

function yerlestir(yogun) {
  const dolu = [];                       // dolu[satir][sutun] = oge adi
  const yer = () => { dolu.push(new Array(SUTUN).fill(null)); };
  const uygun = (s, c, yay) => {
    if (c + yay > SUTUN) return false;
    while (dolu.length <= s) yer();
    for (let i = 0; i < yay; i++) if (dolu[s][c + i]) return false;
    return true;
  };

  const sonuc = [];
  let imlecSatir = 0, imlecSutun = 0;

  for (const o of ogeler) {
    let s = yogun ? 0 : imlecSatir;
    let c = yogun ? 0 : imlecSutun;
    while (!uygun(s, c, o.yay)) {
      c += 1;
      if (c + o.yay > SUTUN) { c = 0; s += 1; }
    }
    while (dolu.length <= s) yer();
    for (let i = 0; i < o.yay; i++) dolu[s][c + i] = o.ad;
    sonuc.push({ ad: o.ad, satir: s + 1, sutun: c + 1, yay: o.yay });
    if (!yogun) {                        // seyrek kipte imlec geri gitmez
      imlecSatir = s;
      imlecSutun = c + o.yay;
      if (imlecSutun >= SUTUN) { imlecSatir = s + 1; imlecSutun = 0; }
    }
  }
  return { dolu, sonuc };
}

function yaz(baslik, { dolu, sonuc }) {
  console.log(`\n=== ${baslik} ===`);
  for (const y of sonuc) {
    console.log(`  ${y.ad.padEnd(9)} satir ${y.satir}, sutun ${y.sutun}..${y.sutun + y.yay - 1}  (grid-column: ${y.sutun} / span ${y.yay})`);
  }
  console.log(`  satir sayisi: ${dolu.length}`);
  const bosHucre = dolu.flat().filter((h) => h === null).length;
  console.log(`  bos hucre: ${bosHucre}`);
  for (const satir of dolu) {
    console.log("  | " + satir.map((h) => (h ?? "-").padEnd(9)).join(" | ") + " |");
  }
}

console.log(`${SUTUN} sutunluk izgara, ${ogeler.length} oge, yaylar: ${ogeler.map((o) => o.yay).join(" ")}`);
yaz("grid-auto-flow: row (seyrek, varsayilan)", yerlestir(false));
yaz("grid-auto-flow: row dense (yogun)", yerlestir(true));
```

```
3 sutunluk izgara, 8 oge, yaylar: 1 2 2 1 1 3 1 2

=== grid-auto-flow: row (seyrek, varsayilan) ===
  sicaklik  satir 1, sutun 1..1  (grid-column: 1 / span 1)
  nem       satir 1, sutun 2..3  (grid-column: 2 / span 2)
  ruzgar    satir 2, sutun 1..2  (grid-column: 1 / span 2)
  basinc    satir 2, sutun 3..3  (grid-column: 3 / span 1)
  yagis     satir 3, sutun 1..1  (grid-column: 1 / span 1)
  kar       satir 4, sutun 1..3  (grid-column: 1 / span 3)
  gunes     satir 5, sutun 1..1  (grid-column: 1 / span 1)
  toprak    satir 5, sutun 2..3  (grid-column: 2 / span 2)
  satir sayisi: 5
  bos hucre: 2
  | sicaklik  | nem       | nem       |
  | ruzgar    | ruzgar    | basinc    |
  | yagis     | -         | -         |
  | kar       | kar       | kar       |
  | gunes     | toprak    | toprak    |

=== grid-auto-flow: row dense (yogun) ===
  sicaklik  satir 1, sutun 1..1  (grid-column: 1 / span 1)
  nem       satir 1, sutun 2..3  (grid-column: 2 / span 2)
  ruzgar    satir 2, sutun 1..2  (grid-column: 1 / span 2)
  basinc    satir 2, sutun 3..3  (grid-column: 3 / span 1)
  yagis     satir 3, sutun 1..1  (grid-column: 1 / span 1)
  kar       satir 4, sutun 1..3  (grid-column: 1 / span 3)
  gunes     satir 3, sutun 2..2  (grid-column: 2 / span 1)
  toprak    satir 5, sutun 1..2  (grid-column: 1 / span 2)
  satir sayisi: 5
  bos hucre: 2
  | sicaklik  | nem       | nem       |
  | ruzgar    | ruzgar    | basinc    |
  | yagis     | gunes     | -         |
  | kar       | kar       | kar       |
  | toprak    | toprak    | -         |
```

İki harita aynı sekiz ögeyi aynı üç sütuna yerleştiriyor ve ilk altı öge için sonuç aynı.
Ayrım yedinci ögede başlar.

Seyrek kipte imleç, `kar` ögesi dördüncü satırı kapladıktan sonra beşinci satıra geçmiştir
ve **geri gitmez**. Üçüncü satırda iki hücre boş olmasına rağmen `gunes` ögesi beşinci
satıra iner. Boşluğun kalması bir kusur değil, kuralın sonucudur: belge sırası ile görsel
sıra arasındaki bağı korur.

Yoğun kipte her öge için arama ızgaranın başından yapılır. `gunes` üçüncü satırdaki boşluğa
yerleşir. `toprak` ögesi iki şerit istediği için oraya sığmaz ve beşinci satıra iner —
yoğun kip boşlukları doldurur, ama sığmayan ögeyi zorlamaz.

Sonuçta iki kipte de iki hücre boş kalır; değişen, hangi ögenin nerede olduğudur. Yoğun kip
her zaman daha az boşluk bırakmaz, yalnızca daha erken doldurur.

#### Yoğun Kipin Bedeli

Yoğun kipte `gunes` ögesi, belgede kendisinden **sonra** gelen `kar` ögesinden yukarıda
görünür. Görsel sıra ile belge sırası ayrışmıştır.

Bunun somut sonuçları vardır. Klavyeyle gezinen bir kullanıcı sekme tuşuna bastığında odak
belge sırasını izler ve ekranda yukarı geri sıçrar. Ekran okuyucu belge sırasını okur ve
görsel düzenle uyuşmaz. İki kullanıcı aynı sayfayı iki farklı sırada algılar.

Aynı uyarı `order` bildirimi, `flex-direction: row-reverse` ve açık hat yerleştirmesiyle
oluşturulan her sıra değişikliği için geçerlidir. Kural şudur: görsel sıra ile belge sırası
ayrılıyorsa, belgedeki sıranın **okuma sırası** olarak doğru olduğundan emin olunmalıdır;
görsel düzen bu sıranın üzerine kurulur, onun yerine geçmez.

Ölçüm kartları için karar kolaydır: kartların sırası ölçüm türünün önemini bildirmiyorsa
yoğun kip kullanılabilir. Sıra anlam taşıyorsa — örneğin ölçümler zaman sırasındaysa —
seyrek kip korunur.

#### Örtük Şeritler

Şerit tanımı ızgarayı sınırlar ama ögeleri sınırlamaz. Sekiz ögenin yerleşmesi için beş
satır gerekti; oysa şerit tanımında satır sayısı yazılı değildi. Tanımlanandan fazla öge
olduğunda ızgara kendiliğinden büyür ve oluşan şeritlere **örtük şerit (implicit track)**
denir.

Tanımlı olanlar **açık ızgarayı (explicit grid)**, kendiliğinden oluşanlar **örtük ızgarayı
(implicit grid)** kurar. Ayrımın iki sonucu vardır. Birincisi, örtük şeritlerin ölçüsü
`grid-template-*` bildiriminden değil `grid-auto-rows` ve `grid-auto-columns`
bildirimlerinden gelir; yazılmazsa ölçü içeriktir. İkincisi, sondan numaralama ($-1$) yalnız
açık ızgarada tanımlıdır; örtük şeritler bu numaralamaya katılmaz.

```css
/* duzen.css — 4. adım: ölçüm kartları */
.olcum-kartlari {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  grid-auto-rows: minmax(8rem, auto);
  gap: var(--aralik-0);
}

.olcum-kartlari .genis { grid-column: span 2; }

.olcum-kartlari .ozet {
  grid-column: 1 / -1;
  grid-row: 1;
}
```

`grid-auto-rows: minmax(8rem, auto)` bildirimi her örtük satıra bir alt sınır verir: kısa içerikli
kartlar da en az sekiz taban birim yüksekliğinde olur ve uzun içerikli kartlar büyüyebilir. `1 /
-1` yazımı özet kartını satırın tamamına yayar ve şerit sayısı ekran genişliğiyle değiştiğinde de
doğru kalır.

Bir uyarı: `span 2` yazan bir kart, `auto-fill` tek şerit ürettiğinde iki şerit isteyecektir.
Bu durumda öge açık ızgaranın dışına taşmaz; ızgara sağa doğru bir örtük sütun ekler ve
düzen bozulur. Kartlara `span` verilecekse şerit sayısının en az iki olduğu genişlikler
ayrıca sınırlanmalıdır — bu koşullu yazımın aracı sonraki konudaki sorgulardır.

#### Hizalama İki Düzeyde Çalışır

Izgarada hizalama iki ayrı soruya yanıt verir. Birincisi: bir öge kendi alanının içinde
nereye oturur? `justify-self` satır içi eksende, `align-self` blok eksende bunu bildirir;
kapsayıcıda yazılan `justify-items` ve `align-items` bütün ögeler için varsayılanı verir.
Varsayılan değer `stretch` olduğu için öge alanını doldurur.

İkincisi: şeritlerin toplamı kapsayıcıdan küçükse ızgaranın kendisi nereye oturur?
`justify-content` ve `align-content` bunu bildirir ve değer kümesi esnek kutudakiyle
aynıdır. Şeritler `fr` ile tanımlıysa boş alan kalmadığı için bu bildirimlerin etkisi
görünmez; sabit ölçülü şeritlerde görünür.

Esnek kutuda `justify-content` ögeleri dağıtıyordu; ızgarada şeritleri dağıtır. Aynı ad, iki
modelde farklı nesneye uygulanır ve bu ayrım karıştırıldığında bildirim etkisiz görünür.

#### Özet

- Her yerleşim bildirimi iki hat numarasına çözülür; `span` uzunluk bildirir, eksi
  numaralar sondan sayılır ve ters yazılan hatlar yer değiştirir.
- Otomatik yerleştirme, ögeleri belge sırasına göre imleçten itibaren sığdıkları ilk
  boşluğa koyar; seyrek kipte imleç geri gitmez ve boşluklar kalır.
- Yoğun kip boşlukları doldurur ama görsel sırayı belge sırasından ayırır; odak sırası ve
  ekran okuyucu belge sırasını izlemeye devam eder.
- Tanımlıdan fazla öge, ölçüsü `grid-auto-rows` ve `grid-auto-columns` bildirimlerinden
  gelen örtük şeritler üretir; sondan numaralama örtük şeritleri kapsamaz.
- Izgarada `justify-items` ve `align-items` ögeyi alanının içine, `justify-content` ve
  `align-content` şeritleri kapsayıcının içine yerleştirir.

#### Sonraki Adım

İki düzen modeli de kurulmuş oldu: biri tek eksende dağıtır, diğeri iki eksende hat tanımlar.
Aynı düzenin her ikisiyle de kurulabildiği durumlar vardır ve seçim çoğu zaman alışkanlıkla
yapılır. Sonraki ders bu seçimi ölçüte bağlar: hangi soru hangi modelle daha az bildirimle
ve daha az kırılganlıkla yanıtlanır?

### Izgara ve Esnek Kutu Seçimi

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/izgara-ve-esnek-kutu-secimi

İki düzen modelinin hangi soruyu yanıtladığı, son satır davranışının hesapla gösterilen farkı, yayılma isteğinin esnek kutuda doğurduğu maliyet ve iki modelin iç içe kullanımı.

Önceki dört ders iki düzen modelini ayrı ayrı kurdu. Esnek kutu tek eksende dağıtıyor,
ızgara iki eksende hat tanımlıyor. İkisiyle de bir kart dizisi yan yana dizilebilir, ikisiyle
de bir gezinme çubuğu kurulabilir; seçim çoğu zaman en son öğrenilen modele göre yapılır.

Bu ders seçimi alışkanlıktan çıkarıp ölçüte bağlar. Ölçüt tek bir sorudur: **düzen kararını
içerik mi veriyor, yoksa siz mi veriyorsunuz?** Sorunun yanıtı iki modelin adında değil,
hesaplarının çıktığı yerde saklıdır.

#### Ölçünün Nereden Geldiği

Esnek kutuda hesap **ögeden** başlar. Her ögenin bir temel boyu vardır; bu boylar toplanır,
kapsayıcıyla farkı alınır ve kalan alan büyüme ile daralma çarpanlarına göre bölüştürülür.
Kapsayıcı ögelerin ne istediğini sorar, sonra pazarlık yapar. Bu yüzden esnek kutuya
**içerikten düzene** çalışan model denir.

Izgarada hesap **kapsayıcıdan** başlar. Şerit tanımı, hiçbir öge yerleştirilmeden önce
hatları belirler; ögeler sonra bu hatlara oturur. Şerit ölçüsü içerikten türetilebilir
(`auto`, `min-content`, `max-content`) ama bu bir seçenektir, kuralın kendisi değildir.
Izgara **düzenden içeriğe** çalışır.

Ayrım, iki modelin hangi soruya iyi yanıt verdiğini belirler:

- Ögelerin sayısı ve ölçüsü önceden bilinmiyorsa, kalan alanı onlar arasında paylaştırmak
  gerekiyorsa — soru tek eksenlidir ve esnek kutu daha az bildirimle çözer.
- Ögelerin birbirine göre hem satırda hem sütunda hizalanması gerekiyorsa, bir ögenin
  kapladığı alan diğerinin yerini belirliyorsa — soru iki eksenlidir ve ızgara gerekir.

"Tek eksenli" ifadesi ögelerin tek satırda kaldığı anlamına gelmez. `flex-wrap: wrap`
bildirilmiş bir esnek kapsayıcı da birden çok satır üretir. Ayrım şudur: esnek kutuda her
satır **kendi içinde** hesaplanır, satırlar birbirini görmez. Izgarada bütün satırlar aynı
hat kümesini paylaşır.

#### Son Satırın Davranışı

Bu ayrımın en somut sonucu, ögeler tam bölünmediğinde ortaya çıkar. Aşağıdaki program aynı
yedi kartı aynı kapsayıcıda iki modelle yerleştirir.

```js
// secim.mjs — ayni kart dizisinin esnek kutu ve izgarayla yerlesimi
const KAPSAYICI = 980;   // px
const ARALIK = 16;       // gap
const TABAN = 200;       // flex-basis / minmax alt siniri
const OGE = 7;

// iki modelde de bir satira sigan oge sayisi ayni kuralla bulunur
const sigan = Math.max(1, Math.floor((KAPSAYICI + ARALIK) / (TABAN + ARALIK)));

// izgara: repeat(auto-fill, minmax(200px, 1fr)) -> her serit ayni olcu
const seritOlcu = (KAPSAYICI - ARALIK * (sigan - 1)) / sigan;

// esnek kutu: flex: 1 1 200px + flex-wrap: wrap -> her satir kendi icinde paylasir
function esnekSatirlar() {
  const satirlar = [];
  for (let i = 0; i < OGE; i += sigan) {
    const adet = Math.min(sigan, OGE - i);
    satirlar.push({ adet, olcu: (KAPSAYICI - ARALIK * (adet - 1)) / adet });
  }
  return satirlar;
}

const yaz = (v) => v.toFixed(1).padStart(6);

console.log(`kapsayici=${KAPSAYICI}px  gap=${ARALIK}px  taban=${TABAN}px  oge=${OGE}`);
console.log(`bir satira sigan oge: ${sigan}\n`);

console.log("--- izgara: repeat(auto-fill, minmax(200px, 1fr)) ---");
let no = 1;
for (let s = 0; s < Math.ceil(OGE / sigan); s++) {
  const adet = Math.min(sigan, OGE - s * sigan);
  let x = 0;
  const yerler = [];
  for (let i = 0; i < adet; i++) {
    yerler.push(`k${no++} ${yaz(x)}..${yaz(x + seritOlcu)}`);
    x += seritOlcu + ARALIK;
  }
  console.log(`satir ${s + 1}: ` + yerler.join("  "));
}

console.log("\n--- esnek kutu: flex: 1 1 200px; flex-wrap: wrap ---");
no = 1;
esnekSatirlar().forEach((satir, s) => {
  let x = 0;
  const yerler = [];
  for (let i = 0; i < satir.adet; i++) {
    yerler.push(`k${no++} ${yaz(x)}..${yaz(x + satir.olcu)}`);
    x += satir.olcu + ARALIK;
  }
  console.log(`satir ${s + 1}: ` + yerler.join("  "));
});

console.log("\n--- ilk sutunun sag kenari satirdan satira ---");
const ilkIzgara = seritOlcu;
esnekSatirlar().forEach((satir, s) => {
  const fark = satir.olcu - ilkIzgara;
  console.log(
    `satir ${s + 1}: izgara ${yaz(ilkIzgara)}  esnek ${yaz(satir.olcu)}  fark ${yaz(fark)}`,
  );
});
```

```
kapsayici=980px  gap=16px  taban=200px  oge=7
bir satira sigan oge: 4

--- izgara: repeat(auto-fill, minmax(200px, 1fr)) ---
satir 1: k1    0.0.. 233.0  k2  249.0.. 482.0  k3  498.0.. 731.0  k4  747.0.. 980.0
satir 2: k5    0.0.. 233.0  k6  249.0.. 482.0  k7  498.0.. 731.0

--- esnek kutu: flex: 1 1 200px; flex-wrap: wrap ---
satir 1: k1    0.0.. 233.0  k2  249.0.. 482.0  k3  498.0.. 731.0  k4  747.0.. 980.0
satir 2: k5    0.0.. 316.0  k6  332.0.. 648.0  k7  664.0.. 980.0

--- ilk sutunun sag kenari satirdan satira ---
satir 1: izgara  233.0  esnek  233.0  fark    0.0
satir 2: izgara  233.0  esnek  316.0  fark   83.0
```

İlk satırlar birebir aynı. İki model de kapsayıcıya dört kartın sığdığını aynı aritmetikle
buluyor ve dört kartı aynı yerlere koyuyor. Ayrım ikinci satırda başlıyor.

Izgarada ikinci satır **aynı hatları** kullanır. Üç kart kalmıştır ama şeritler dörttür; kartlar
ilk üç şeride oturur ve dördüncü şerit boş kalır. Kartların ölçüsü değişmez: 233 birim.

Esnek kutuda ikinci satır kendi hesabını yapar. Üç kart 980 birimi paylaşır ve her biri 316
birime çıkar. Son satır 83 birim daha geniş kartlarla dolar; birinci satırın kart kenarları
ile ikinci satırınkiler hiçbir noktada çakışmaz.

Bu bir kusur değil, iki modelin tanımının sonucudur. Hangisinin doğru olduğu ne istendiğine
bağlıdır. Kartlar bir çizelgenin görsel karşılığıysa ve sütun hizası okumayı taşıyorsa ızgara
doğrudur. Kartlar birbirinden bağımsız etiketlerse ve boş yer bırakmak istenmiyorsa esnek
kutunun davranışı istenen davranıştır.

Sık görülen bir düzeltme girişimi, son satıra "hayalet" ögeler eklemektir: sığmayan boşluğu
doldurmak için görünmez kutular. Bu, esnek kutuya ızgara işi yaptırmanın belirtisidir;
belgeye anlamı olmayan öge eklemek yerine model değiştirmek daha ucuzdur.

#### Yayılma İsteğinin Bedeli

İkinci ölçüt, bir ögenin diğerlerinden **daha geniş** olması istendiğinde ortaya çıkar. Izgara
bunu tek bildirimle yazar: `grid-column: span 2`. Esnek kutuda karşılığı bir orandır ve o
oran şerit sayısına bağlıdır.

```js
// yayilma.mjs — iki serit genisligindeki bir kartin iki modeldeki bedeli
const ARALIK = 16;
const TABAN = 200;

console.log("minmax(200px, 1fr), gap 16px — iki serit kaplayan kartin olcusu");
console.log("kapsayici  serit  serit olcu  span 2 olcu  ayni sonuc icin flex-basis");

for (const W of [640, 780, 980, 1180, 1420]) {
  const serit = Math.max(1, Math.floor((W + ARALIK) / (TABAN + ARALIK)));
  const olcu = (W - ARALIK * (serit - 1)) / serit;
  const span2 = 2 * olcu + ARALIK;         // izgarada: grid-column: span 2
  const yuzde = (span2 / W) * 100;          // esnek kutuda elle yazilacak taban
  console.log(
    `${String(W).padStart(9)}  ${String(serit).padStart(5)}  ` +
      `${olcu.toFixed(1).padStart(10)}  ${span2.toFixed(1).padStart(11)}  ` +
      `${yuzde.toFixed(2).padStart(24)}%`,
  );
}

console.log("\n--- ayni kart tek bir flex-basis degeriyle yazilirsa ---");
const SECILEN = 49.18; // 980px genisligine gore hesaplanan deger
console.log(`flex: 0 0 ${SECILEN}%  (980px icin dogru olan deger)`);
console.log("kapsayici  uygulanan olcu  o genislikte dogru olan  sapma");
for (const W of [640, 780, 980, 1180, 1420]) {
  const serit = Math.max(1, Math.floor((W + ARALIK) / (TABAN + ARALIK)));
  const olcu = (W - ARALIK * (serit - 1)) / serit;
  const dogru = 2 * olcu + ARALIK;
  const uygulanan = (W * SECILEN) / 100;
  const sapma = Math.round((uygulanan - dogru) * 10) / 10 || 0;
  console.log(
    `${String(W).padStart(9)}  ${uygulanan.toFixed(1).padStart(14)}  ` +
      `${dogru.toFixed(1).padStart(22)}  ${sapma.toFixed(1).padStart(6)}`,
  );
}
```

```
minmax(200px, 1fr), gap 16px — iki serit kaplayan kartin olcusu
kapsayici  serit  serit olcu  span 2 olcu  ayni sonuc icin flex-basis
      640      3       202.7        421.3                     65.83%
      780      3       249.3        514.7                     65.98%
      980      4       233.0        482.0                     49.18%
     1180      5       223.2        462.4                     39.19%
     1420      6       223.3        462.7                     32.58%

--- ayni kart tek bir flex-basis degeriyle yazilirsa ---
flex: 0 0 49.18%  (980px icin dogru olan deger)
kapsayici  uygulanan olcu  o genislikte dogru olan  sapma
      640           314.8                   421.3  -106.6
      780           383.6                   514.7  -131.1
      980           482.0                   482.0     0.0
     1180           580.3                   462.4   117.9
     1420           698.4                   462.7   235.7
```

Birinci tablo, "iki şerit genişliğinde" ifadesinin sabit bir oran olmadığını gösteriyor.
Şerit sayısı üçken oran yüzde 66'ya yakın, altıya çıktığında yüzde 33'e iniyor. Aynı görsel
istek, kapsayıcı genişliğine göre beş farklı sayı demek.

İkinci tablo bedeli ölçüyor. Bir genişlik seçilip oran ona göre yazıldığında, o genişlikte
sapma sıfırdır; diğer genişliklerde 100 ile 235 birim arasında değişir. Kart, dar
kapsayıcıda olması gerekenden dar, geniş kapsayıcıda olması gerekenden geniştir. Doğru
sonucu her genişlikte tutturmanın yolu, oranı genişlik aralıklarına göre yeniden yazmaktır —
yani düzen kararını ızgaranın hesapladığı şeyi elle hesaplayarak taklit etmektir.

Izgarada `span 2` bildirimi bu hesabın hiçbirini yazmaz; şerit ölçüsü ne olursa olsun iki
şerit ve aralarındaki bir aralık kadar yer kaplar.

#### Sıra, Yayılma ve Çakışma

Üçüncü ölçüt, ögelerin birbirini görüp görmediğidir.

Esnek kutuda ögeler tek boyutlu bir dizidir. Sıra `order` ile değiştirilebilir ama bir öge
başka bir ögenin üstüne gelemez, iki satırı birden kaplayamaz. Izgarada ögeler hücrelere
yerleşir; aynı hücreye iki öge yerleştirmek geçerlidir ve ikisi üst üste gelir. Bir görselin
üzerine yazı bindirmek, konumlandırma kullanmadan yalnız ızgarayla yazılabilir.

Bir başka ayrım da erişilebilirlik tarafında sürer. Izgara Yerleşimi dersinde görüldüğü gibi
görsel sıranın belge sırasından ayrılması odak sırasını bozar. Izgara bu ayrımı çok daha
kolay üretir: hat numarasıyla yerleştirme, belge sırasını tamamen yok sayar. Esnek kutuda aynı
riski yalnız `order` ve ters yönler doğurur. Modelin gücü, yanlış kullanıldığında aynı ölçüde
zarar verir.

#### İki Modelin İç İçe Kullanımı

Ölçütler karşılaştırma gibi görünse de seçim çoğu sayfada "ikisinden biri" değildir. Sayfanın
büyük bölümleri iki eksenlidir; bir bölümün içindeki düğme ve etiket dizileri tek eksenlidir.
Iç içe kullanım kuralın sonucudur.

```css
/* duzen.css — 5. adım: iki modelin iş bölümü */
.istasyon-duzeni {           /* iki eksenli: bölümler birbirine göre hizalanır */
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  grid-template-areas:
    "kunye     kunye"
    "olcumler  yan-bilgi"
    "konum     yan-bilgi";
  gap: var(--aralik-1);
}

.kunye        { grid-area: kunye; }
.olcum-bolumu { grid-area: olcumler; }
.konum-bolumu { grid-area: konum; }
.yan-bilgi    { grid-area: yan-bilgi; }

.olcum-kartlari {            /* iki eksenli: kart kenarları sütun sütun hizalanır */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--aralik-0);
}

.olcum-kartlari .ozet { grid-column: 1 / -1; }

.kart-basligi {              /* tek eksenli: başlık ve rozet aynı satırda */
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.kart-basligi .birim { margin-inline-start: auto; }
```

Üç kapsayıcı, üç ayrı soru. Sayfa düzeni bölümleri hem satırda hem sütunda hizalar: ızgara.
Kart dizisi, kartların kenarlarının satırlar arasında çakışmasını ister: ızgara. Kart
başlığındaki iki öge yalnız bir doğrultuda dizilir ve aradaki boşluk içerikten artar: esnek
kutu.

Bir sınır burada belirir. Kart başlığının içindeki `.birim` ögesi, komşu karttaki `.birim`
ögesiyle hizalanmaz; her kart kendi esnek hesabını yapar. Bu hizanın kurulması için iç
kapsayıcının dış ızgaranın hatlarını görmesi gerekir. `subgrid` değeri, iç içe bir ızgaranın
kendi şeritlerini tanımlamak yerine kapsayanın hatlarını devralmasını sağlar ve bu sorunu
çözer. Değerin tanınıp tanınmadığı bir özellik sorgusuyla sınanır; sorgu yazımı sonraki
konuda ele alınacaktır.

#### Karar Sırası

Seçim üç soruyla bitirilebilir ve sıraları önemlidir.

1. **Ögeler birbirine göre iki eksende hizalanacak mı?** Yanıt evetse ızgara. Bu soru, kalan
   ikisini gereksiz kılacak kadar belirleyicidir.
2. **Ölçüyü içerik mi belirliyor?** Ögelerin sayısı bilinmiyor, ölçüleri metnin uzunluğundan
   geliyor ve aralarındaki boşluk artan alandan dağıtılacaksa esnek kutu daha az bildirim
   ister.
3. **Bir öge diğerlerinden farklı bir alan mı kaplayacak?** Yayılma, üst üste binme ya da
   belirli bir hücreye yerleştirme isteği varsa ızgara.

Ölçütlerin hiçbiri "ızgara yeni, esnek kutu eski" biçiminde bir sıralama içermez. İki model
aynı düzen katmanının iki ayrı aracıdır ve birbirinin yerine geçmez.

#### Özet

- Esnek kutu hesabı ögeden başlatır ve her satırı kendi içinde çözer; ızgara hesabı
  kapsayıcıdan başlatır ve bütün satırlara aynı hat kümesini uygular.
- Ögeler tam bölünmediğinde iki model ayrışır: ızgarada son satırın ögeleri şerit ölçüsünü
  korur; esnek kutuda kalan ögeler satırı paylaşarak genişler.
- Bir ögenin iki şerit kaplaması ızgarada tek bildirimdir; esnek kutuda karşılığı şerit
  sayısıyla değişen bir orandır ve tek bir değerle her genişlikte doğru olmaz.
- Üst üste binme, belirli hücreye yerleştirme ve iki eksenli hizalama yalnız ızgarada
  tanımlıdır; sıra bozma riski de aynı ölçüde büyür.
- Sayfa düzeyinde ızgara, bileşen içinde esnek kutu yaygın bir iş bölümüdür; bunun nedeni
  alışkanlık değil, iki düzeyin farklı sayıda eksende karar vermesidir.

#### Sonraki Adım

Buraya kadar düzenin birimi hep bir kutu oldu: kart, bölüm, düğme. Kutular hatlara
yerleştirildi ya da eksende dağıtıldı. Ama sayfadaki metnin kendisi de düzenlenebilir bir
akıştır; uzun bir açıklama paragrafı geniş bir ekranda tek satır hâlinde uzadığında okunması
zorlaşır. Sonraki ders metin akışını kutulara bölmeden sütunlara ayıran modeli ele alır:
sütun sayısı ile sütun genişliği arasındaki ilişki nasıl kurulur ve içerik sütunlar arasında
hangi kuralla dağılır?

### Çok Sütunlu Düzen

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/cok-sutunlu-duzen

Metin akışının sütunlara bölünmesi, sütun sayısı ile sütun genişliği bildirimlerinin birlikte çözülmesi, dengeleme kuralı, kırılma denetimi ve çok sütunlu düzenin uygun olmadığı durumlar.

Buraya kadarki bütün düzenlerde birim bir kutuydu: kart, bölüm, düğme. Kutular hatlara
yerleştirildi ya da eksende dağıtıldı. Sayfadaki uzun açıklama metni ise tek bir kutudur ve
geniş bir kapsayıcıda tek bir satır dizisi olarak uzar.

Bu ders metnin kendisini bölen modeli ele alır. **Çok sütunlu düzen (multi-column layout)**,
bir kutunun içeriğini birden çok sütuna dağıtır; kutuyu bölmez, akışı böler. Ayrım küçük
görünür ama düzenin bütün davranışını belirler.

#### Akışı Bölmek, Kutuyu Bölmek Değil

Bir kutuya `columns` bildirildiğinde kutunun kendisi tek bir kutu olarak kalır. Kenarlığı,
arka planı ve dış boşluğu bütüne uygulanır. Değişen, kutunun içindeki satırların
yerleştirilmesidir: satırlar birinci sütunu doldurur, dolduğunda ikinci sütuna geçer.

Bu, esnek kutu ve ızgaradan iki noktada ayrılır. Birincisi, sütunlar birer öge değildir; seçiciyle
hedeflenemez ve sayıları belgede görünmez. İkincisi, tek bir paragraf iki sütuna **bölünebilir**:
metnin ilk üç satırı birinci sütunda, kalan iki satırı ikinci sütunda yer alabilir. Izgarada bir
öge iki şeride bölünmez, yayılır; burada içerik gerçekten parçalanır.

Bu yüzden çok sütunlu düzen bir sayfa düzeni aracı değildir. Sayfa bölümlerini yan yana
koymak için ızgara vardır; çok sütunlu düzen, tek bir sürekli metnin okunabilir genişlikte
kalması için kullanılır.

#### Sayı mı, Genişlik mi

İki bildirim vardır ve ikisi de aynı hesaba girer. `column-count` kaç sütun olacağını,
`column-width` bir sütunun **en az** ne kadar geniş olacağını söyler. `columns` kısayolu
ikisini birlikte yazar.

Hangisi yazılırsa yazılsın, kullanılan sütun sayısı ve kullanılan sütun genişliği tek bir
kuralla türetilir.

```js
// sutun.mjs — cok sutunlu duzende kullanilan sutun sayisi ve genisligi
// CSS Multi-column sozde algoritmasi: sayi ve genislikten hangisi yazilmissa
// digeri kapsayicinin genisliginden turetilir.
const GAP = 32; // column-gap: 2rem (kok 16px)

function coz(W, { genislik, sayi }) {
  let kullanilanSayi;
  if (sayi === "auto") {
    kullanilanSayi = Math.max(1, Math.floor((W + GAP) / (genislik + GAP)));
  } else if (genislik === "auto") {
    kullanilanSayi = sayi;
  } else {
    kullanilanSayi = Math.min(sayi, Math.max(1, Math.floor((W + GAP) / (genislik + GAP))));
  }
  const kullanilanGenislik = (W - GAP * (kullanilanSayi - 1)) / kullanilanSayi;
  return { sayi: kullanilanSayi, genislik: kullanilanGenislik };
}

const durumlar = [
  ["column-width: 18rem", { genislik: 288, sayi: "auto" }],
  ["column-count: 3", { genislik: "auto", sayi: 3 }],
  ["columns: 18rem 3", { genislik: 288, sayi: 3 }],
];

console.log(`column-gap: 2rem = ${GAP}px   (18rem = 288px, kok 16px)`);
for (const [ad, bildirim] of durumlar) {
  console.log(`\n--- ${ad} ---`);
  console.log("kapsayici  sutun  sutun genisligi");
  for (const W of [320, 480, 640, 800, 1024, 1280]) {
    const { sayi, genislik } = coz(W, bildirim);
    console.log(
      `${String(W).padStart(9)}  ${String(sayi).padStart(5)}  ${genislik.toFixed(1).padStart(15)}`,
    );
  }
}

// column-width bir alt sinirdir: n sutun uretildiginde kullanilan genislik
// [U, ((n+1)U + G)/n) araliginda kalir. Aralik n buyudukce daralir.
console.log("\n--- column-width bir alt sinirdir: kullanilan genisligin araligi ---");
console.log("U = 288px (18rem), G = 32px");
console.log("sutun  en dar  en genis (sinira yaklasirken)  oran");
for (let n = 1; n <= 5; n++) {
  const U = 288;
  const enDar = U;
  const enGenis = ((n + 1) * U + GAP) / n;
  console.log(
    `${String(n).padStart(5)}  ${enDar.toFixed(1).padStart(6)}  ` +
      `${enGenis.toFixed(1).padStart(28)}  ${(enGenis / enDar).toFixed(2).padStart(4)}`,
  );
}
```

```
column-gap: 2rem = 32px   (18rem = 288px, kok 16px)

--- column-width: 18rem ---
kapsayici  sutun  sutun genisligi
      320      1            320.0
      480      1            480.0
      640      2            304.0
      800      2            384.0
     1024      3            320.0
     1280      4            296.0

--- column-count: 3 ---
kapsayici  sutun  sutun genisligi
      320      3             85.3
      480      3            138.7
      640      3            192.0
      800      3            245.3
     1024      3            320.0
     1280      3            405.3

--- columns: 18rem 3 ---
kapsayici  sutun  sutun genisligi
      320      1            320.0
      480      1            480.0
      640      2            304.0
      800      2            384.0
     1024      3            320.0
     1280      3            405.3

--- column-width bir alt sinirdir: kullanilan genisligin araligi ---
U = 288px (18rem), G = 32px
sutun  en dar  en genis (sinira yaklasirken)  oran
    1   288.0                         608.0  2.11
    2   288.0                         448.0  1.56
    3   288.0                         394.7  1.37
    4   288.0                         368.0  1.28
    5   288.0                         352.0  1.22
```

Üç bildirim biçimi, üç ayrı davranış.

`column-width` tek başına yazıldığında sütun sayısı kapsayıcıdan türetilir ve üst sınırı
yoktur: kapsayıcı genişledikçe sütun eklenir. Çok geniş bir ekranda beş, altı sütun oluşması
istenmiyorsa bu bildirim tek başına yetmez.

`column-count` tek başına yazıldığında sütun sayısı sabittir ve kapsayıcı daraldığında
sütunlar birlikte daralır. Çıktının ikinci bloğu bunun sonucunu gösteriyor: 320 birimlik bir
kapsayıcıda üç sütun, her biri 85 birim. Bir sütuna on karakter sığar; metin okunamaz hâle
gelir. Bu bildirim, dar kapsayıcıya uyarlanmak için tek başına kullanılamaz.

`columns: 18rem 3` ikisini birleştirir ve şu cümleyi yazar: "en fazla üç sütun, ama her sütun
en az 18 taban birim genişliğinde olsun." Sütun sayısı iki koşulun küçüğüdür; dar kapsayıcıda
genişlik koşulu, geniş kapsayıcıda sayı koşulu bağlayıcı olur. Uygulamada aranan davranış
çoğu zaman budur.

Son blok, sıkça yanlış anlaşılan bir noktayı sayıya döküyor. `column-width` bir hedef değil,
bir **alt sınırdır**. Kapsayıcı 18 taban birimden geniş olduğu sürece kullanılan genişlik bu
değerin altına inmez, ama üstüne çıkabilir: tek sütunlu durumda iki katından fazlasına kadar.
Sütun sayısı arttıkça bu oran daralır, çünkü yeni bir sütun daha erken eklenir. Bu yüzden
`column-width` değeri, kabul edilebilir **en dar** ölçüye göre seçilir; okumak için ideal olan
ölçüye göre değil.

Kapsayıcı `column-width` değerinden dar olduğunda alt sınır uygulanamaz. Sütun sayısı bire
düşer ve genişlik kapsayıcıya eşitlenir; taşma oluşmaz.

#### İçerik Sütunlara Nasıl Dağılır

Sütunların ölçüsü belli olduğunda ikinci soru gelir: hangi satır hangi sütuna gider?
`column-fill` bunu bildirir. `balance` değeri sütun yüksekliklerini eşitlemeye çalışır, `auto`
değeri sütunları sırayla doldurur ve bunun için kutuya bir yükseklik verilmiş olmasını
gerektirir.

Dengeleme, bölünmez bloklar olmadığında yalnız bir bölmedir. Bölünmez bloklar girdiğinde
sonuç değişir.

```js
// denge.mjs — sutun dengeleme modeli: icerik sutunlara hangi yukseklikte boluniyor
// Olcu birimi satirdir. Bir blok "bolunmez" ise break-inside: avoid yazilmis demektir.
const SUTUN = 3;

const icerik = [
  { ad: "giris-p", satir: 6, bolunur: true },
  { ad: "yontem-p", satir: 5, bolunur: true },
  { ad: "olcu-listesi", satir: 7, bolunur: false },
  { ad: "ara-p", satir: 4, bolunur: true },
  { ad: "sekil", satir: 9, bolunur: false },
  { ad: "sonuc-p", satir: 8, bolunur: true },
  { ad: "not-p", satir: 3, bolunur: true },
];

// Verilen sutun yuksekligiyle icerigi sirayla sutunlara dagitir.
function paketle(H) {
  const sutunlar = [[]];
  let dolu = 0;
  const yeniSutun = () => { sutunlar.push([]); dolu = 0; };

  for (const blok of icerik) {
    if (!blok.bolunur) {
      if (dolu > 0 && dolu + blok.satir > H) yeniSutun();
      sutunlar[sutunlar.length - 1].push(`${blok.ad}(${blok.satir})`);
      dolu += blok.satir;
      continue;
    }
    let kalan = blok.satir;
    let parca = 0;
    while (kalan > 0) {
      if (dolu >= H) yeniSutun();
      const konan = Math.min(kalan, H - dolu);
      parca += 1;
      const etiket = kalan === blok.satir && konan === blok.satir
        ? `${blok.ad}(${konan})`
        : `${blok.ad}.${parca}(${konan})`;
      sutunlar[sutunlar.length - 1].push(etiket);
      dolu += konan;
      kalan -= konan;
    }
  }
  const yukseklikler = sutunlar.map((s) =>
    s.reduce((t, e) => t + Number(e.match(/\((\d+)\)/)[1]), 0));
  return { sutunlar, yukseklikler };
}

const toplam = icerik.reduce((t, b) => t + b.satir, 0);
console.log(`toplam ${toplam} satir, ${SUTUN} sutun, esit bolunme ${(toplam / SUTUN).toFixed(2)} satir`);
console.log("bolunmez bloklar: " + icerik.filter((b) => !b.bolunur).map((b) => `${b.ad}(${b.satir})`).join(", "));

// column-fill: balance — sutun sayisini asmayan en kucuk yukseklik aranir
let H = Math.ceil(toplam / SUTUN);
let sonuc = paketle(H);
while (sonuc.sutunlar.length > SUTUN) {
  H += 1;
  sonuc = paketle(H);
}
console.log(`\n--- column-fill: balance  (bulunan yukseklik: ${H} satir) ---`);
sonuc.sutunlar.forEach((s, i) => {
  console.log(`sutun ${i + 1} (${sonuc.yukseklikler[i]} satir): ${s.join(" | ")}`);
});
console.log(`en yuksek - en alcak fark: ${Math.max(...sonuc.yukseklikler) - Math.min(...sonuc.yukseklikler)} satir`);

// column-fill: auto — yukseklik disaridan verilir, sutunlar sirayla doldurulur
const SABIT = 12;
const oto = paketle(SABIT);
console.log(`\n--- column-fill: auto, block-size: ${SABIT} satir ---`);
oto.sutunlar.forEach((s, i) => {
  const tasma = i >= SUTUN ? "  <- tanimli sutun sayisini asti" : "";
  console.log(`sutun ${i + 1} (${oto.yukseklikler[i]} satir): ${s.join(" | ")}${tasma}`);
});

// bolunmez bloklar kaldirilirsa dengeleme ne kadar iyilesir
const eskiIcerik = icerik.map((b) => ({ ...b }));
for (const b of icerik) b.bolunur = true;
let H2 = Math.ceil(toplam / SUTUN);
let s2 = paketle(H2);
while (s2.sutunlar.length > SUTUN) { H2 += 1; s2 = paketle(H2); }
console.log(`\n--- ayni icerik, hicbir blokta break-inside: avoid yok ---`);
console.log(`bulunan yukseklik: ${H2} satir (bolunmez bloklarla: ${H} satir)`);
s2.sutunlar.forEach((s, i) => {
  console.log(`sutun ${i + 1} (${s2.yukseklikler[i]} satir): ${s.join(" | ")}`);
});
icerik.forEach((b, i) => { b.bolunur = eskiIcerik[i].bolunur; });
```

```
toplam 42 satir, 3 sutun, esit bolunme 14.00 satir
bolunmez bloklar: olcu-listesi(7), sekil(9)

--- column-fill: balance  (bulunan yukseklik: 18 satir) ---
sutun 1 (18 satir): giris-p(6) | yontem-p(5) | olcu-listesi(7)
sutun 2 (18 satir): ara-p(4) | sekil(9) | sonuc-p.1(5)
sutun 3 (6 satir): sonuc-p.2(3) | not-p(3)
en yuksek - en alcak fark: 12 satir

--- column-fill: auto, block-size: 12 satir ---
sutun 1 (11 satir): giris-p(6) | yontem-p(5)
sutun 2 (11 satir): olcu-listesi(7) | ara-p(4)
sutun 3 (12 satir): sekil(9) | sonuc-p.1(3)
sutun 4 (8 satir): sonuc-p.2(5) | not-p(3)  <- tanimli sutun sayisini asti

--- ayni icerik, hicbir blokta break-inside: avoid yok ---
bulunan yukseklik: 14 satir (bolunmez bloklarla: 18 satir)
sutun 1 (14 satir): giris-p(6) | yontem-p(5) | olcu-listesi.1(3)
sutun 2 (14 satir): olcu-listesi.2(4) | ara-p(4) | sekil.1(6)
sutun 3 (14 satir): sekil.2(3) | sonuc-p(8) | not-p(3)
```

Bu bir modeldir, tarayıcının çıktısı değil: satır sayıları varsayılmış ve dengeleme araması en
küçük yüksekliği bulacak biçimde yazılmıştır. Modelin gösterdiği kısıt gerçektir.

Üçüncü blok, bölünmenin serbest olduğu durumu veriyor: 42 satır üç sütuna 14, 14, 14 olarak
dağılıyor. Eşit bölünmeden başka bir sonuç yok.

Birinci blokta iki blok bölünmez işaretli. Dengeleme, üçüncü bir sütun açmadan içeriği
sığdırabilen en küçük yüksekliği arıyor ve 14 yerine 18 buluyor. Sonuçtaki dağılım 18, 18, 6;
son sütun diğerlerinin üçte biri kadar dolu. Bölünmez bir blok, kendisinden küçük bir boşluğa
sığmadığında bir sonraki sütunun başına atlar ve arkasında kullanılamayan boşluk bırakır.
Dengeleme bu boşlukları geri kazanamaz.

İkinci blok `auto` değerini gösteriyor. Yükseklik dışarıdan verilmiş, sütunlar sırayla
doluyor ve içerik üç sütuna sığmıyor: dördüncü bir sütun oluşuyor. Tanımlı sütun sayısını
aşan bu sütunlara **taşan sütun (overflow column)** denir ve kutunun sınırının dışına çıkar.
`column-fill: auto` bu yüzden yalnız yüksekliği denetlenen kutularda anlamlıdır.

#### Kırılma Denetimi

Metin sütunlar arasında bölündüğünde bazı bölünmeler istenmez: bir başlık sütunun en altında
tek başına kalmamalı, bir tablo ortadan ikiye ayrılmamalıdır. Bunu bildiren özellik ailesi
kırılma denetimidir.

`break-inside: avoid` bir kutunun içinden bölünmesini engeller. `break-before: column` ve
`break-after: column` bir kutunun önünde ya da arkasında sütun kırılmasını zorlar. Bu
bildirimler yalnız çok sütunlu düzende değil, sayfalanmış çıktıda (yazdırma) da aynı adlarla
çalışır; ikisi de içeriğin parçalara ayrıldığı bağlamlardır.

İki bildirim daha satır düzeyinde çalışır. `orphans` bir parçanın **sonunda** bırakılacak en
az satır sayısını, `widows` bir sonraki parçanın **başında** bulunacak en az satır sayısını
verir. Varsayılan değer ikisi için de 2'dir: bir paragrafın son satırı, tek başına yeni bir
sütunun başına düşmez.

Bu bildirimler istek bildirir, garanti vermez. İstek yerine getirildiğinde bir bedeli olduğu
yukarıdaki hesapta görüldü: bölünmez blok sayısı arttıkça dengeleme bozulur. Kırılma denetimi
seçici kullanılır; her bloğa `avoid` yazmak sütunları kullanılamaz hâle getirir.

#### Ayıraç ve Yayılma

İki bildirim daha görsel düzenlemeyi tamamlar.

`column-rule`, sütunların arasına bir çizgi çizer ve yazımı kenarlık kısayoluyla aynıdır:
kalınlık, biçim, renk. Bu çizgi **yer kaplamaz**; sütun aralığının ortasına, ölçüye
katılmadan çizilir. Aralık çizgiden darsa çizgi sütunların üzerine taşar.

`column-span: all`, bir ögenin bütün sütunları kesmesini sağlar. Böyle bir öge akışı ikiye böler:
kendisinden önceki içerik sütunlara dağıtılır, öge tam genişlikte yerleşir ve sonraki içerik
yeniden sütunlara dağıtılır. İki grup ayrı ayrı dengelenir. Bir bölüm başlığını sütunların
üzerinde tutmanın yolu budur.

```css
/* duzen.css — 6. adım: istasyon yöntem notları */
.istasyon-notlari {
  columns: 18rem 3;
  column-gap: var(--aralik-1);
  column-rule: 1px solid var(--cizgi);
}

.istasyon-notlari > h3 {
  column-span: all;
  margin-block-start: 0;
}

.istasyon-notlari figure,
.istasyon-notlari table,
.istasyon-notlari li {
  break-inside: avoid;
}

.istasyon-notlari p {
  orphans: 3;
  widows: 3;
}
```

Bildirim kümesi küçük ama her satırın gerekçesi ayrıdır. `columns` sütun sayısını iki koşulun
küçüğüne bağlar. `column-rule` ayrımı görünür kılar ve ölçüye karışmaz. `column-span: all`
başlığı sütunların üzerinde tutar. `break-inside: avoid` yalnız bölünmesi anlamı bozan üç öge
türüne yazılmıştır — paragraflara yazılmamıştır, çünkü paragrafın bölünmesi normaldir.
`orphans` ve `widows` değerleri varsayılanın bir üstüne çekilerek tek ve iki satırlık
artıklar engellenmiştir.

#### Nerede Uygun Değil

Çok sütunlu düzenin bir sınırı vardır ve okuma yönünden gelir. Sütunlar dikey doldurulur:
okuyucu birinci sütunun altına iner, sonra ikinci sütunun başına döner. Bu, kâğıtta doğaldır
çünkü sütunun tamamı aynı anda görünür. Ekranda sütun, görüntü alanından uzunsa okuyucu
aşağı kaydırır, sonra yukarı geri kaydırır ve bir sonraki sütuna geçer.

Kural şudur: bir sütunun yüksekliği görüntü alanını aşıyorsa çok sütunlu düzen okumayı
zorlaştırır. Kısa metinlerde — bir yöntem notu, bir tanım listesi, bir kaynakça — kazanç
gerçektir; uzun bir makalede kayıptır.

İkinci sınır, sütunların belgede karşılığı olmamasıdır. Ekran okuyucu ve klavye odağı belge
sırasını izler; sütunlar bu sırayı değiştirmediği için ek bir erişilebilirlik sorunu doğmaz.
Ancak sütun içindeki bir bağlantıya odaklanıldığında sayfanın nereye kaydırılacağı, sütun
yüksekliğine bağlı olarak beklenmedik olabilir.

#### Özet

- Çok sütunlu düzen kutuyu değil akışı böler; tek bir paragraf iki sütuna parçalanabilir,
  sütunlar ise belgede öge karşılığı olmayan yapılardır.
- Kullanılan sütun sayısı, yazılan `column-count` değeri ile kapsayıcıya sığan sütun
  sayısının küçüğüdür; kullanılan genişlik kalan alanın bu sayıya bölünmesiyle bulunur.
- `column-width` bir hedef değil alt sınırdır: kullanılan genişlik bu değerin üstüne, tek
  sütunlu durumda iki katından fazlasına çıkabilir.
- `column-fill: balance` sütun yüksekliklerini eşitlemeye çalışır; bölünmez bloklar
  dengelemeyi bozar ve son sütunun boş kalmasına yol açar.
- `column-span: all` akışı ikiye böler ve iki bölüm ayrı dengelenir; `column-rule` sütun
  aralığına yer kaplamadan çizilir.
- Bir sütunun yüksekliği görüntü alanını aşıyorsa okuyucu aşağı inip yukarı dönmek zorunda
  kalır; bu, düzenin uygun olmadığının ölçütüdür.

#### Sonraki Adım

Bu ders bir ölçütü sessizce kullandı: sütun genişliği kapsayıcıya sığmadığında sütun sayısı
düşürüldü. Karar, bir ekran genişliği listesine değil, içeriğin okunabilir kaldığı en dar
ölçüye dayanıyordu. Aynı akıl yürütme sayfanın tamamı için geçerlidir. Sonraki konu bu
akıl yürütmeyi düzenin bütününe taşır: bir düzenin hangi genişlikte değişmesi gerektiği
hangi ölçüyle bulunur ve bu karar neden ekran listelerinden değil içerikten türetilir?

## Duyarlı Tasarım

Kesme noktasının içerikten türetilmesi, ortam ve kapsayıcı sorgularının hangi soruyu sorduğu, akışkan tipografi hesabı, kaynak kümesiyle görsel seçimi ve kullanıcı tercihlerine uyan biçem.

### Duyarlı Tasarım İlkesi

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/duyarli-tasarim-ilkesi

Duyarlılığın üç bileşeni, kesme noktalarının içerikten hesapla türetilmesi, iç boyutlandırmayla kendiliğinden çözülen eşikler ve şerit tanımının eşiği nasıl kaydırdığı.

Çok sütunlu düzen dersinde bir karar sessizce verildi: sütun genişliği kapsayıcıya sığmadığında
sütun sayısı düşürüldü. Eşik, bir ekran genişliği listesinden değil, metnin okunabilir kaldığı
en dar ölçüden geldi.

Bu ders aynı akıl yürütmeyi sayfanın bütününe taşır. Bir düzenin hangi genişlikte değişmesi
gerektiği hesaplanabilir bir sorudur; yanıtı aygıt adlarında değil, içeriğin kendi
ölçülerindedir.

#### Duyarlılığın Üç Bileşeni

**Duyarlı tasarım (responsive design)** tek bir bildirimin adı değildir; üç davranışın birlikte
bulunmasıdır.

**Akışkan ızgara (fluid grid).** Düzenin ölçüleri sabit uzunluklarla değil, oranlarla ve
kalan alanla verilir. `fr`, yüzde, `minmax()` ve `auto` bu işi görür; sabit piksel genişlikler
yalnız gerçekten sabit olması gereken yerlerde kalır.

**Esnek medya.** Görsel ve gömülü içerik kapsayıcısını aşmaz; ölçüsü kendi doğal boyutu değil,
kapsayıcının bıraktığı yerdir.

**Koşullu biçem.** Yukarıdaki ikisi tek başına yetmediğinde — düzenin yapısı değişmek zorunda
olduğunda — biçem bir koşula bağlanır.

Sıralama önemlidir. İlk iki bileşen ne kadar iş görürse, üçüncüsüne o kadar az iş kalır.
Sorguyla yazılan her koşul, biçem dosyasında bakımı gereken bir daldır; iç boyutlandırmayla
çözülen her eşik ise dosyada hiç görünmez.

#### Kesme Noktası Bir Ölçüdür

**Kesme noktası (breakpoint)**, düzenin yapısının değiştiği genişliktir. Sorunun doğru
sorulması şöyledir: bu bileşen, anlamını yitirmeden ne kadar daralabilir? Yanıt bir sayıdır ve
bileşenin içeriğinden gelir.

Aşağıdaki program, ölçüm istasyonu sayfasının üç bileşeni için bu sayıyı hesaplıyor.

```js
// kesme.mjs — kesme noktalarinin icerikten turetilmesi
// Sayfanin yatay ic bosluklari her iki yanda 24px.
const IC_BOSLUK = 24 * 2;

// 1) Metin sutunu: govde yazisi 18px, ortalama karakter genisligi 0.5em -> 9px/karakter
const KARAKTER = 9;
const OLCU_ALT = 45;   // okunabilir en az karakter
const OLCU_UST = 75;   // okunabilir en cok karakter
const metinAlt = OLCU_ALT * KARAKTER;
const metinUst = OLCU_UST * KARAKTER;

// 2) Olcum kartlari: bir kartin anlamli kaldigi en dar olcu ve aralik
const KART = 200;
const KART_ARALIK = 16;
const kartEsik = (n) => n * (KART + KART_ARALIK) - KART_ARALIK;

// 3) Sayfa duzeni: ana bolum ve yan bilgi yan yana durabilmek icin kendi en dar olculeri
const ANA_EN_DAR = 480;
const YAN_EN_DAR = 260;
const SAYFA_ARALIK = 24;
const ikiSutunEsik = ANA_EN_DAR + YAN_EN_DAR + SAYFA_ARALIK;

console.log("--- 1) metin sutunu ---");
console.log(`govde yazisi 18px, 1 karakter ~ ${KARAKTER}px`);
console.log(`olcu ${OLCU_ALT}-${OLCU_UST} karakter -> sutun ${metinAlt}-${metinUst}px`);
console.log(`gorus alani karsiligi: ${metinAlt + IC_BOSLUK}-${metinUst + IC_BOSLUK}px`);
console.log(`ust sinir asildiginda satir uzar: esik ${metinUst + IC_BOSLUK}px`);

console.log("\n--- 2) olcum kartlari ---");
console.log(`kart en dar ${KART}px, aralik ${KART_ARALIK}px`);
console.log("sutun  gereken icerik alani  gorus alani esigi");
for (let n = 1; n <= 5; n++) {
  console.log(
    `${String(n).padStart(5)}  ${String(kartEsik(n)).padStart(20)}  ${String(kartEsik(n) + IC_BOSLUK).padStart(18)}`,
  );
}

console.log("\n--- 3) sayfa duzeni ---");
console.log(`ana bolum en dar ${ANA_EN_DAR}px + yan bilgi en dar ${YAN_EN_DAR}px + aralik ${SAYFA_ARALIK}px`);
console.log(`iki sutunun mumkun oldugu en dar icerik alani: ${ikiSutunEsik}px`);
console.log(`gorus alani esigi (alt sinir): ${ikiSutunEsik + IC_BOSLUK}px`);

console.log("\n--- turetilen esiklerin tamami ---");
const esikler = [
  { ad: "kart 2 sutun", px: kartEsik(2) + IC_BOSLUK, sorgu: false },
  { ad: "kart 3 sutun", px: kartEsik(3) + IC_BOSLUK, sorgu: false },
  { ad: "metin ust siniri", px: metinUst + IC_BOSLUK, sorgu: false },
  { ad: "sayfa iki sutun*", px: ikiSutunEsik + IC_BOSLUK, sorgu: true },
  { ad: "kart 4 sutun", px: kartEsik(4) + IC_BOSLUK, sorgu: false },
  { ad: "kart 5 sutun", px: kartEsik(5) + IC_BOSLUK, sorgu: false },
];
esikler.sort((a, b) => a.px - b.px);
console.log("esik(px)  bilesen              sorgu gerekiyor mu");
for (const e of esikler) {
  console.log(
    `${String(e.px).padStart(8)}  ${e.ad.padEnd(20)} ${e.sorgu ? "evet" : "hayir (ic boyutlandirma yeter)"}`,
  );
}
const yazilacak = esikler.filter((e) => e.sorgu);
console.log(`\ntoplam esik: ${esikler.length}, yazilacak sorgu: ${yazilacak.length} (${yazilacak.map((e) => e.px + "px").join(", ")})`);
console.log("* en dar olculerin toplamindan gelen alt sinir; serit tanimina gore yukari cikar");
```

```
--- 1) metin sutunu ---
govde yazisi 18px, 1 karakter ~ 9px
olcu 45-75 karakter -> sutun 405-675px
gorus alani karsiligi: 453-723px
ust sinir asildiginda satir uzar: esik 723px

--- 2) olcum kartlari ---
kart en dar 200px, aralik 16px
sutun  gereken icerik alani  gorus alani esigi
    1                   200                 248
    2                   416                 464
    3                   632                 680
    4                   848                 896
    5                  1064                1112

--- 3) sayfa duzeni ---
ana bolum en dar 480px + yan bilgi en dar 260px + aralik 24px
iki sutunun mumkun oldugu en dar icerik alani: 764px
gorus alani esigi (alt sinir): 812px

--- turetilen esiklerin tamami ---
esik(px)  bilesen              sorgu gerekiyor mu
     464  kart 2 sutun         hayir (ic boyutlandirma yeter)
     680  kart 3 sutun         hayir (ic boyutlandirma yeter)
     723  metin ust siniri     hayir (ic boyutlandirma yeter)
     812  sayfa iki sutun*     evet
     896  kart 4 sutun         hayir (ic boyutlandirma yeter)
    1112  kart 5 sutun         hayir (ic boyutlandirma yeter)

toplam esik: 6, yazilacak sorgu: 1 (812px)
* en dar olculerin toplamindan gelen alt sinir; serit tanimina gore yukari cikar
```

Üç bileşen, üç ayrı ölçü kaynağı.

Metin sütununun ölçüsü tipografiden gelir. Bir satırda 45 karakterin altına inildiğinde göz çok
sık satır atlar; 75 karakterin üstüne çıkıldığında satır sonundan sonrakinin başına dönmek
zorlaşır. On sekiz taban ölçüsündeki bir gövde yazısında karakter genişliği yaklaşık dokuz
birimdir; iki sınır 405 ve 675 birime karşılık gelir.

Kartların ölçüsü içeriğin kendisinden gelir: bir ölçüm kartında başlık, değer ve birim yan yana
okunabilmelidir ve bu 200 birimin altında bozulur. Kaç sütun sığdığı buradan çıkar.

Sayfa düzeninin ölçüsü iki bölümün ölçülerinin toplamıdır. Yan yana durabilmeleri için gereken
en dar alan, ikisinin en dar ölçüsü ile aralarındaki boşluğun toplamıdır.

Elde edilen liste altı eşik içeriyor ve hiçbiri bir aygıt adıyla anılmıyor. Sayılar tuhaf: 464,
680, 723, 812. Bu tuhaflık bir kusur değil, ölçünün gerçekten içerikten geldiğinin işaretidir.

#### Yazılmayan Eşikler

Altı eşikten yalnız biri koşullu biçem gerektiriyor. Kalan beşi, önceki derslerde kurulan
**iç boyutlandırma (intrinsic sizing)** araçlarıyla kendiliğinden karşılanır.

Kart ızgarası `repeat(auto-fill, minmax(200px, 1fr))` yazımıyla tanımlıdır. Sütun sayısı
kapsayıcı genişliğinden hesaplanır ve 464, 680, 896, 1112 eşikleri hesabın doğal sonucu olarak
geçilir. Bu eşikleri sorguyla yazmak aynı hesabı ikinci kez, elle yapmak olurdu.

Metin sütununun üst sınırı `max-inline-size` ile verilir; ölçü 675 birimi aşmaz ve kalan alan dış
boşluğa gider. Bunun için de koşul gerekmez.

Geriye sayfa düzeninin kendisi kalır. `grid-template-areas` bildirimi bir yapıdır, bir ölçü
değil; iki sütunlu yerleşimden tek sütunluya geçmek bildirimin **başka bir bildirimle
değiştirilmesini** gerektirir. Iç boyutlandırma bir ölçüyü hesaplayabilir, bir yapıyı
seçemez.

Ölçüt buradan çıkar: **ölçü değişiyorsa iç boyutlandırma; yapı değişiyorsa koşullu biçem.**

#### Şerit Tanımı Eşiği Kaydırır

Yukarıdaki 812 sayısı en dar ölçülerin toplamıdır ve bir alt sınırdır. Gerçek eşik, şeritlerin
nasıl tanımlandığına bağlıdır.

```js
// liste.mjs — esigin nereden geldigi: serit tanimina gore turetme
const IC_BOSLUK = 48;      // sayfanin iki yanindaki ic bosluk toplami
const ARALIK = 24;         // iki sutun arasindaki aralik
const ANA_EN_DAR = 480;    // ana bolumun anlamli kaldigi en dar olcu
const YAN_EN_DAR = 260;    // yan bilginin anlamli kaldigi en dar olcu

// minmax(alt, Nfr) seritlerinin olculmesi: pay alt sinirin altinda kalirsa
// serit alt sinirda dondurulur, kalan alan yeniden bolusturulur.
function seritler(gorusAlani, tanim) {
  let kalan = gorusAlani - IC_BOSLUK - ARALIK;
  const sonuc = tanim.map((t) => ({ ...t, olcu: null }));
  let degisti = true;
  while (degisti) {
    degisti = false;
    const acik = sonuc.filter((t) => t.olcu === null);
    const frToplam = acik.reduce((s, t) => s + t.fr, 0);
    for (const t of acik) {
      const pay = (kalan * t.fr) / frToplam;
      if (pay < t.alt) {
        t.olcu = t.alt;
        kalan -= t.alt;
        degisti = true;
        break;
      }
    }
    if (!degisti) for (const t of acik) t.olcu = (kalan * t.fr) / frToplam;
  }
  return sonuc;
}

const tanimlar = [
  ["minmax(0, 2fr) minmax(0, 1fr)", [{ ad: "ana", fr: 2, alt: 0 }, { ad: "yan", fr: 1, alt: 0 }]],
  ["minmax(480px, 2fr) minmax(260px, 1fr)",
    [{ ad: "ana", fr: 2, alt: ANA_EN_DAR }, { ad: "yan", fr: 1, alt: YAN_EN_DAR }]],
];

for (const [ad, tanim] of tanimlar) {
  console.log(`--- ${ad} ---`);
  console.log("gorus alani  ana sutun  yan sutun  ana yeterli  yan yeterli  toplam tasma");
  for (const W of [768, 812, 852, 900, 1024]) {
    const s = seritler(W, tanim);
    const ana = s[0].olcu, yan = s[1].olcu;
    const tasma = ana + yan + ARALIK - (W - IC_BOSLUK);
    console.log(
      `${String(W).padStart(11)}  ${ana.toFixed(1).padStart(9)}  ${yan.toFixed(1).padStart(9)}  ` +
        `${(ana >= ANA_EN_DAR ? "evet" : "HAYIR").padStart(11)}  ` +
        `${(yan >= YAN_EN_DAR ? "evet" : "HAYIR").padStart(11)}  ${tasma.toFixed(1).padStart(12)}`,
    );
  }
  // her iki serit de en dar olcusunu tasmasiz karsilayan ilk genislik
  let esik = 300;
  while (esik < 2000) {
    const s = seritler(esik, tanim);
    const tasma = s[0].olcu + s[1].olcu + ARALIK - (esik - IC_BOSLUK);
    if (s[0].olcu >= ANA_EN_DAR && s[1].olcu >= YAN_EN_DAR && tasma <= 0.001) break;
    esik += 1;
  }
  console.log(`turetilen kesme noktasi: ${esik}px\n`);
}

console.log("--- aygit adlarindan gelen bir esik ile karsilastirma ---");
const HAZIR = 768;
for (const [ad, tanim] of tanimlar) {
  const s = seritler(HAZIR, tanim);
  const tasma = s[0].olcu + s[1].olcu + ARALIK - (HAZIR - IC_BOSLUK);
  const dar = s[0].olcu < ANA_EN_DAR || s[1].olcu < YAN_EN_DAR;
  const tani = dar ? "seritler en dar olcunun altinda" : `seritler yeterli ama ${tasma.toFixed(0)}px tasma var`;
  console.log(`${HAZIR}px, ${ad}`);
  console.log(`  ana ${s[0].olcu.toFixed(1)}  yan ${s[1].olcu.toFixed(1)}  -> ${tani}`);
}
```

```
--- minmax(0, 2fr) minmax(0, 1fr) ---
gorus alani  ana sutun  yan sutun  ana yeterli  yan yeterli  toplam tasma
        768      464.0      232.0        HAYIR        HAYIR           0.0
        812      493.3      246.7         evet        HAYIR           0.0
        852      520.0      260.0         evet         evet           0.0
        900      552.0      276.0         evet         evet           0.0
       1024      634.7      317.3         evet         evet           0.0
turetilen kesme noktasi: 852px

--- minmax(480px, 2fr) minmax(260px, 1fr) ---
gorus alani  ana sutun  yan sutun  ana yeterli  yan yeterli  toplam tasma
        768      480.0      260.0         evet         evet          44.0
        812      480.0      260.0         evet         evet           0.0
        852      520.0      260.0         evet         evet           0.0
        900      552.0      276.0         evet         evet           0.0
       1024      634.7      317.3         evet         evet           0.0
turetilen kesme noktasi: 812px

--- aygit adlarindan gelen bir esik ile karsilastirma ---
768px, minmax(0, 2fr) minmax(0, 1fr)
  ana 464.0  yan 232.0  -> seritler en dar olcunun altinda
768px, minmax(480px, 2fr) minmax(260px, 1fr)
  ana 480.0  yan 260.0  -> seritler yeterli ama 44px tasma var
```

İki tanım, iki farklı eşik.

Birinci tanımda oran kilitlidir: yan bilgi her genişlikte kalanın üçte birini alır. Yan
bilginin 260 birime ulaşması için üçte birin 260 olması gerekir; eşik 852 birime çıkar. Alt
sınırların toplamı olan 812 burada yetmez, çünkü ana bölüm ihtiyacından fazlasını almaktadır.

İkinci tanımda alt sınırlar şeride yazılıdır. Şeritler önce en dar ölçülerine oturur ve artan alan
orana göre bölüşülür. Eşik alt sınırların toplamına, yani 812 birime iner. Bu tanım sayfaya 40
birim genişlik kazandırır ve kesme noktasını hesabın ilk sonucuyla aynı yere getirir.

Son blok, eşiğin dışarıdan seçilmesinin iki ayrı biçimde nasıl kırıldığını gösteriyor. Aygıt
adlarından gelen 768 birimlik eşikte birinci tanımda şeritler en dar ölçünün altında kalır: düzen
uygulanır ama içerik sıkışır. İkinci tanımda şeritler alt sınırlarını korur, ama bu kez toplamları
kapsayıcıyı 44 birim aşar: yatay taşma oluşur.

İki sonuç da aynı nedenden doğar. Eşik içerikten türetilmediğinde, ölçüler ile eşik arasındaki
bağ kopar ve hata ya sıkışma ya taşma olarak görünür.

#### Dar Taraftan Başlamak

Kesme noktası bulunduktan sonra bir yazım kararı kalır: taban biçem hangi düzeni tarif etsin?

Taban biçem **dar** düzeni tarif ediyorsa, koşul genişledikçe eklenir. Tek sütunlu yerleşim
zaten yazılıdır; koşul yalnız ikinci sütunu kurar.

Taban biçem **geniş** düzeni tarif ediyorsa, koşul daraldıkça eklenir ve her koşulun içinde
tabanın kurduğu şeyi **geri alan** bildirimler bulunur: alan adlarını sıfırlamak, şerit sayısını
bire indirmek, dış boşlukları küçültmek.

Dar taraftan başlamak, geri alma bildirimlerini ortadan kaldırır. İkinci bir gerekçe daha
vardır: içerik dar bir alanda önceliklendirilmek zorundadır. Hangi bölümün önce geleceği, hangi
bilginin taşınabilir olduğu dar düzende karara bağlanır; geniş düzen bu kararların üzerine
kurulur. Ters yönde çalışıldığında dar düzen, geniş düzenden arta kalan olur.

```css
/* duzen.css — 7. adım: taban dar düzen, tek eşik */
.istasyon-duzeni {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "kunye"
    "olcumler"
    "yan-bilgi"
    "konum";
  gap: var(--aralik-1);
}

.olcum-kartlari {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--aralik-0);
}

.istasyon-notlari { max-inline-size: 675px; }
```

Taban biçemde tek bir sorgu yoktur. Kart ızgarası genişlikle birlikte sütun kazanır, metin
sütunu üst sınırında durur, sayfa tek sütunludur. Yalnız 812 birimlik eşikte yapı değişir; o
bildirim sonraki derste yazılacaktır.

#### Görüntü Alanı Bildirimi

Bir ön koşul, bütün bu hesabın geçerli olması için gereklidir. Küçük ekranlı aygıtlarda tarayıcı,
dar ekrana göre yazılmamış sayfaları okunur kılmak için geniş bir sanal görüntü alanı varsayıp
sonucu küçülterek gösterebilir. Bu durumda CSS'in gördüğü genişlik ekranın gerçek genişliği
olmaz ve türetilen eşikler hiç tetiklenmez.

Web'in Temelleri ve HTML kursunda tanıtılan görüntü alanı meta etiketi bu varsayımı kapatır:

```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```

Bildirim, CSS'in gördüğü genişliği aygıtın kendi genişliğine eşitler. Duyarlı biçemin ilk
satırı biçem dosyasında değil, belgenin baş bölümündedir.

#### Özet

- Duyarlılık üç bileşenin birlikte bulunmasıdır: akışkan ölçüler, kapsayıcısını aşmayan medya
  ve gerektiğinde koşullu biçem.
- Kesme noktası bir aygıt genişliği değil, bileşenin anlamını yitirmeden inebildiği en dar
  ölçüden türetilen bir sayıdır; türetilen sayılar yuvarlak olmaz.
- Ölçü değişiyorsa iç boyutlandırma yeter; yapı değişiyorsa koşullu biçem gerekir. Istasyon
  sayfasında altı eşikten yalnız biri sorguya dönüşür.
- Şerit tanımı eşiği kaydırır: oran kilitli tanımda eşik 852, alt sınırları yazılı tanımda 812
  birimdir.
- Eşik dışarıdan seçildiğinde hata iki biçimde görünür — şeritler en dar ölçünün altına iner ya
  da toplamları kapsayıcıyı aşarak taşma üretir.
- Taban biçem dar düzeni tarif ettiğinde koşullar ekleme yapar; geniş düzenden başlandığında her
  koşul geri alma bildirimleri taşır.

#### Sonraki Adım

Türetilen eşik elde var: 812 birim. Geriye bu sayının biçem dosyasında nasıl yazılacağı kalıyor.
Koşul yalnız genişlikle kurulmaz; yüksekliğe, ekranın yönüne, çıktının kâğıda mı ekrana mı
gideceğine göre de biçem ayrılabilir. Sonraki ders koşullu biçemin yazımını ele alır: bir koşul
hangi özelliklere bakabilir, iki koşul nasıl birleştirilir ve iki aralığın ucundaki genişlikte
hangi kural kazanır?

### Ortam Sorguları

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/ortam-sorgulari

Ortam sorgusunun bileşenleri, kuralların basamaklamadaki yeri, iki aralığın ucundaki çakışma ve boşluk, sorgu birimlerinin kullanıcı yazı boyutuyla ilişkisi ve özellik sorguları.

Bir önceki ders türetilen eşiği verdi: 812 birim. Geriye bu sayının biçem dosyasında nasıl
yazılacağı kalıyor.

Koşullu biçemin aracı **ortam sorgusudur (media query)**. Sorgu bir soru sorar, yanıt doğruysa
içindeki kurallar hesaba katılır. Bu ders sorunun neye bakabildiğini, kuralların basamaklamada
nereye oturduğunu ve iki aralığın birleştiği noktada ne olduğunu ele alır.

#### Sorgunun Bileşenleri

Bir ortam sorgusu üç parçadan kurulur.

**Ortam türü (media type)**, çıktının hangi ortama gittiğini söyler: `screen` ekran, `print`
yazdırma ya da yazdırma önizlemesi, `all` her ikisi. Yazılmazsa `all` varsayılır.

**Ortam özelliği (media feature)**, ortamın ölçülebilir bir niteliğini sınar ve parantez içinde
yazılır. En sık kullanılanlar `width` ve `height`; bunların yanında ekranın yönünü veren
`orientation`, en boy oranını veren `aspect-ratio`, piksel yoğunluğunu veren `resolution`
bulunur. Bir başka grup, ekran ölçüsünü değil giriş yeteneğini sınar: `pointer` işaretleme
aygıtının inceliğini, `hover` üzerine gelme davranışının bulunup bulunmadığını bildirir.

**Mantıksal işleçler.** `and` iki koşulu birlikte ister, virgül ise "ya da" anlamındadır; virgülle
ayrılmış dallardan biri doğruysa sorgu doğrudur. `not` bir sorguyu tersine çevirir.

Özellik sınamasının iki yazımı vardır. Eskisi `min-` ve `max-` öneklerini kullanır ve **kapalı
aralık** üretir: `(min-width: 812px)` bildirimi "genişlik 812'ye eşit ya da büyük" demektir.
Yenisi karşılaştırma işleçlerini doğrudan yazar: `(width >= 812px)`. İkinci yazım açık aralık
da kurabilir ve iki sınırı tek koşulda birleştirebilir.

#### Değerlendirme ve Basamaklama

Sorgunun sonucu ile kuralın uygulanması iki ayrı adımdır. Sorgu doğruysa içindeki kurallar
basamaklamaya **katılır**; hangi bildirimin kazandığına basamaklama karar verir.

Burada sık yapılan bir varsayım yanlıştır: ortam sorgusu özgüllük **eklemez**. Sorgunun içindeki
`.istasyon-duzeni` seçicisi ile dışındaki `.istasyon-duzeni` seçicisi aynı özgüllüktedir; ikisi
çakıştığında sonra yazılan kazanır.

```js
// sorgu.mjs — ortam sorgusu degerlendiricisi ve basamaklama sonucu
// Desteklenen yazimlar: (min-width: Npx), (max-width: Npx), (width >= Npx),
// (Npx <= width < Mpx), (orientation: portrait|landscape), and, virgul, not.

function ozellikDegeri(ad, ortam) {
  if (ad === "width") return ortam.genislik;
  if (ad === "height") return ortam.yukseklik;
  if (ad === "orientation") return ortam.genislik >= ortam.yukseklik ? "landscape" : "portrait";
  throw new Error(`bilinmeyen ozellik: ${ad}`);
}

function ozellik(metin, ortam) {
  const s = metin.trim().replace(/^\(/, "").replace(/\)$/, "").trim();

  let m = s.match(/^(\d+(?:\.\d+)?)px\s*(<=|<)\s*([a-z-]+)\s*(<=|<)\s*(\d+(?:\.\d+)?)px$/);
  if (m) {
    const v = ozellikDegeri(m[3], ortam);
    const altOk = m[2] === "<=" ? Number(m[1]) <= v : Number(m[1]) < v;
    const ustOk = m[4] === "<=" ? v <= Number(m[5]) : v < Number(m[5]);
    return altOk && ustOk;
  }

  m = s.match(/^([a-z-]+)\s*(>=|<=|>|<)\s*(\d+(?:\.\d+)?)px$/);
  if (m) {
    const v = ozellikDegeri(m[1], ortam);
    const n = Number(m[3]);
    return m[2] === ">=" ? v >= n : m[2] === "<=" ? v <= n : m[2] === ">" ? v > n : v < n;
  }

  m = s.match(/^(min-|max-)?([a-z-]+)\s*:\s*(.+)$/);
  if (m) {
    const v = ozellikDegeri(m[2], ortam);
    const hedef = m[3].endsWith("px") ? Number(m[3].slice(0, -2)) : m[3];
    if (m[1] === "min-") return v >= hedef;
    if (m[1] === "max-") return v <= hedef;
    return v === hedef;
  }

  throw new Error(`cozulemeyen kosul: ${s}`);
}

function sorguEslesir(sorgu, ortam) {
  return sorgu.split(",").some((dal) => {
    const parcalar = dal.trim().split(/\s+and\s+/);
    const olumsuz = parcalar[0].startsWith("not ");
    parcalar[0] = parcalar[0].replace(/^not\s+/, "");
    const sonuc = parcalar.every((p) => ozellik(p, ortam));
    return olumsuz ? !sonuc : sonuc;
  });
}

const ortamlar = [
  { ad: "dar dikey", genislik: 380, yukseklik: 780 },
  { ad: "orta dikey", genislik: 720, yukseklik: 1024 },
  { ad: "esik ustu", genislik: 812, yukseklik: 1000 },
  { ad: "genis yatay", genislik: 1280, yukseklik: 800 },
  { ad: "alcak yatay", genislik: 900, yukseklik: 420 },
];

const kurallar = [
  { sorgu: null, deger: "tek sutun" },
  { sorgu: "(min-width: 812px)", deger: "iki sutun" },
  { sorgu: "(min-width: 1200px)", deger: "iki sutun + genis kenar" },
  { sorgu: "(orientation: landscape) and (max-height: 480px)", deger: "sikisik baslik" },
];

console.log("--- kurallar kaynak sirasinda, sonuncu eslesen kazanir ---");
kurallar.forEach((k, i) => console.log(`${i + 1}. ${k.sorgu ?? "(taban, sorgusuz)"} -> ${k.deger}`));

console.log("\nortam            genislik  yukseklik  eslesen kurallar  uygulanan");
for (const o of ortamlar) {
  const eslesen = kurallar
    .map((k, i) => (k.sorgu === null || sorguEslesir(k.sorgu, o) ? i + 1 : null))
    .filter((x) => x !== null);
  const kazanan = kurallar[eslesen[eslesen.length - 1] - 1].deger;
  console.log(
    `${o.ad.padEnd(15)}  ${String(o.genislik).padStart(8)}  ${String(o.yukseklik).padStart(9)}  ` +
      `${eslesen.join(",").padStart(16)}  ${kazanan}`,
  );
}

console.log("\n--- iki araligin ucundaki genislik ---");
const cift = [
  ["(max-width: 812px)", "(min-width: 812px)"],
  ["(max-width: 811.98px)", "(min-width: 812px)"],
  ["(width < 812px)", "(width >= 812px)"],
];
for (const [a, b] of cift) {
  console.log(`\n${a}  |  ${b}`);
  console.log("genislik  ilk  ikinci  durum");
  for (const g of [811, 811.99, 812, 813]) {
    const o = { genislik: g, yukseklik: 900 };
    const x = sorguEslesir(a, o);
    const y = sorguEslesir(b, o);
    const durum = x && y ? "IKISI DE eslesti" : x || y ? "tek kural" : "HICBIRI eslesmedi";
    console.log(
      `${String(g).padStart(8)}  ${String(x).padStart(5)}  ${String(y).padStart(6)}  ${durum}`,
    );
  }
}
```

```
--- kurallar kaynak sirasinda, sonuncu eslesen kazanir ---
1. (taban, sorgusuz) -> tek sutun
2. (min-width: 812px) -> iki sutun
3. (min-width: 1200px) -> iki sutun + genis kenar
4. (orientation: landscape) and (max-height: 480px) -> sikisik baslik

ortam            genislik  yukseklik  eslesen kurallar  uygulanan
dar dikey             380        780                 1  tek sutun
orta dikey            720       1024                 1  tek sutun
esik ustu             812       1000               1,2  iki sutun
genis yatay          1280        800             1,2,3  iki sutun + genis kenar
alcak yatay           900        420             1,2,4  sikisik baslik

--- iki araligin ucundaki genislik ---

(max-width: 812px)  |  (min-width: 812px)
genislik  ilk  ikinci  durum
     811   true   false  tek kural
  811.99   true   false  tek kural
     812   true    true  IKISI DE eslesti
     813  false    true  tek kural

(max-width: 811.98px)  |  (min-width: 812px)
genislik  ilk  ikinci  durum
     811   true   false  tek kural
  811.99  false   false  HICBIRI eslesmedi
     812  false    true  tek kural
     813  false    true  tek kural

(width < 812px)  |  (width >= 812px)
genislik  ilk  ikinci  durum
     811   true   false  tek kural
  811.99   true   false  tek kural
     812  false    true  tek kural
     813  false    true  tek kural
```

Birinci tablo eşleşmenin biriktiğini gösteriyor. 1280 birimlik bir ortamda üç kural birden
doğrudur; uygulanan değer, doğru olanların **sonuncusudur**. Bu, kuralların yazım sırasının
anlam taşıdığı anlamına gelir: aynı bildirimi hedefleyen sorgular dar aralıktan geniş aralığa
doğru sıralanmazsa geniş aralığın kuralı dar aralığınki tarafından ezilir.

Dördüncü kural farklı bir eksende çalışıyor. 900 birim genişliğindeki alçak bir ortamda hem
genişlik koşulu hem yön ve yükseklik koşulu doğru; sonuncusu kazanıyor. Genişlik tek ölçü
değildir: yatay tutulan ve dikeyde yeri kısıtlı bir ortamda başlığın alçalması, genişlikle
ilgisi olmayan bir karardır.

#### Aralıkların Ucu

İkinci bölüm, iki komşu aralığın birleştiği noktayı üç yazımla sınıyor.

Birinci çift klasik hatadır. `max-width: 812px` ve `min-width: 812px` yazımlarının ikisi de
812 birimde doğrudur, çünkü iki önek de **kapalı** sınır üretir. Tam o genişlikte iki kural
birden uygulanır ve sonuç kaynak sırasına kalır. Hata görünmez olabilir — sonuç zaten
istenen kural olabilir — ama bildirimler farklıysa ikisinin karışımı uygulanır.

İkinci çift, sık önerilen düzeltmeyi deniyor: üst sınırı biraz aşağı çekmek. Çakışma
kalkıyor ama yerine bir **boşluk** geliyor. 811.99 birimlik bir genişlikte hiçbir kural
eşleşmiyor. Kesirli genişlikler kuramsal bir olasılık değildir; yakınlaştırma ve
ölçeklendirme kesirli CSS piksel değerleri üretir.

Üçüncü çift, karşılaştırma yazımıyla aralığı gerçekten böler. `(width < 812px)` ve
`(width >= 812px)` birbirini dışlar ve birlikte bütün sayı doğrusunu kaplar. Ne çakışma ne
boşluk kalır.

En sağlam yol, ikinci aralığı hiç yazmamaktır. Taban biçem dar düzeni tarif ediyorsa yalnız
`(min-width: 812px)` yazılır; alt aralık için kural gerekmez ve uç noktası sorunu doğmaz.

#### Sorgudaki Birim

Uzunluk yazılan her yerde birim seçimi bir karardır. Ortam sorgusunda bu kararın alışılmadık
bir sonucu vardır: sorgudaki `em`, kök ögeye yazılan yazı boyutuna göre değil, **kullanıcının
varsayılan yazı boyutuna** göre çözülür.

```js
// birim.mjs — ortam sorgusundaki uzunluk biriminin kullanici yazi boyutuyla iliskisi
// Sorgudaki em, kok ogeye yazilan font-size'a degil, kullanicinin varsayilan
// yazi boyutuna gore cozulur.

// 16px varsayilanda olculen icerik gereksinimleri
const ANA = 480, YAN = 260, ARALIK = 24, IC_BOSLUK = 48;
const TABAN = 16;
const GEREKEN_16 = ANA + YAN + ARALIK + IC_BOSLUK;

const emEsik = GEREKEN_16 / TABAN;   // ayni esigin em karsiligi
const pxEsik = GEREKEN_16;

console.log(`16px varsayilanda iki sutun icin gereken genislik: ${GEREKEN_16}px`);
console.log(`px yazilirsa: (min-width: ${pxEsik}px)`);
console.log(`em yazilirsa: (min-width: ${emEsik}em)\n`);

console.log("kullanici  gereken   px sorgusu  em sorgusu  px ile sapma  em ile sapma");
console.log("varsayilan genislik  tetiklenir  tetiklenir");
for (const d of [12, 16, 20, 24, 32]) {
  const gereken = (GEREKEN_16 * d) / TABAN;   // butun olculer rem tabanli oldugu icin olceklenir
  const pxTetik = pxEsik;
  const emTetik = emEsik * d;
  console.log(
    `${String(d + "px").padStart(10)}  ${gereken.toFixed(0).padStart(8)}  ` +
      `${pxTetik.toFixed(0).padStart(10)}  ${emTetik.toFixed(0).padStart(10)}  ` +
      `${(pxTetik - gereken).toFixed(0).padStart(12)}  ${(emTetik - gereken).toFixed(0).padStart(12)}`,
  );
}

console.log("\n--- 20px varsayilanda 812px genisliginde ne oluyor ---");
const d = 20;
const olcek = d / TABAN;
const alan = 812 - IC_BOSLUK * olcek - ARALIK * olcek;
const ana = (alan * 2) / 3, yan = alan / 3;
console.log(`ana bolum: ${ana.toFixed(1)}px  (gereken ${(ANA * olcek).toFixed(1)}px)`);
console.log(`yan bilgi: ${yan.toFixed(1)}px  (gereken ${(YAN * olcek).toFixed(1)}px)`);
console.log(`px sorgusu bu genislikte iki sutunu acar; em sorgusu ${(emEsik * d).toFixed(0)}px degerine kadar beklemis olur`);
```

```
16px varsayilanda iki sutun icin gereken genislik: 812px
px yazilirsa: (min-width: 812px)
em yazilirsa: (min-width: 50.75em)

kullanici  gereken   px sorgusu  em sorgusu  px ile sapma  em ile sapma
varsayilan genislik  tetiklenir  tetiklenir
      12px       609         812         609           203             0
      16px       812         812         812             0             0
      20px      1015         812        1015          -203             0
      24px      1218         812        1218          -406             0
      32px      1624         812        1624          -812             0

--- 20px varsayilanda 812px genisliginde ne oluyor ---
ana bolum: 481.3px  (gereken 600.0px)
yan bilgi: 240.7px  (gereken 325.0px)
px sorgusu bu genislikte iki sutunu acar; em sorgusu 1015px degerine kadar beklemis olur
```

Sağdaki iki sütun karşılaştırmayı tek başına yapıyor. Kullanıcı varsayılan yazı boyutunu
değiştirdiğinde bütün ölçüler taban birime bağlı olduğu için birlikte büyür; iki sütunun
gerektirdiği genişlik de büyür. Piksel yazılan sorgu bu değişimi göremez ve eşiği yerinde
bırakır: 20 birimlik bir varsayılanda düzen, ihtiyacın 203 birim altında iki sütuna geçer.
Taban birim yazılan sorgu ihtiyaçla birlikte kayar ve sapma her satırda sıfır kalır.

Son blok, sapmanın somut karşılığını veriyor: eşik erken tetiklendiğinde ana bölüm 481, yan
bilgi 241 birime düşer; oysa büyütülmüş yazıyla aynı içeriğin 600 ve 325 birime ihtiyacı
vardır. Yazı büyütmek, sayfayı okunur kılmak için yapılan bir ayardır; düzen bu ayarı ölçeğe
katmazsa ayarın kendisi sonucu bozar.

Kural şudur: ortam sorgusunda uzunluk taban birimle yazılır. Aynı gerekçe yükseklik ve en boy
oranı sınamaları için de geçerlidir.

#### Ortam Türleri ve Özellik Sorguları

Genişlik dışındaki iki sınama, ortam sorgularının yalnız ekran ölçüsüyle ilgili olmadığını
gösterir.

`@media print` kuralları yazdırma çıktısına uygulanır. Yazdırılan bir ölçüm sayfasında gezinme
çubuğunun, süzgeç alanının ve bağlantı süslemelerinin yeri yoktur; buna karşılık bağlantı
adreslerinin metne yazılması ve sayfa kırılmalarının denetlenmesi gerekir. Çok sütunlu düzen
dersinde tanıtılan `break-inside` bildirimi burada da çalışır.

Ortam sorgularından ayrı ama aynı biçimde yazılan ikinci bir yapı, **özellik sorgusudur
(feature query)**. `@supports` bir bildirimin tanınıp tanınmadığını sınar; ortamı değil, biçem
dilinin çözülmesini sorgular. Izgara ve Esnek Kutu Seçimi dersinde bırakılan soru buradan
yanıtlanır: `subgrid` değerinin tanındığı yerde iç ızgara kapsayanın hatlarını devralır,
tanınmadığı yerde kendi şeritlerini tanımlar.

```css
/* duzen.css — 8. adım: türetilen eşik ve yazdırma */
@media (min-width: 50.75em) {
  .istasyon-duzeni {
    grid-template-columns: minmax(480px, 2fr) minmax(260px, 1fr);
    grid-template-areas:
      "kunye     kunye"
      "olcumler  yan-bilgi"
      "konum     yan-bilgi";
  }
}

@supports (grid-template-columns: subgrid) {
  .olcum-karti {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 3;
  }
}

@media print {
  .gezinme,
  .olcum-suzgeci { display: none; }

  .istasyon-duzeni {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "kunye" "olcumler" "konum" "yan-bilgi";
  }

  .olcum-cizelgesi tr { break-inside: avoid; }
}
```

Üç blok, üç ayrı soru soruyor. Birincisi ortamın genişliğini sınıyor ve yalnız yapıyı
değiştiriyor; ölçüler taban biçemdeki iç boyutlandırmadan geliyor. İkincisi bir bildirimin
tanınıp tanınmadığını sınıyor ve tanınıyorsa kart satırlarını dış ızgaranın hatlarına
bağlıyor. Üçüncüsü çıktının nereye gittiğini sınıyor.

Üçünün ortak yanı, hiçbirinin taban biçemde yazılanı geri almamasıdır. Her blok kendi
koşulunda yeni bir bildirim ekler.

#### Özet

- Ortam sorgusu ortam türü, parantez içinde yazılan ortam özellikleri ve `and`, virgül, `not`
  işleçlerinden kurulur; yazılmayan tür `all` sayılır.
- Sorgu özgüllük eklemez; içindeki kurallar dışarıdakilerle aynı özgüllükte yarışır ve
  çakışmayı kaynak sırası çözer.
- `min-` ve `max-` önekleri kapalı sınır üretir: aynı sayıya yazılan iki sorgu o noktada
  birlikte eşleşir; sınırı kesirle kaydırmak çakışmayı boşluğa çevirir.
- Karşılaştırma yazımı aralığı çakışmasız ve boşluksuz böler; tek yönlü tek bir sorgu yazmak
  ise sorunu tamamen ortadan kaldırır.
- Sorgudaki taban birim kullanıcının varsayılan yazı boyutuna göre çözülür ve eşiği içerikle
  birlikte kaydırır; piksel yazılan eşik bu ayarı göremez.
- `@media print` çıktının ortamını, `@supports` biçem dilinin bir bildirimi tanıyıp
  tanımadığını sınar; ikisi de taban biçemi geri almadan bildirim ekler.

#### Sonraki Adım

Bu dersteki bütün sorgular tek bir şeye bakıyordu: görüntü alanının kendisi. Oysa ölçüm
kartının nasıl görünmesi gerektiğini belirleyen, ekranın genişliği değil kartın **içine
yerleştirildiği yerin** genişliğidir. Aynı kart geniş bir ekranda dar bir yan sütunda
durabilir; görüntü alanı geniş olduğu için kart geniş düzenini açar ve sıkışır. Sonraki ders
sorunun öznesini değiştirir: bir bileşen, bulunduğu kabın ölçüsünü nasıl sorgular?

### Kapsayıcı Sorguları

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/kapsayici-sorgulari

Sorgunun öznesinin görüntü alanından bileşenin kabına taşınması, kap bildirimi ve sınırlama gereği, kapsayıcı birimleri ve aynı bileşenin iki ayrı yerde doğru karar vermesi.

Önceki dersteki bütün sorgular tek bir şeye bakıyordu: görüntü alanının kendisi. Sayfa düzeni
için bu doğru öznedir; sayfanın kaç sütun olacağını gerçekten ekranın genişliği belirler.

Ölçüm kartı için doğru özne değildir. Kartın nasıl görünmesi gerektiğini belirleyen, ekranın
genişliği değil kartın **yerleştirildiği kabın** genişliğidir. Bu ders sorgunun öznesini
değiştirir ve değişimin ne kadar fark ettiğini hesapla ölçer.

#### Aynı Kart, İki Kap

İstasyon sayfasında ölçüm kartı iki yerde bulunur: ana bölümdeki kart ızgarasında ve yan
bilgideki özet listesinde. İki kabın genişlikleri farklıdır ve bu genişlikler görüntü alanının
genişliğinden doğrudan okunamaz.

Kartın kendi eşiği vardır: 280 birimin altında başlık, değer ve birim alt alta dizilir; üstünde
yan yana durur. Aşağıdaki program bu kararı iki ayrı sorgu türüyle veriyor.

```js
// kapsayici.mjs — ayni kartin iki ayri kapta aldigi olcu, iki sorgu turuyle karar
const IC_BOSLUK = 48;
const SAYFA_ARALIK = 24;
const ANA_EN_DAR = 480, YAN_EN_DAR = 260;
const KART = 200, KART_ARALIK = 16;

// sayfa duzeni: minmax(480px, 2fr) minmax(260px, 1fr), tek sutun esigi 812px
function bolumler(gorusAlani) {
  if (gorusAlani < 812) return { ana: gorusAlani - IC_BOSLUK, yan: gorusAlani - IC_BOSLUK };
  let kalan = gorusAlani - IC_BOSLUK - SAYFA_ARALIK;
  let ana = (kalan * 2) / 3, yan = kalan / 3;
  if (yan < YAN_EN_DAR) { yan = YAN_EN_DAR; ana = kalan - yan; }
  if (ana < ANA_EN_DAR) { ana = ANA_EN_DAR; yan = kalan - ana; }
  return { ana, yan };
}

// kart izgarasi: repeat(auto-fill, minmax(200px, 1fr))
function kartOlcusu(kapGenisligi) {
  const n = Math.max(1, Math.floor((kapGenisligi + KART_ARALIK) / (KART + KART_ARALIK)));
  return { sutun: n, olcu: (kapGenisligi - KART_ARALIK * (n - 1)) / n };
}

// kartin kendi esigi: 280px altinda basliklar alt alta, ustunde yan yana
const KART_ESIGI = 280;

console.log("kartin yan yana duzene gectigi olcu: 280px");
console.log("ortam sorgusu karari: (min-width: 812px) -> her kart yan yana");
console.log("kapsayici sorgusu karari: (min-width: 280px) -> her kart kendi kabina gore\n");

console.log("gorus  ana bolum  yan bilgi  ana kart  yan kart  ortam sorgusu       kapsayici sorgusu");
console.log("alani                                            ana / yan           ana / yan");
const hatalar = [];
for (const W of [400, 720, 812, 900, 1024, 1280, 1600]) {
  const { ana, yan } = bolumler(W);
  const anaKart = kartOlcusu(ana), yanKart = kartOlcusu(yan);
  const ortam = W >= 812 ? "yanyana" : "altalta";
  const kapAna = anaKart.olcu >= KART_ESIGI ? "yanyana" : "altalta";
  const kapYan = yanKart.olcu >= KART_ESIGI ? "yanyana" : "altalta";
  if (ortam !== kapAna) hatalar.push(`${W}px ana bolum`);
  if (ortam !== kapYan) hatalar.push(`${W}px yan bilgi`);
  console.log(
    `${String(W).padStart(5)}  ${ana.toFixed(0).padStart(9)}  ${yan.toFixed(0).padStart(9)}  ` +
      `${anaKart.olcu.toFixed(0).padStart(8)}  ${yanKart.olcu.toFixed(0).padStart(8)}  ` +
      `${(ortam + " / " + ortam).padEnd(18)}  ${kapAna} / ${kapYan}`,
  );
}

console.log(`\nortam sorgusunun yanlis karar verdigi durum sayisi: ${hatalar.length}`);
for (const h of hatalar) console.log(`  ${h}`);
```

```
kartin yan yana duzene gectigi olcu: 280px
ortam sorgusu karari: (min-width: 812px) -> her kart yan yana
kapsayici sorgusu karari: (min-width: 280px) -> her kart kendi kabina gore

gorus  ana bolum  yan bilgi  ana kart  yan kart  ortam sorgusu       kapsayici sorgusu
alani                                            ana / yan           ana / yan
  400        352        352       352       352  altalta / altalta   yanyana / yanyana
  720        672        672       213       213  altalta / altalta   altalta / altalta
  812        480        260       232       260  yanyana / yanyana   altalta / altalta
  900        552        276       268       276  yanyana / yanyana   altalta / altalta
 1024        635        317       201       317  yanyana / yanyana   altalta / yanyana
 1280        805        403       258       403  yanyana / yanyana   altalta / yanyana
 1600       1019        509       243       247  yanyana / yanyana   altalta / altalta

ortam sorgusunun yanlis karar verdigi durum sayisi: 10
  400px ana bolum
  400px yan bilgi
  812px ana bolum
  812px yan bilgi
  900px ana bolum
  900px yan bilgi
  1024px ana bolum
  1280px ana bolum
  1600px ana bolum
  1600px yan bilgi
```

On dört karardan onu yanlış. Bu oran, ortam sorgusunun bileşen düzeyinde kötü bir araç
olduğunu tek başına gösteriyor ama asıl ilginç olan hataların **yönü**.

İlk satır beklenenin tersini veriyor. Görüntü alanı 400 birim — dar bir ekran. Sayfa tek
sütunlu olduğu için kart ızgarası tek sütun üretiyor ve kart 352 birim genişliğinde. Kart, dar
ekranda **geniş**. Ortam sorgusu ise ekran dar olduğu için kartı alt alta diziyor.

Son satır aynı yanılgının öbür ucu. Görüntü alanı 1600 birim; ana bölüm 1019 birime çıkıyor ve
kart ızgarası dört sütun üretiyor. Her kart 243 birim: kartın kendi eşiğinin altında. Geniş
ekranda kart **dar**.

Aradaki satırlar üçüncü bir durumu gösteriyor: 1024 ve 1280 birimde ana bölümdeki kartlar dar,
yan bilgideki kart geniş. Tek bir görüntü alanı ölçüsü, aynı anda iki farklı yanıt gerektiren
iki kap içeriyor. Ortam sorgusunun verebileceği tek bir yanıt vardır ve ikisinden en az biri
yanlış olur.

Sonuç bir kuraldır: **sayfanın yapısı görüntü alanına, bileşenin biçimi kabına sorulur.**

#### Kap Bildirmek

**Kapsayıcı sorgusu (container query)** iki adımdan oluşur. Önce bir öge **sorgu kabı (query
container)** ilan edilir, sonra o kabın soyundan gelen ögeler kabın ölçüsünü sorgular.

`container-type` bildirimi kabı ilan eder ve üç değeri vardır. `inline-size`, kabın yalnız
satır içi eksendeki ölçüsünü sorgulanabilir kılar. `size`, iki ekseni birden açar.
`normal` ise ölçü sorgusu açmaz; yalnız biçem sorgularına izin verir.

Bildirimin bir bedeli vardır ve bu bedel bir mantık zorunluluğudur. Kabın ölçüsü sorgulanacaksa,
o ölçü sorgunun sonucundan **etkilenmemelidir**; aksi halde döngü oluşur: kap daralır, sorgu
değişir, içerik değişir, kap genişler, sorgu değişir. Bunu engellemek için kaba **sınırlama
(containment)** uygulanır: `inline-size` değeri satır içi eksende, `size` değeri iki eksende
kabın ölçüsünü içeriğinden bağımsız kılar.

Pratik sonucu şudur: `container-type: size` yazılan bir kaba yükseklik dışarıdan verilmelidir,
yoksa yüksekliği sıfır kabul edilir. `inline-size` bu sorunu doğurmaz, çünkü satır içi ölçü
zaten çoğu zaman kapsayanından gelir. Bu yüzden yaygın olan değer `inline-size` değeridir.

İkinci bildirim `container-name`, kaba ad verir. Adsız bir sorgu **en yakın** sorgu kabını
hedefler; adlı bir sorgu, o adı taşıyan en yakın kabı arar. İç içe kaplar bulunduğunda ad,
hangisinin sorgulandığını belirsizlikten çıkarır. `container` kısayolu ikisini birlikte yazar.

Bir sınır burada belirir ve unutulduğunda şaşırtır: **bir öge kendisini sorgulayamaz.** Sorgu
kabına yazılan bildirimler kabın kendi ölçüsüne göre değişemez; yalnız soyundan gelenler
değişebilir. Bileşenin en dış ögesi bu yüzden kap olarak ilan edilir ve biçimlendirilen ögeler
onun içinde durur.

#### Kapsayıcı Birimleri

Sorgunun öznesi değiştiğinde uzunluk birimlerinin de karşılığı gerekir. **Kapsayıcı birimleri
(container query units)** kabın ölçüsünün yüzde birine karşılık gelir: `cqi` satır içi eksende,
`cqb` blok eksende, `cqw` ve `cqh` fiziksel eksenlerde, `cqmin` ve `cqmax` ikisinin küçüğü ve
büyüğü.

```js
// cqbirim.mjs — kapsayici birimi (cqi) ile gorus alani biriminin (vw) karsilastirmasi
const IC_BOSLUK = 48, SAYFA_ARALIK = 24;
const ANA_EN_DAR = 480, YAN_EN_DAR = 260;
const KART = 200, KART_ARALIK = 16, KOK = 16;

function bolumler(gorusAlani) {
  if (gorusAlani < 812) return { ana: gorusAlani - IC_BOSLUK, yan: gorusAlani - IC_BOSLUK };
  let kalan = gorusAlani - IC_BOSLUK - SAYFA_ARALIK;
  let ana = (kalan * 2) / 3, yan = kalan / 3;
  if (yan < YAN_EN_DAR) { yan = YAN_EN_DAR; ana = kalan - yan; }
  return { ana, yan };
}

function kartOlcusu(kap) {
  const n = Math.max(1, Math.floor((kap + KART_ARALIK) / (KART + KART_ARALIK)));
  return (kap - KART_ARALIK * (n - 1)) / n;
}

const clamp = (k, t, b) => Math.max(k, Math.min(t, b));
const ALT = 0.875 * KOK;   // 14px
const UST = 1.25 * KOK;    // 20px
const SABIT = 0.75 * KOK;  // 12px

// cqi: kapsayicinin satir ici olcusunun yuzde biri
const cqiDeger = (kap) => clamp(ALT, SABIT + (1.5 * kap) / 100, UST);
// vw: gorus alaninin yuzde biri
const vwDeger = (gorus) => clamp(ALT, SABIT + (1.5 * gorus) / 100, UST);

console.log("font-size: clamp(0.875rem, 0.75rem + 1.5cqi, 1.25rem)   (kok 16px)");
console.log("karsilastirma: ayni ifade 1.5vw ile yazilsaydi\n");
console.log("gorus  ana kart  yan kart  cqi: ana  cqi: yan  vw: ana  vw: yan  vw ayrim yapiyor mu");
for (const W of [400, 720, 900, 1024, 1280, 1600]) {
  const { ana, yan } = bolumler(W);
  const anaKart = kartOlcusu(ana), yanKart = kartOlcusu(yan);
  const ca = cqiDeger(anaKart), cy = cqiDeger(yanKart);
  const va = vwDeger(W), vy = vwDeger(W);
  console.log(
    `${String(W).padStart(5)}  ${anaKart.toFixed(0).padStart(8)}  ${yanKart.toFixed(0).padStart(8)}  ` +
      `${ca.toFixed(2).padStart(8)}  ${cy.toFixed(2).padStart(8)}  ` +
      `${va.toFixed(2).padStart(7)}  ${vy.toFixed(2).padStart(7)}  ${(va === vy ? "hayir" : "evet").padStart(19)}`,
  );
}

console.log("\n--- ayni kart tek basina: kabin olcusu degisince cqi degeri ---");
console.log("kap olcusu  1cqi   uygulanan yazi boyutu  hangi bolge");
for (const kap of [180, 240, 300, 380, 460, 560]) {
  const bir = kap / 100;
  const d = cqiDeger(kap);
  const bolge = d === ALT ? "alt sinir" : d === UST ? "ust sinir" : "tercih edilen";
  console.log(
    `${String(kap).padStart(10)}  ${bir.toFixed(2).padStart(4)}  ${d.toFixed(2).padStart(21)}  ${bolge.padStart(11)}`,
  );
}
```

```
font-size: clamp(0.875rem, 0.75rem + 1.5cqi, 1.25rem)   (kok 16px)
karsilastirma: ayni ifade 1.5vw ile yazilsaydi

gorus  ana kart  yan kart  cqi: ana  cqi: yan  vw: ana  vw: yan  vw ayrim yapiyor mu
  400       352       352     17.28     17.28    18.00    18.00                hayir
  720       213       213     15.20     15.20    20.00    20.00                hayir
  900       268       276     16.02     16.14    20.00    20.00                hayir
 1024       201       317     15.01     16.76    20.00    20.00                hayir
 1280       258       403     15.87     18.04    20.00    20.00                hayir
 1600       243       247     15.64     15.70    20.00    20.00                hayir

--- ayni kart tek basina: kabin olcusu degisince cqi degeri ---
kap olcusu  1cqi   uygulanan yazi boyutu  hangi bolge
       180  1.80                  14.70  tercih edilen
       240  2.40                  15.60  tercih edilen
       300  3.00                  16.50  tercih edilen
       380  3.80                  17.70  tercih edilen
       460  4.60                  18.90  tercih edilen
       560  5.60                  20.00    ust sinir
```

Son sütun, görüntü alanı biriminin bileşen ölçeklemesinde neden yetersiz kaldığını gösteriyor:
`vw` iki kabı hiçbir satırda ayırt etmiyor. Aynı sayfada, aynı anda, iki farklı genişlikteki
kaba aynı değeri veriyor — üstelik 720 biriminden sonra üst sınıra dayandığı için değer hiç
değişmiyor.

Kapsayıcı birimi ise kabın ölçüsünü izliyor. 1024 birimlik görüntü alanında ana bölümdeki kart
15,01, yan bilgideki kart 16,76 birimlik yazı alıyor; ikisi de kendi genişliğine oranlı.

İkinci tablo tek bir kabın davranışını ayrıştırıyor. Kap genişledikçe `1cqi` büyür ve tercih
edilen değer yükselir; 560 birimde üst sınıra dayanır ve kelepçelenir. CSS Fonksiyonları
dersindeki üç bölgeli davranış burada da geçerlidir, yalnız girdi değişmiştir.

Bir ayrıntı: kapsayıcı birimleri hiçbir sorgu kabının içinde olmayan bir ögede kullanılırsa
görüntü alanına göre çözülür. Birim yazmak kabı ilan etmez; `container-type` bildirimi ayrıca
yazılmalıdır.

#### Bileşen Kendi Eşiğini Taşır

Kapsayıcı sorgusunun asıl kazancı hesapta değil, biçem dosyasının yapısındadır.

Ortam sorgusuyla yazıldığında bileşenin eşiği, bileşenin bulunduğu **her yer** için ayrı ayrı
hesaplanmak zorundadır: ana bölümdeki kart için bir eşik, yan bilgideki kart için başka bir
eşik, sayfa düzeni değiştiğinde ikisi birden. Bileşen yeni bir yere taşındığında eşikler yeniden
hesaplanır.

Kapsayıcı sorgusuyla yazıldığında eşik bileşenin kendisine aittir ve tek bir yerde durur.
Bileşen nereye konursa konsun doğru kararı verir; sayfa düzeni değiştiğinde bileşenin biçem
kodu değişmez.

```css
/* duzen.css — 9. adım: kart kendi kabını sorgular */
.olcum-karti-kabi {
  container-type: inline-size;
  container-name: kart;
}

.olcum-karti {
  display: grid;
  gap: 0.25rem;
  font-size: clamp(0.875rem, 0.75rem + 1.5cqi, 1.25rem);
}

@container kart (min-width: 280px) {
  .olcum-karti {
    grid-template-columns: 1fr auto;
    align-items: baseline;
  }

  .olcum-karti .aciklama { grid-column: 1 / -1; }
}

@container kart (min-width: 420px) {
  .olcum-karti .egilim { display: block; }
}
```

Kabın ayrı bir öge olması bir zorunluluktur: kart, kendisini sorgulayamaz. Kap yalnız
`container-type` ve `container-name` taşır; görsel bildirimlerin tamamı içerideki `.olcum-karti`
ögesindedir.

İki eşik iki ayrı soruya karşılık geliyor. 280 birimde başlık ve değer yan yana geçiyor; 420
birimde eğilim göstergesi görünür oluyor. İkisi de görüntü alanından değil kabın genişliğinden
okunuyor ve iki farklı kapta iki farklı sonuç veriyor.

Sorgunun bir üçüncü biçimi daha vardır. Ölçü yerine bir özel özelliğin değeri sorgulanabilir;
`@container style(--durum: uyari)` yazımı, kapta tanımlı özelliğin değerine göre biçem ayırır.
Ölçü sorgusundan farkı, kabın ölçüsüne değil biçem durumuna bakmasıdır ve sınırlama gerektirmez.

#### Özet

- Sayfanın yapısı görüntü alanına, bileşenin biçimi bulunduğu kaba sorulur; iki soru aynı ölçüyle
  yanıtlanamaz.
- İstasyon sayfasının kart yerleşiminde ortam sorgusu on dört karardan onunda yanlış sonuç verir;
  dar ekranda kart geniş, geniş ekranda kart dar olabilir.
- `container-type` bir ögeyi sorgu kabı yapar ve karşılığında sınırlama uygular; `size` değeri
  yüksekliğin dışarıdan verilmesini gerektirdiği için `inline-size` daha az kısıtlıdır.
- Bir öge kendisini sorgulayamaz; kap, biçimlendirilecek bileşenin dışında ayrı bir öge olarak
  ilan edilir.
- Kapsayıcı birimleri kabın ölçüsünün yüzde birine karşılık gelir ve aynı sayfadaki iki kabı
  ayırt eder; görüntü alanı birimi ayırt edemez.
- Eşik bileşenin kendisine yazıldığında, bileşen taşındığında biçem kodu değişmez; ortam
  sorgusuyla yazılan eşik her yerleşim için yeniden hesaplanır.

#### Sonraki Adım

Bu derste yazı boyutu bir kez kabın genişliğine bağlandı ve `clamp()` ifadesinin içine
yerleştirildi. İfadenin üç sayısı — alt sınır, tercih edilen değer, üst sınır — seçilmiş gibi
göründü ama nasıl seçildikleri söylenmedi. Sonraki ders bu üç sayıyı hesaplanabilir kılar: iki
ölçü noktası verildiğinde aradaki doğrusal geçiş nasıl kurulur, ölçek adımları arasındaki oran
dar ekranda neden daralır ve yazı boyutunun kullanıcı ayarına saygı göstermesi ne demektir?

### Duyarlı Tipografi

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/duyarli-tipografi

Akışkan yazı boyutu ifadesinin iki ölçü noktasından türetilmesi, ölçek oranının dar ekranda daralması, satır uzunluğu denetimi ve yazının yakınlaştırma ile kullanıcı ayarına verdiği tepki.

Bir önceki derste kart yazısı `clamp()` ifadesinin içine yerleştirildi ve üç sayı verildi: alt
sınır, tercih edilen değer, üst sınır. Sayıların nasıl seçildiği söylenmedi.

Bu ders o seçimi hesaba döker. Yazı boyutunun ölçekle değişmesi bir görsel tercih değil,
okunabilirlik kısıtının sonucudur; kısıt sayıya çevrildiğinde ifade kendiliğinden çıkar.

#### İki Ölçü Noktası, Bir Doğru

**Akışkan tipografide (fluid typography)** girdi iki karardır: **en dar** düzende yazının ne
kadar olacağı ve **en geniş** düzende ne kadar olacağı. İki nokta bir doğru belirler; `clamp()` ifadesinin orta terimi
bu doğrunun denklemidir.

Doğrunun eğimi görüntü alanı birimiyle, kesişimi taban birimle yazılır. İkisinin toplamı,
görüntü alanı büyüdükçe yazının doğrusal büyümesini verir.

```js
// akiskan.mjs — iki olcu noktasindan akiskan yazi boyutu ifadesinin turetilmesi
const KOK = 16;

// iki olcu noktasi: (gorus alani, istenen yazi boyutu)
function turet(v1, s1, v2, s2) {
  const egim = (s2 - s1) / (v2 - v1);       // px / px
  const vw = egim * 100;                    // 1vw = gorus alaninin yuzde biri
  const sabitPx = s1 - egim * v1;           // ifadenin sabit terimi
  return { vw, sabitRem: sabitPx / KOK, sabitPx };
}

const govde = turet(360, 16, 1280, 20);
const baslik = turet(360, 24, 1280, 40);

const yaz = (t, alt, ust) =>
  `clamp(${(alt / KOK).toFixed(3)}rem, ${t.sabitRem.toFixed(4)}rem + ${t.vw.toFixed(4)}vw, ${(ust / KOK).toFixed(3)}rem)`;

console.log("olcu noktalari: 360px -> govde 16px / baslik 24px,  1280px -> govde 20px / baslik 40px");
console.log("govde  :", yaz(govde, 16, 20));
console.log("baslik :", yaz(baslik, 24, 40));

const clamp = (k, t, b) => Math.max(k, Math.min(t, b));
const coz = (t, alt, ust, gorus) => clamp(alt, t.sabitPx + (t.vw * gorus) / 100, ust);

console.log("\ngorus alani  govde  baslik  baslik/govde  govde bolgesi");
for (const V of [320, 360, 500, 700, 900, 1280, 1600]) {
  const g = coz(govde, 16, 20, V);
  const b = coz(baslik, 24, 40, V);
  const bolge = g === 16 ? "alt sinir" : g === 20 ? "ust sinir" : "tercih";
  console.log(
    `${String(V).padStart(11)}  ${g.toFixed(2).padStart(5)}  ${b.toFixed(2).padStart(6)}  ` +
      `${(b / g).toFixed(3).padStart(12)}  ${bolge.padStart(13)}`,
  );
}

// olcu denetimi: metin sutunu 675px ust sinirli, karakter genisligi yazi boyutunun yarisi
console.log("\n--- satir uzunlugu denetimi (sutun ust siniri 675px, kenar bosluk 48px) ---");
console.log("gorus alani  sutun  govde  karakter  45-75 araliginda mi");
for (const V of [320, 360, 500, 700, 900, 1280, 1600]) {
  const sutun = Math.min(675, V - 48);
  const g = coz(govde, 16, 20, V);
  const karakter = sutun / (g * 0.5);
  console.log(
    `${String(V).padStart(11)}  ${sutun.toFixed(0).padStart(5)}  ${g.toFixed(2).padStart(5)}  ` +
      `${karakter.toFixed(1).padStart(8)}  ${(karakter >= 45 && karakter <= 75 ? "evet" : "HAYIR").padStart(19)}`,
  );
}
```

```
olcu noktalari: 360px -> govde 16px / baslik 24px,  1280px -> govde 20px / baslik 40px
govde  : clamp(1.000rem, 0.9022rem + 0.4348vw, 1.250rem)
baslik : clamp(1.500rem, 1.1087rem + 1.7391vw, 2.500rem)

gorus alani  govde  baslik  baslik/govde  govde bolgesi
        320  16.00   24.00         1.500      alt sinir
        360  16.00   24.00         1.500      alt sinir
        500  16.61   26.43         1.592         tercih
        700  17.48   29.91         1.711         tercih
        900  18.35   33.39         1.820         tercih
       1280  20.00   40.00         2.000      ust sinir
       1600  20.00   40.00         2.000      ust sinir

--- satir uzunlugu denetimi (sutun ust siniri 675px, kenar bosluk 48px) ---
gorus alani  sutun  govde  karakter  45-75 araliginda mi
        320    272  16.00      34.0                HAYIR
        360    312  16.00      39.0                HAYIR
        500    452  16.61      54.4                 evet
        700    652  17.48      74.6                 evet
        900    675  18.35      73.6                 evet
       1280    675  20.00      67.5                 evet
       1600    675  20.00      67.5                 evet
```

İki satırlık çıktı, elle yazılması zor iki ifadeyi veriyor. Gövde yazısı için sabit terim
0,9022 taban birim, eğim 0,4348 görüntü alanı birimi. Sayıların ondalıklı olması beklenendir:
iki ölçü noktasından geçen doğrunun katsayıları yuvarlak çıkmaz.

İfade üç bölgede çalışır. 360 biriminin altında alt sınır, 1280 biriminin üstünde üst sınır
uygulanır; arada doğru işler. Ölçü noktalarındaki değerler tam olarak istenen sayılardır — bu,
türetmenin doğruluk denetimidir.

#### Ölçek Dar Ekranda Daralır

Dördüncü sütun ayrı bir olguyu ölçüyor: başlık ile gövde arasındaki oran sabit değil.

Dar düzende oran 1,5; geniş düzende 2,0. Bu, iki ayrı ifadenin farklı eğimlere sahip olmasının
kaçınılmaz sonucudur ve istenen sonuçtur. Bir başlık, gövde yazısının iki katı olduğunda dar bir
ekranda satırı ikiye böler ve hiyerarşi bilgisi yerine bir engel üretir. Aynı oran geniş bir
ekranda yerini bulur.

CSS ile Görsel Sunum kursunda tanıtılan **modüler ölçek**, adımlar arasındaki oranı sabit bir
çarpanla verir. Duyarlı tipografide bu çarpanın kendisi ölçekle değişir: dar düzende sıkışır,
geniş düzende açılır. Uygulamada bu, her ölçek adımı için ayrı bir `clamp()` ifadesi yazmak
demektir; adımların ifadeleri farklı eğimlere sahip olur.

Bir uyarı: ölçek adımlarının **sırası** korunmalıdır. İki ifade farklı eğimlerle yazıldığında,
belirli bir genişlikte alt adımın üst adımı geçmesi mümkündür. Denetim yolu türetmenin
kendisidir — iki uçtaki değerler sıralıysa ve iki ifade de doğrusalsa arada sıra bozulmaz.

#### Satır Uzunluğu Denetimi

Yazı boyutu tek başına anlam taşımaz; anlamlı olan **satır uzunluğudur (line length)** — yani
satırda kaç karakter bulunduğudur. İkinci tablo
bunu denetliyor: sütun genişliği yazı boyutunun yarısına bölünerek karakter sayısı bulunuyor.

Beş genişlikte sonuç aralıkta, iki genişlikte değil. 320 ve 360 birimlik görüntü alanlarında
satır 34 ve 39 karakter; alt sınırın altında.

Bu bir hata değil, bir kısıttır. Dar bir ekranda 45 karaktere ulaşmanın tek yolu yazıyı
küçültmektir ve yazı zaten alt sınırındadır. İki kısıt aynı anda karşılanamaz; okunabilir yazı
boyutu korunur ve satır uzunluğu kabul edilir.

Tablonun asıl değeri şudur: kısıt **bilinerek** kabul edilmiştir. Aynı hesap, üst sınırın aşıldığı
durumu da yakalar — 700 birimde satır 74,6 karakterle sınıra dayanıyor. Üst sınırın gerçekten
bağlayıcı olduğu yer ise 900 birimdir; sütun orada 675'e kelepçelenir. Bu sınır 675 yerine 720
birim seçilseydi aynı satır 78,5 karaktere çıkar ve aralığın dışına düşerdi.

#### Yakınlaştırma ve Kullanıcı Ayarı

Bir yazı boyutu ifadesinin son sınavı, kullanıcının yazıyı büyütme isteğine verdiği tepkidir.

```js
// yakinlastirma.mjs — yazi boyutu ifadesinin yakinlastirmaya ve kullanici ayarina tepkisi
const AYGIT = 1280;   // aygitin fiziksel genisligi (aygit pikseli)

// Yakinlastirma orani Z: 1 CSS pikseli Z aygit pikseli olur,
// gorus alani CSS pikseli cinsinden AYGIT/Z olur.
function olcum(Z, kok) {
  const gorusCss = AYGIT / Z;
  const vwSadece = (1.25 * gorusCss) / 100;                       // font-size: 1.25vw
  const remSadece = 1.25 * kok;                                    // font-size: 1.25rem
  const tercih = 0.9022 * kok + (0.4348 * gorusCss) / 100;         // clamp'in orta terimi
  const kelepce = Math.max(1 * kok, Math.min(tercih, 1.25 * kok)); // clamp(1rem, ..., 1.25rem)
  return {
    gorusCss,
    vwCss: vwSadece, vwAygit: vwSadece * Z,
    remCss: remSadece, remAygit: remSadece * Z,
    clampCss: kelepce, clampAygit: kelepce * Z,
  };
}

console.log(`aygit genisligi ${AYGIT} aygit pikseli, kok yazi boyutu 16px\n`);
console.log("--- sayfa yakinlastirma: metnin fiziksel boyutu ---");
console.log("yakinlastirma  gorus alani(css)  1.25vw  1.25rem  clamp  <- aygit pikseli");
for (const Z of [1, 1.25, 1.5, 2]) {
  const o = olcum(Z, 16);
  console.log(
    `${(Z * 100 + "%").padStart(13)}  ${o.gorusCss.toFixed(0).padStart(16)}  ` +
      `${o.vwAygit.toFixed(2).padStart(6)}  ${o.remAygit.toFixed(2).padStart(7)}  ${o.clampAygit.toFixed(2).padStart(5)}`,
  );
}
const t1 = olcum(1, 16), t2 = olcum(2, 16);
console.log("\n%200 yakinlastirmada buyume orani:");
console.log(`  1.25vw  : ${(t2.vwAygit / t1.vwAygit).toFixed(2)}x`);
console.log(`  1.25rem : ${(t2.remAygit / t1.remAygit).toFixed(2)}x`);
console.log(`  clamp   : ${(t2.clampAygit / t1.clampAygit).toFixed(2)}x`);

console.log("\n--- kullanici varsayilan yazi boyutunu degistirirse (yakinlastirma yok) ---");
console.log("varsayilan  1.25vw  1.25rem  clamp  <- css pikseli");
for (const kok of [16, 20, 24, 32]) {
  const o = olcum(1, kok);
  console.log(
    `${(kok + "px").padStart(10)}  ${o.vwCss.toFixed(2).padStart(6)}  ` +
      `${o.remCss.toFixed(2).padStart(7)}  ${o.clampCss.toFixed(2).padStart(5)}`,
  );
}
const k1 = olcum(1, 16), k2 = olcum(1, 32);
console.log("\nvarsayilan iki katina cikarildiginda buyume orani:");
console.log(`  1.25vw  : ${(k2.vwCss / k1.vwCss).toFixed(2)}x`);
console.log(`  1.25rem : ${(k2.remCss / k1.remCss).toFixed(2)}x`);
console.log(`  clamp   : ${(k2.clampCss / k1.clampCss).toFixed(2)}x`);
```

```
aygit genisligi 1280 aygit pikseli, kok yazi boyutu 16px

--- sayfa yakinlastirma: metnin fiziksel boyutu ---
yakinlastirma  gorus alani(css)  1.25vw  1.25rem  clamp  <- aygit pikseli
         100%              1280   16.00    20.00  20.00
         125%              1024   16.00    25.00  23.61
         150%               853   16.00    30.00  27.22
         200%               640   16.00    40.00  34.44

%200 yakinlastirmada buyume orani:
  1.25vw  : 1.00x
  1.25rem : 2.00x
  clamp   : 1.72x

--- kullanici varsayilan yazi boyutunu degistirirse (yakinlastirma yok) ---
varsayilan  1.25vw  1.25rem  clamp  <- css pikseli
      16px   16.00    20.00  20.00
      20px   16.00    25.00  23.61
      24px   16.00    30.00  27.22
      32px   16.00    40.00  34.44

varsayilan iki katina cikarildiginda buyume orani:
  1.25vw  : 1.00x
  1.25rem : 2.00x
  clamp   : 1.72x
```

Birinci tablonun ilk sütunu sonucun tamamını taşıyor: yalnız görüntü alanı birimiyle yazılan
yazı boyutu yakınlaştırmadan **hiç etkilenmiyor**. Nedeni aritmetiktir. Yakınlaştırma oranı iki
katına çıktığında görüntü alanı CSS pikseli cinsinden yarıya iner; yazı boyutu da yarıya iner;
her CSS pikseli iki aygıt pikseli olduğu için sonuç değişmez. Kullanıcı yakınlaştırma yapar,
sayfadaki her şey büyür, yazı olduğu yerde kalır.

Taban birimle yazılan boyut iki katına çıkar. Karışık ifade ikisinin arasındadır: 1,72 katı.
Ölçek oranı, sabit terimin toplam içindeki payı kadardır; görüntü alanı birimi ağırlaştıkça
tepki azalır.

İkinci tablo aynı sayıları veriyor. Bu bir rastlantı değil: sabit terim taban birimle, değişken
terim görüntü alanı birimiyle yazıldığında, yakınlaştırmayı iki katına çıkarmak ile kök yazı
boyutunu iki katına çıkarmak aynı sonucu üretir. İki ayar da aynı ifadeden geçer.

Erişilebilirlik ölçütü buradan okunur. WCAG 1.4.4 ölçütü, metnin yardımcı bir araç
gerektirmeden yüzde 200'e kadar büyütülebilmesini ve bunun içerik ya da işlev kaybına yol
açmamasını ister. Yalnız görüntü alanı birimiyle yazılan bir yazı boyutu bu ölçütü karşılamaz.
Kural yalındır: tercih edilen değerin **taban birimle yazılmış bir bileşeni olmalıdır**;
görüntü alanı birimi tek başına kullanılmaz.

#### Yazının Diğer Ölçüleri

Yazı boyutu tek değişken değildir.

**Satır aralığı** birimsiz yazılır: `line-height: 1.5` bir orandır ve her ögede kendi yazı
boyutuyla çarpılır. Uzunluk yazıldığında — `line-height: 24px` — kalıtımla geçen şey hesaplanmış
uzunluktur; 32 birimlik bir başlık, 24 birimlik satır aralığıyla kalır ve satırlar üst üste
biner. Oran ayrıca ölçekle de değişir: küçük yazıda daha geniş, büyük başlıkta daha dar satır
aralığı okunurluğu artırır.

**Harf aralığı** büyük boyutlarda daralır. Başlık boyutu büyüdükçe harfler arasındaki boşluk
görsel olarak açılır; hafif bir eksi değer bunu dengeler. Gövde yazısında böyle bir düzeltme
gerekmez.

**Satır kırma davranışı** iki bildirimle ayarlanır. `text-wrap: balance` başlıklarda satırların
uzunluğunu eşitler ve son satırda tek kelime kalmasını önler; `text-wrap: pretty` uzun metinlerde
paragraf sonundaki tek kelimeyi düzeltir. İkisi de öneridir, garanti değil.

**Heceleme**, `hyphens: auto` ile açılır ve yalnız belgenin dili bildirilmişse çalışır — Web'in
Temelleri ve HTML kursunda kurulan dil bildirimi burada işlevsel bir karşılık kazanır. Dar
sütunlarda satır sonlarındaki büyük boşlukları azaltır.

```css
/* duzen.css — 10. adım: akışkan tipografi ölçeği */
:root {
  --yazi-govde:  clamp(1rem, 0.9022rem + 0.4348vw, 1.25rem);
  --yazi-baslik: clamp(1.5rem, 1.1087rem + 1.7391vw, 2.5rem);
}

body {
  font-size: var(--yazi-govde);
  line-height: 1.6;
}

h1 {
  font-size: var(--yazi-baslik);
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.istasyon-notlari {
  max-inline-size: 675px;
  hyphens: auto;
  text-wrap: pretty;
}
```

İki özel özellik, iki türetilmiş ifade taşıyor. Satır aralığı gövdede 1,6, başlıkta 1,15;
birimsiz oldukları için ikisi de kendi yazı boyutuyla çarpılıyor. Sütunun üst sınırı taban
birimle değil piksel cinsinden yazılmış durumda — bu bilinçli bir seçimdir: sütun genişliği
satır uzunluğu denetiminden geliyor ve o denetim yazı boyutunu zaten hesaba katıyor.

#### Özet

- Akışkan yazı boyutu iki ölçü noktasından türetilir; eğim görüntü alanı birimiyle, sabit terim
  taban birimle yazılır ve `clamp()` ifadesinin orta terimini oluşturur.
- Ölçek adımları farklı eğimlerle yazıldığında başlık–gövde oranı dar düzende daralır, geniş
  düzende açılır; bu istenen davranıştır.
- Satır uzunluğu, sütun genişliğinin yazı boyutuna oranıdır ve 45–75 karakter aralığında tutulur;
  çok dar düzenlerde alt sınır karşılanamaz ve bu kısıt bilinerek kabul edilir.
- Yalnız görüntü alanı birimiyle yazılan yazı boyutu yakınlaştırmadan etkilenmez ve metnin
  yüzde 200'e kadar büyütülebilmesini isteyen ölçütü karşılamaz.
- Yakınlaştırmayı iki katına çıkarmak ile kök yazı boyutunu iki katına çıkarmak aynı ifadeden
  geçer ve aynı sonucu verir.
- Satır aralığı birimsiz yazılır; uzunluk yazıldığında kalıtımla geçen hesaplanmış değer büyük
  yazılarda satırların üst üste binmesine yol açar.

#### Sonraki Adım

Yazı ölçekle birlikte değişiyor, düzen kabına göre uyarlanıyor. Geriye sayfanın en ağır ögesi
kalıyor: görsel. Bir görsel, dar bir ekranda gereğinden büyük bir dosyayla indirilebilir ya da
geniş bir ekranda bulanık görünebilir; ikisi de aynı bildirimden doğar. Üstelik bazı görsellerde
sorun ölçü değil içeriktir — geniş bir manzara karesinin dar ekranda anlamlı kalması için
kırpılması gerekir. Sonraki ders bu iki soruyu ayırır: tarayıcı hangi dosyayı seçer ve seçimi
kim yönlendirir?

### Duyarlı Görseller

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/duyarli-gorseller

Çözünürlük değişimi ile sanat yönetiminin ayrılması, kaynak boyutu bildiriminin çözülmesi, aday seçim kuralı, kaynak sırasının etkisi ve ayrılan yerin içerik kaymasını önlemesi.

Yazı ölçekle birlikte değişiyor, düzen kabına göre uyarlanıyor. Geriye sayfanın en ağır ögesi
kalıyor: konum bölümündeki görsel.

Görselde iki ayrı sorun aynı anda bulunur ve karıştırıldıklarında ikisi de çözülmez. Birincisi
**ölçü** sorunudur: aynı kare, dar bir ekranda gereğinden büyük bir dosyayla indirilir ya da
yoğun bir ekranda bulanık görünür. İkincisi **içerik** sorunudur: geniş bir manzara karesi dar
bir ekranda küçüldüğünde ayrıntısı okunamaz hâle gelir; gereken küçültme değil kırpmadır.

Bu ders iki sorunu ayırır ve her birinin aracını gösterir.

#### Çözünürlük Değişimi

Birinci sorunun adı **çözünürlük değişimidir (resolution switching)**: aynı görselin farklı
piksel ölçülerindeki kopyaları hazırlanır ve tarayıcı hangisini indireceğine karar verir.

Karar iki bilgiye dayanır. `srcset` özniteliği bir **aday kümesi (candidate set)** verir; her
aday, dosyanın gerçek piksel genişliğiyle birlikte yazılır. `sizes` özniteliği ise görselin
sayfada kaplayacağı yeri bildirir — buna **kaynak boyutu (source size)** denir.

İkincisi neden gereklidir? Tarayıcı, biçem dosyasını çözmeden önce görsel indirmeye başlar;
görselin sayfada ne kadar yer kaplayacağını o anda bilemez. `sizes`, bu bilgiyi biçem
dosyasından bağımsız olarak belgeye yazar.

```js
// srcset.mjs — kaynak boyutunun cozulmesi ve aday secimi
// sizes: sirayla denenir, ilk eslesen kosulun uzunlugu kaynak boyutudur.
const SIZES = [
  ["(min-width: 812px)", "33vw"],
  ["(min-width: 500px)", "50vw"],
  [null, "calc(100vw - 48px)"],
];

// srcset: her aday, gercek piksel genisligiyle (w tanimlayicisi) bildirilir
const ADAYLAR = [320, 480, 640, 960, 1280, 1920];

function kosulDogru(kosul, gorusAlani) {
  if (kosul === null) return true;
  const m = kosul.match(/^\((min|max)-width:\s*(\d+)px\)$/);
  return m[1] === "min" ? gorusAlani >= Number(m[2]) : gorusAlani <= Number(m[2]);
}

function uzunluk(ifade, gorusAlani) {
  let m = ifade.match(/^(\d+(?:\.\d+)?)vw$/);
  if (m) return (Number(m[1]) * gorusAlani) / 100;
  m = ifade.match(/^calc\((\d+(?:\.\d+)?)vw\s*-\s*(\d+(?:\.\d+)?)px\)$/);
  if (m) return (Number(m[1]) * gorusAlani) / 100 - Number(m[2]);
  m = ifade.match(/^(\d+(?:\.\d+)?)px$/);
  if (m) return Number(m[1]);
  throw new Error(`cozulemeyen uzunluk: ${ifade}`);
}

function kaynakBoyutu(gorusAlani) {
  for (const [kosul, ifade] of SIZES) {
    if (kosulDogru(kosul, gorusAlani)) return { ifade, px: uzunluk(ifade, gorusAlani) };
  }
  throw new Error("sizes listesi bos");
}

// Secim kurali: gereken piksel genisligini karsilayan en kucuk aday.
// Hicbiri karsilamiyorsa en buyuk aday secilir.
function secim(gereken) {
  return ADAYLAR.find((a) => a >= gereken) ?? ADAYLAR[ADAYLAR.length - 1];
}

console.log("sizes: " + SIZES.map(([k, i]) => (k ? `${k} ${i}` : i)).join(", "));
console.log("srcset adaylari: " + ADAYLAR.map((a) => `${a}w`).join(", "));
console.log("\ngorus  kaynak boyutu ifadesi   kaynak   piksel  gereken  secilen  fazla");
console.log("alani                          boyutu   orani            aday     piksel");
for (const V of [360, 500, 700, 812, 1280, 1600]) {
  for (const dpr of [1, 2, 3]) {
    const { ifade, px } = kaynakBoyutu(V);
    const gereken = px * dpr;
    const secilen = secim(gereken);
    const fazla = secilen - gereken;
    console.log(
      `${String(V).padStart(5)}  ${ifade.padEnd(21)}  ${px.toFixed(0).padStart(6)}  ` +
        `${String(dpr + "x").padStart(6)}  ${gereken.toFixed(0).padStart(7)}  ` +
        `${String(secilen + "w").padStart(7)}  ${fazla.toFixed(0).padStart(6)}`,
    );
  }
}

// aday genisliklerinin piksel sayisi (3:2 en boy orani varsayilarak)
console.log("\n--- aday dosyalarin piksel sayisi (3:2 en boy orani) ---");
console.log("aday   genislik x yukseklik   piksel      320w'in kaci kadar");
for (const a of ADAYLAR) {
  const y = Math.round((a * 2) / 3);
  const piksel = a * y;
  const taban = 320 * Math.round((320 * 2) / 3);
  console.log(
    `${String(a + "w").padStart(5)}  ${String(a + " x " + y).padStart(18)}  ` +
      `${String(piksel).padStart(9)}  ${(piksel / taban).toFixed(1).padStart(18)}x`,
  );
}

// sizes yanlis yazilirsa: gorsel 33vw yer kapliyorken 100vw bildirilmis olsun
console.log("\n--- sizes yanlis yazilirsa (gercekte 33vw, bildirilen 100vw) ---");
console.log("gorus  piksel  dogru sizes  yanlis sizes  secilen piksel orani");
console.log("alani  orani   ile secilen  ile secilen");
for (const V of [812, 1280, 1600]) {
  for (const dpr of [1, 2]) {
    const dogru = secim(kaynakBoyutu(V).px * dpr);
    const yanlis = secim(V * dpr);
    const y1 = Math.round((dogru * 2) / 3), y2 = Math.round((yanlis * 2) / 3);
    const oran = (yanlis * y2) / (dogru * y1);
    console.log(
      `${String(V).padStart(5)}  ${String(dpr + "x").padStart(6)}  ` +
        `${String(dogru + "w").padStart(11)}  ${String(yanlis + "w").padStart(12)}  ${oran.toFixed(1).padStart(19)}x`,
    );
  }
}
```

```
sizes: (min-width: 812px) 33vw, (min-width: 500px) 50vw, calc(100vw - 48px)
srcset adaylari: 320w, 480w, 640w, 960w, 1280w, 1920w

gorus  kaynak boyutu ifadesi   kaynak   piksel  gereken  secilen  fazla
alani                          boyutu   orani            aday     piksel
  360  calc(100vw - 48px)        312      1x      312     320w       8
  360  calc(100vw - 48px)        312      2x      624     640w      16
  360  calc(100vw - 48px)        312      3x      936     960w      24
  500  50vw                      250      1x      250     320w      70
  500  50vw                      250      2x      500     640w     140
  500  50vw                      250      3x      750     960w     210
  700  50vw                      350      1x      350     480w     130
  700  50vw                      350      2x      700     960w     260
  700  50vw                      350      3x     1050    1280w     230
  812  33vw                      268      1x      268     320w      52
  812  33vw                      268      2x      536     640w     104
  812  33vw                      268      3x      804     960w     156
 1280  33vw                      422      1x      422     480w      58
 1280  33vw                      422      2x      845     960w     115
 1280  33vw                      422      3x     1267    1280w      13
 1600  33vw                      528      1x      528     640w     112
 1600  33vw                      528      2x     1056    1280w     224
 1600  33vw                      528      3x     1584    1920w     336

--- aday dosyalarin piksel sayisi (3:2 en boy orani) ---
aday   genislik x yukseklik   piksel      320w'in kaci kadar
 320w           320 x 213      68160                 1.0x
 480w           480 x 320     153600                 2.3x
 640w           640 x 427     273280                 4.0x
 960w           960 x 640     614400                 9.0x
1280w          1280 x 853    1091840                16.0x
1920w         1920 x 1280    2457600                36.1x

--- sizes yanlis yazilirsa (gercekte 33vw, bildirilen 100vw) ---
gorus  piksel  dogru sizes  yanlis sizes  secilen piksel orani
alani  orani   ile secilen  ile secilen
  812      1x         320w          960w                  9.0x
  812      2x         640w         1920w                  9.0x
 1280      1x         480w         1280w                  7.1x
 1280      2x         960w         1920w                  4.0x
 1600      1x         640w         1920w                  9.0x
 1600      2x        1280w         1920w                  2.3x
```

Birinci tablo iki adımı ayrı ayrı gösteriyor. Önce `sizes` listesi soldan sağa denenir ve
koşulu doğru olan **ilk** uzunluk kaynak boyutu olur — 812 birimlik bir görüntü alanında 33
görüntü alanı birimi, yani 268 piksel. Sonra bu değer aygıtın **piksel oranıyla (device pixel
ratio)** çarpılır: iki katlık bir ekranda 536 gerçek piksel gerekir. Aday kümesinden bu sayıyı
karşılayan en küçük dosya seçilir.

Sayılar bir noktayı hemen görünür kılıyor: aynı sayfa, aynı görüntü alanı genişliğinde, piksel
oranına göre 320, 640 ya da 960 piksellik dosya indirir. Görüntü alanı geniş olduğu için büyük
dosya seçilmez; kaynak boyutu ile piksel oranının çarpımı seçer.

İkinci tablo adaylar arasındaki farkın büyüklüğünü veriyor. Piksel sayısı genişliğin **karesiyle**
büyür: 1920 piksellik dosya, 320 piksellik dosyanın 36 katı piksel taşır. Bu yüzden aday
kümesinde bir basamak yanlış seçim, katlanarak büyüyen bir maliyettir.

#### Kaynak Boyutu Yanlış Bildirilirse

Üçüncü tablo, en sık yapılan hatanın ölçüsünü veriyor. Görsel gerçekte görüntü alanının üçte
birini kaplarken `sizes` bildirimi tamamını kapladığını söylerse, tarayıcı gereğinden çok büyük
adayı seçer. Fark 2,3 ile 9 kat arasında değişiyor.

Hatanın gizlenmesi kolaydır, çünkü görsel doğru görünür — yalnız gereğinden büyük bir dosyayla.
Denetim yolu şudur: kaynak boyutu bildirimi, biçem dosyasındaki genişlik kurallarının bir
kopyasıdır ve düzen değiştiğinde birlikte güncellenmelidir. İkisinin ayrı yerlerde durması bu
yazımın yapısal zayıflığıdır.

İki not daha. Aday kümesi genişlik yerine piksel oranı tanımlayıcısıyla da yazılabilir
(`gorsel.jpg 1x, gorsel@2x.jpg 2x`); bu yazımda kaynak boyutu hesaba katılmaz ve yalnız aygıtın
piksel oranı seçer. Sabit ölçülü küçük görsellerde — bir simge, bir rozet — yeterlidir.

İkincisi, buradaki seçim kuralı bir modeldir. Aday seçimi tarayıcının takdirine bırakılmıştır;
ağ koşullarına ya da kullanıcı ayarına göre daha küçük bir aday seçilebilir. Yazılan
bildirimin görevi doğru bilgiyi vermektir, seçimi zorlamak değil.

#### Sanat Yönetimi

İkinci sorun ölçüyle çözülmez. Geniş bir panorama karesi dar bir ekranda ne kadar küçültülürse
küçültülsün, ölçüm istasyonunun bulunduğu nokta ayırt edilemez hâle gelir. Gereken, aynı karenin
farklı bir kırpımıdır.

Bu ayrıma **sanat yönetimi (art direction)** denir ve aracı `picture` ögesidir. Öge, koşullu
`source` ögeleri ve bir `img` ögesi taşır.

```js
// sanat.mjs — picture ogesinde kaynak secimi ve ayrilan yerin hesabi
// Kural: kaynaklar belge sirasinda denenir, kosulu dogru olan ILK kaynak secilir.

const KAYNAKLAR = [
  { ad: "konum-genis", kosul: "(min-width: 812px)", en: 1440, boy: 600 },   // 12:5 panorama
  { ad: "konum-orta", kosul: "(min-width: 500px)", en: 960, boy: 640 },     // 3:2
  { ad: "konum-dar", kosul: null, en: 480, boy: 600 },                      // 4:5 kirpik
];

const SIZES = [
  ["(min-width: 812px)", 0.33],
  ["(min-width: 500px)", 0.5],
  [null, null],   // calc(100vw - 48px)
];

function kosulDogru(kosul, V) {
  if (kosul === null) return true;
  const m = kosul.match(/^\(min-width:\s*(\d+)px\)$/);
  return V >= Number(m[1]);
}

function gosterilenGenislik(V) {
  for (const [kosul, oran] of SIZES) {
    if (kosulDogru(kosul, V)) return oran === null ? V - 48 : V * oran;
  }
}

function sec(liste, V) {
  return liste.find((k) => kosulDogru(k.kosul, V));
}

console.log("kaynaklar belge sirasinda:");
for (const k of KAYNAKLAR) {
  console.log(`  ${k.ad.padEnd(12)} ${String(k.kosul ?? "(kosulsuz img)").padEnd(20)} ${k.en}x${k.boy}  en boy orani ${(k.en / k.boy).toFixed(2)}`);
}

console.log("\ngorus  gosterilen  secilen       en boy  ayrilan");
console.log("alani  genislik    kaynak        orani   yukseklik");
for (const V of [360, 500, 700, 812, 1280, 1600]) {
  const g = gosterilenGenislik(V);
  const k = sec(KAYNAKLAR, V);
  const oran = k.en / k.boy;
  console.log(
    `${String(V).padStart(5)}  ${g.toFixed(0).padStart(10)}  ${k.ad.padEnd(12)}  ` +
      `${oran.toFixed(2).padStart(6)}  ${(g / oran).toFixed(0).padStart(9)}`,
  );
}

// sira bozulursa: kosullu kaynaklar dardan genise siralanirsa
const TERS = [KAYNAKLAR[1], KAYNAKLAR[0], KAYNAKLAR[2]];
console.log("\n--- kosullu kaynaklar dardan genise siralanirsa ---");
console.log("gorus  secilen kaynak  dogru secim  ayni mi");
for (const V of [360, 500, 812, 1280]) {
  const t = sec(TERS, V), d = sec(KAYNAKLAR, V);
  console.log(
    `${String(V).padStart(5)}  ${t.ad.padEnd(14)}  ${d.ad.padEnd(11)}  ${(t.ad === d.ad ? "evet" : "HAYIR").padStart(7)}`,
  );
}

// ayrilan yer kaynaktan kaynaga degisir: her kaynak kendi olculerini bildirmezse
console.log("\n--- ayni gosterilen genislikte (500px) kaynaklarin yukseklikleri ---");
for (const k of KAYNAKLAR) {
  console.log(`  ${k.ad.padEnd(12)} ${(500 / (k.en / k.boy)).toFixed(0).padStart(4)}px`);
}
```

```
kaynaklar belge sirasinda:
  konum-genis  (min-width: 812px)   1440x600  en boy orani 2.40
  konum-orta   (min-width: 500px)   960x640  en boy orani 1.50
  konum-dar    (kosulsuz img)       480x600  en boy orani 0.80

gorus  gosterilen  secilen       en boy  ayrilan
alani  genislik    kaynak        orani   yukseklik
  360         312  konum-dar       0.80        390
  500         250  konum-orta      1.50        167
  700         350  konum-orta      1.50        233
  812         268  konum-genis     2.40        112
 1280         422  konum-genis     2.40        176
 1600         528  konum-genis     2.40        220

--- kosullu kaynaklar dardan genise siralanirsa ---
gorus  secilen kaynak  dogru secim  ayni mi
  360  konum-dar       konum-dar       evet
  500  konum-orta      konum-orta      evet
  812  konum-orta      konum-genis    HAYIR
 1280  konum-orta      konum-genis    HAYIR

--- ayni gosterilen genislikte (500px) kaynaklarin yukseklikleri ---
  konum-genis   208px
  konum-orta    333px
  konum-dar     625px
```

Üç kaynak, üç ayrı en boy oranı. Dar düzende dikey bir kırpım kullanılıyor: aynı genişlikte daha
çok dikey yer kaplıyor ve istasyonun bulunduğu nokta ölçekte kalıyor. Geniş düzende panorama
kırpımı, çevredeki arazi biçimini de gösteriyor.

İkinci tablo, ortam sorgularından ayrılan bir kuralı ölçüyor. Biçem dosyasında eşleşen
kuralların **sonuncusu** kazanıyordu; `picture` içinde koşulu doğru olan **ilki** seçilir.
Kaynaklar dardan genişe sıralandığında geniş koşullar hiç sıraya gelmez: 812 ve 1280 birimde
orta kırpım seçilir ve panorama kullanılmaz. Kaynaklar geniş koşuldan dar koşula doğru sıralanır.

Üçüncü blok, kırpımların birbirinden ne kadar farklı yer kapladığını gösteriyor. Aynı 500
birimlik genişlikte panorama 208, dikey kırpım 625 birim yükseklik ister. Bu fark, ayrılan yer
sorununu doğrudan doğurur.

#### Ayrılan Yer

Görsel indirilmeden önce tarayıcı ona ne kadar yer ayıracağını bilemez. Bilmezse yer ayırmaz;
görsel geldiğinde altındaki içerik aşağı itilir. Web'in Temelleri ve HTML kursunda tanıtılan
**içerik kayması** budur.

Çözüm, en boy oranını belgeye yazmaktır. `img` ögesine `width` ve `height` öznitelikleri
verildiğinde tarayıcı oranı ikisinin bölümünden hesaplar ve görselin kaplayacağı kutuyu
önceden ayırır. Öznitelikler piksel değeridir ama sabit ölçü anlamına gelmez; biçem dosyasındaki
`inline-size` bildirimi görselin gerçek genişliğini belirler, öznitelikler yalnız oranı taşır.

Sanat yönetiminde bir ek gerekir. Her kırpımın oranı farklı olduğu için ayrılacak yer de
farklıdır; bu yüzden **her** `source` ögesi kendi `width` ve `height` değerlerini bildirir. Tek
bir oran yazıldığında kaynak değiştiği anda yer yeniden hesaplanır ve kayma oluşur.

Aynı sonuç biçem tarafında `aspect-ratio` bildirimiyle de kurulabilir; iki yol birlikte
kullanıldığında biçem kazanır.

```html
<figure class="konum-gorseli">
  <picture>
    <source media="(min-width: 812px)"
            srcset="konum-genis-960.jpg 960w, konum-genis-1440.jpg 1440w"
            sizes="33vw" width="1440" height="600">
    <source media="(min-width: 500px)"
            srcset="konum-orta-640.jpg 640w, konum-orta-960.jpg 960w"
            sizes="50vw" width="960" height="640">
    <img src="konum-dar-480.jpg"
         srcset="konum-dar-320.jpg 320w, konum-dar-480.jpg 480w"
         sizes="calc(100vw - 48px)"
         width="480" height="600"
         loading="lazy" decoding="async"
         alt="Kuzey Yamaç ölçüm istasyonunun bulunduğu sırtın güneybatıdan görünümü">
  </picture>
  <figcaption>Ölçüm istasyonu, sırtın güneybatı yamacında 1840 metrede.</figcaption>
</figure>
```

```css
/* duzen.css — 11. adım: görselin kutusu */
.konum-gorseli img {
  inline-size: 100%;
  block-size: auto;
  object-fit: cover;
  object-position: 50% 35%;
  background-color: var(--zemin);
}

.konum-gorseli figcaption {
  margin-block-start: 0.5rem;
  color: var(--metin-soluk);
  font-size: 0.875em;
}
```

Belge tarafında dört bildirim var ve her biri ayrı bir soruya yanıt veriyor: `media` hangi
kırpımın kullanılacağını, `srcset` o kırpımın hangi ölçülerde bulunduğunu, `sizes` sayfada ne
kadar yer kaplayacağını, `width` ile `height` hangi oranda yer ayrılacağını söylüyor. Alternatif
metin kırpımdan bağımsızdır ve `img` ögesinde tek bir kez yazılır — üç kaynak da aynı sahneyi
gösterir.

Biçem tarafında `inline-size: 100%` görseli kabına oturtur, `block-size: auto` oranı korur.
`object-fit: cover` bildirimi, kutunun oranı görselin oranından farklı kaldığında görseli
gerdirmek yerine kırpar; `object-position` kırpmanın hangi noktayı merkez alacağını bildirir.
Yüzde 35 değeri, ufuk çizgisinin üst üçte birde kalmasını sağlar.

Arka plan görselleri için karşılık `image-set()` fonksiyonudur; aynı aday mantığını biçem
dosyasında kurar. Ancak arka plan görselleri belgede karşılığı olmayan süsleyici görsellerdir;
içerik taşıyan bir kare arka plana konmaz.

#### Özet

- Görselde iki ayrı sorun vardır: aynı karenin ölçüsünü seçmek (çözünürlük değişimi) ve dar
  düzende farklı bir kırpım göstermek (sanat yönetimi).
- Aday seçimi iki çarpanla belirlenir: `sizes` bildiriminden çözülen kaynak boyutu ve aygıtın
  piksel oranı; görüntü alanının genişliği tek başına seçmez.
- Aday dosyaların piksel sayısı genişliğin karesiyle büyür; kaynak boyutu yanlış bildirildiğinde
  seçilen dosya 2,3 ile 9 kat arası fazla piksel taşır.
- `picture` ögesinde koşulu doğru olan **ilk** kaynak seçilir; bu, biçem dosyasındaki "sonuncusu
  kazanır" kuralının tersidir ve kaynaklar geniş koşuldan dar koşula sıralanır.
- Kırpımların en boy oranları farklı olduğu için her kaynak kendi `width` ve `height` değerlerini
  bildirir; tek bir oran yazmak kaynak değiştiğinde içerik kayması üretir.
- `object-fit` ve `object-position`, kutunun oranı görselinkinden farklı olduğunda gerdirme
  yerine kırpma ve kırpma merkezi seçimi sağlar.

#### Sonraki Adım

Buraya kadar biçem hep ortamın ölçülebilir niteliklerine uyarlandı: genişlik, kabın ölçüsü,
piksel oranı. Kullanıcının kendi tercihleri de aynı yolla okunabilir — koyu ya da açık renk
şeması isteyip istemediği, kontrastı artırmak isteyip istemediği, hareketin azaltılmasını
isteyip istemediği. Sonraki ders bu tercihleri ele alır ve sonuncusu bir soru bırakacaktır:
hareket azaltma tercihi tam olarak **neyi** azaltır?

### Kullanıcı Tercihleri

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/kullanici-tercihleri

Renk şeması, kontrast, zorunlu renkler ve hareket azaltma tercihlerinin sorgulanması, koyu paletin kontrast denetimi, tercihlerin birlikte çözülmesi ve hareket azaltmanın neyi kapsadığı.

Buraya kadar biçem hep ortamın ölçülebilir niteliklerine uyarlandı: görüntü alanının genişliği,
kabın ölçüsü, aygıtın piksel oranı. Bunların hepsi aygıtın özellikleriydi.

Kullanıcının kendisi de bir tercih bildirir. İşletim sistemi ya da tarayıcı ayarlarında koyu bir
renk şeması seçmiş, kontrastın artırılmasını istemiş ya da hareketin azaltılmasını istemiş
olabilir. Bu tercihler aynı sorgu yazımıyla okunur ve sayfa bunlara uyar.

#### Tercih Bir Ortam Özelliğidir

Dört ortam özelliği kullanıcı tercihini bildirir.

`prefers-color-scheme` açık ya da koyu bir yüzey tercihini verir; değerleri `light` ve
`dark`. `prefers-contrast` kontrastın artırılması ya da azaltılması isteğini bildirir.
`prefers-reduced-motion` hareketin azaltılması isteğini taşır ve tek anlamlı değeri `reduce`.
`forced-colors` ise ayrı bir durumu bildirir: kullanıcı, sayfanın renklerinin yerine kendi
belirlediği sınırlı bir palete zorlanmasını istemiştir.

Dördü de ortam sorgularıyla aynı biçimde yazılır ve aynı basamaklama kurallarına uyar. Ayrım
soruda değil öznededir: genişlik aygıtın, tercih kullanıcınındır.

Bir tasarım kararı hemen ortaya çıkar. Tercih sorguları biçemi **eklemeli** yazılmaya zorlar:
taban biçem `no-preference` durumunu tarif eder, sorgu bloğu ise yalnız değişeni yazar. Ters yönde
yazıldığında — koyu şema taban alınıp açık şema sorguyla geri alındığında — tercih bildirmemiş bir
kullanıcı, tasarımın istisnası olur.

#### Koyu Şemanın Kontrast Denetimi

Koyu şema, açık şemanın renklerinin ters çevrilmesi değildir. Renklerin bağıl parlaklıkları
doğrusal olmadığı için, zeminin değişmesi ön plandaki her rengin oranını yeniden hesaplamayı
gerektirir.

CSS ile Görsel Sunum kursunda tanıtılan **kontrast oranı**, iki rengin bağıl parlaklıklarından
hesaplanır. Aşağıdaki program istasyon paletini iki şemada denetliyor.

```js
// kontrast.mjs — acik ve koyu renk semasinin kontrast oranlarinin denetimi
// Bagil parlaklik ve kontrast orani WCAG tanimina gore hesaplanir.

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)];
}

function bagilParlaklik([r, g, b]) {
  const d = (c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
  return 0.2126 * d(r) + 0.7152 * d(g) + 0.0722 * d(b);
}

function oran(renk1, renk2) {
  const l1 = bagilParlaklik(renk1), l2 = bagilParlaklik(renk2);
  const [buyuk, kucuk] = l1 > l2 ? [l1, l2] : [l2, l1];
  return (buyuk + 0.05) / (kucuk + 0.05);
}

// acik sema: CSS ile Gorsel Sunum kursunda kurulan palet
const ACIK = {
  zemin: [203, 18, 97],
  metin: [211, 29, 16],
  metinSoluk: [211, 12, 45],
  uyari: [0, 66, 33],
  cizgi: [207, 15, 86],
};

// koyu sema, yalniz zemin ve ana metin degistirilmis; kalanlar oldugu gibi birakilmis
const KOYU_EKSIK = {
  zemin: [211, 22, 12],
  metin: [211, 20, 92],
  metinSoluk: ACIK.metinSoluk,
  uyari: ACIK.uyari,
  cizgi: ACIK.cizgi,
};

const HEDEF = { metin: 4.5, metinSoluk: 4.5, uyari: 4.5, cizgi: 3 };

function tablo(baslik, sema) {
  console.log(`\n--- ${baslik} ---`);
  console.log(`zemin: hsl(${sema.zemin.join(" ")})`);
  console.log("on plan      hsl           hedef   oran   sonuc");
  for (const ad of ["metin", "metinSoluk", "uyari", "cizgi"]) {
    const o = oran(hslRgb(...sema[ad]), hslRgb(...sema.zemin));
    console.log(
      `${ad.padEnd(11)}  ${("hsl(" + sema[ad].join(" ") + ")").padEnd(14)}` +
        `${(HEDEF[ad] + ":1").padStart(6)}  ${o.toFixed(2).padStart(5)}  ${(o >= HEDEF[ad] ? "gecer" : "KALIR").padStart(5)}`,
    );
  }
}

tablo("acik sema", ACIK);
tablo("koyu sema: yalniz zemin ve metin degistirilmis", KOYU_EKSIK);

// duzeltme: her on plan icin hedefi saglayan en dusuk aydinlik degeri aranir
console.log("\n--- koyu semada gereken aydinlik degerleri ---");
console.log("on plan      su anki L  gereken L  su anki oran  duzeltilmis oran");
for (const ad of ["metin", "metinSoluk", "uyari", "cizgi"]) {
  const [h, s, l] = KOYU_EKSIK[ad];
  let gereken = l;
  while (gereken <= 100 && oran(hslRgb(h, s, gereken), hslRgb(...KOYU_EKSIK.zemin)) < HEDEF[ad]) {
    gereken += 0.5;
  }
  console.log(
    `${ad.padEnd(11)}  ${l.toFixed(1).padStart(9)}  ${gereken.toFixed(1).padStart(9)}  ` +
      `${oran(hslRgb(h, s, l), hslRgb(...KOYU_EKSIK.zemin)).toFixed(2).padStart(12)}  ` +
      `${oran(hslRgb(h, s, gereken), hslRgb(...KOYU_EKSIK.zemin)).toFixed(2).padStart(16)}`,
  );
}
```

```
--- acik sema ---
zemin: hsl(203 18 97)
on plan      hsl           hedef   oran   sonuc
metin        hsl(211 29 16) 4.5:1  13.93  gecer
metinSoluk   hsl(211 12 45) 4.5:1   4.59  gecer
uyari        hsl(0 66 33)   4.5:1   8.56  gecer
cizgi        hsl(207 15 86)   3:1   1.30  KALIR

--- koyu sema: yalniz zemin ve metin degistirilmis ---
zemin: hsl(211 22 12)
on plan      hsl           hedef   oran   sonuc
metin        hsl(211 20 92) 4.5:1  13.90  gecer
metinSoluk   hsl(211 12 45) 4.5:1   3.42  KALIR
uyari        hsl(0 66 33)   4.5:1   1.83  KALIR
cizgi        hsl(207 15 86)   3:1  12.08  gecer

--- koyu semada gereken aydinlik degerleri ---
on plan      su anki L  gereken L  su anki oran  duzeltilmis oran
metin             92.0       92.0         13.90             13.90
metinSoluk        45.0       53.0          3.42              4.54
uyari             33.0       61.0          1.83              4.53
cizgi             86.0       86.0         12.08             12.08
```

Açık şemada üç metin rengi de 4,5:1 ölçütünü karşılıyor. Ayraç çizgisi 1,30 oranıyla 3:1
ölçütünün altında — bu bir kusur değil, kapsam sorusudur. WCAG 1.4.11 ölçütü metin dışı
kontrastı arayüz bileşenlerinin ve durum göstergelerinin sınırları için ister; bir çizelgedeki
satır ayracı süsleyicidir. Aynı renk bir girdi kenarlığı ya da odak göstergesi olarak
kullanılacaksa ölçütü karşılamaz ve koyulaştırılması gerekir.

Koyu şemada tablo tersine dönüyor. Zemin ve ana metin değiştirilmiş; ikisi arasında 13,90 oran
var ve sorun görünmüyor. Ama değiştirilmemiş iki renk düşüyor: soluk metin 3,42, uyarı rengi
1,83. Uyarı rengi, kullanıcıyı uyarması gereken renktir ve koyu zeminde neredeyse okunamaz.

Üçüncü blok düzeltmeyi hesaplıyor. Soluk metnin aydınlık değeri 45'ten 53'e, uyarı renginin 33'ten
61'e çıkarıldığında ölçüt karşılanıyor. Uyarı renginde gereken 28 puanlık artış, doğrudan
çevirmenin neden yetmediğini gösteriyor: kırmızının bağıl parlaklığı düşüktür ve koyu zeminde
ayırt edilmesi için çok daha açık bir tona çıkması gerekir.

Kural şudur: koyu şema, açık şemanın **her** renginin yeniden denetlenmesidir. Denetlenmeden
bırakılan renk, sessizce ölçüt dışına düşebilen renktir — buradaki üç değiştirilmemiş renkten
ikisi düştü, ayraç çizgisi ise hedefi zaten karşılıyordu.

#### Tercihlerin Birlikte Çözülmesi

Kullanıcı birden çok tercihi aynı anda bildirebilir. Sorgular birbirinden bağımsız olduğu için
sonuç, eşleşen bütün blokların basamaklamadaki birleşimidir.

```js
// tercih.mjs — kullanici tercihi sorgularinin cozulmesi ve sonucun belirlenmesi

// Kurallar kaynak sirasinda; kosulu dogru olanlarin sonuncusu kazanir.
const KURALLAR = [
  { kosul: null, deger: { zemin: "acik", cizgi: "ince", gecis: "200ms", kaydirma: "yumusak" } },
  { kosul: "prefers-color-scheme: dark", deger: { zemin: "koyu" } },
  { kosul: "prefers-contrast: more", deger: { cizgi: "kalin" } },
  { kosul: "prefers-reduced-motion: reduce", deger: { gecis: "1ms", kaydirma: "ani" } },
  { kosul: "forced-colors: active", deger: { zemin: "sistem", cizgi: "sistem" } },
];

const PROFILLER = [
  { ad: "varsayilan", "prefers-color-scheme": "light", "prefers-contrast": "no-preference", "prefers-reduced-motion": "no-preference", "forced-colors": "none" },
  { ad: "koyu sema", "prefers-color-scheme": "dark", "prefers-contrast": "no-preference", "prefers-reduced-motion": "no-preference", "forced-colors": "none" },
  { ad: "koyu + az hareket", "prefers-color-scheme": "dark", "prefers-contrast": "no-preference", "prefers-reduced-motion": "reduce", "forced-colors": "none" },
  { ad: "yuksek kontrast", "prefers-color-scheme": "light", "prefers-contrast": "more", "prefers-reduced-motion": "no-preference", "forced-colors": "none" },
  { ad: "zorunlu renkler", "prefers-color-scheme": "dark", "prefers-contrast": "more", "prefers-reduced-motion": "reduce", "forced-colors": "active" },
];

function eslesir(kosul, profil) {
  if (kosul === null) return true;
  const [ozellik, deger] = kosul.split(":").map((s) => s.trim());
  return profil[ozellik] === deger;
}

function coz(profil) {
  const sonuc = {};
  const eslesen = [];
  for (const k of KURALLAR) {
    if (!eslesir(k.kosul, profil)) continue;
    eslesen.push(k.kosul ?? "taban");
    Object.assign(sonuc, k.deger);
  }
  return { sonuc, eslesen };
}

console.log("kurallar:");
for (const k of KURALLAR) {
  console.log(`  ${(k.kosul ?? "(taban)").padEnd(32)} ${JSON.stringify(k.deger)}`);
}

console.log("\nprofil             zemin    cizgi    gecis  kaydirma  eslesen kural sayisi");
for (const p of PROFILLER) {
  const { sonuc, eslesen } = coz(p);
  console.log(
    `${p.ad.padEnd(17)}  ${sonuc.zemin.padEnd(7)}  ${sonuc.cizgi.padEnd(7)}  ` +
      `${sonuc.gecis.padStart(5)}  ${sonuc.kaydirma.padEnd(8)}  ${String(eslesen.length).padStart(19)}`,
  );
}

// color-scheme bildirimi: tarayici denetimleri ve kaydirma cubugu hangi semayi kullanir
console.log("\n--- color-scheme bildiriminin tarayici denetimlerine etkisi ---");
console.log("kullanici tercihi  color-scheme bildirimi  denetimler  sayfa    uyumlu mu");
for (const tercih of ["light", "dark"]) {
  for (const bildirim of ["normal", "light", "light dark"]) {
    const destekli = bildirim.split(" ").includes(tercih);
    const denetim = destekli ? tercih : bildirim === "normal" ? "light" : bildirim.split(" ")[0];
    const sayfa = tercih;   // sayfanin kendi renkleri sorguyla zaten uyarlanmis
    console.log(
      `${tercih.padEnd(17)}  ${bildirim.padEnd(22)}  ${denetim.padEnd(10)}  ${sayfa.padEnd(7)}  ` +
        `${(denetim === sayfa ? "evet" : "HAYIR").padStart(9)}`,
    );
  }
}

// hareket azaltma neyi azaltir: ozellik turune gore politika
console.log("\n--- hareket azaltma tercihinde ozellik turlerine gore sure ---");
const HAREKETLER = [
  { ad: "translate/scale", tur: "yer degistirme", taban: 300 },
  { ad: "rotate", tur: "yer degistirme", taban: 400 },
  { ad: "opacity", tur: "gorunurluk", taban: 200 },
  { ad: "background-color", tur: "renk", taban: 150 },
  { ad: "surekli donen bant", tur: "surekli hareket", taban: 8000 },
];
console.log("ozellik                 tur               taban   azaltilmis  karar");
for (const h of HAREKETLER) {
  const kapatilir = h.tur === "yer degistirme" || h.tur === "surekli hareket";
  const yeni = kapatilir ? 0 : h.taban;
  const karar = kapatilir ? "kaldirilir" : "korunur";
  console.log(
    `${h.ad.padEnd(22)}  ${h.tur.padEnd(16)}  ${String(h.taban + "ms").padStart(6)}  ` +
      `${String(yeni + "ms").padStart(10)}  ${karar.padStart(11)}`,
  );
}
```

```
kurallar:
  (taban)                          {"zemin":"acik","cizgi":"ince","gecis":"200ms","kaydirma":"yumusak"}
  prefers-color-scheme: dark       {"zemin":"koyu"}
  prefers-contrast: more           {"cizgi":"kalin"}
  prefers-reduced-motion: reduce   {"gecis":"1ms","kaydirma":"ani"}
  forced-colors: active            {"zemin":"sistem","cizgi":"sistem"}

profil             zemin    cizgi    gecis  kaydirma  eslesen kural sayisi
varsayilan         acik     ince     200ms  yumusak                     1
koyu sema          koyu     ince     200ms  yumusak                     2
koyu + az hareket  koyu     ince       1ms  ani                         3
yuksek kontrast    acik     kalin    200ms  yumusak                     2
zorunlu renkler    sistem   sistem     1ms  ani                         5

--- color-scheme bildiriminin tarayici denetimlerine etkisi ---
kullanici tercihi  color-scheme bildirimi  denetimler  sayfa    uyumlu mu
light              normal                  light       light         evet
light              light                   light       light         evet
light              light dark              light       light         evet
dark               normal                  light       dark         HAYIR
dark               light                   light       dark         HAYIR
dark               light dark              dark        dark          evet

--- hareket azaltma tercihinde ozellik turlerine gore sure ---
ozellik                 tur               taban   azaltilmis  karar
translate/scale         yer degistirme     300ms         0ms   kaldirilir
rotate                  yer degistirme     400ms         0ms   kaldirilir
opacity                 gorunurluk         200ms       200ms      korunur
background-color        renk               150ms       150ms      korunur
surekli donen bant      surekli hareket   8000ms         0ms   kaldirilir
```

Birinci tablo, tercihlerin birbirini engellemediğini gösteriyor. Koyu şema ve hareket azaltma
birlikte bildirildiğinde ikisi de uygulanır; her sorgu kendi özelliğini yazar ve birbirinin
yazdığını ezmez.

Son satır bir istisnayı işaretliyor. Zorunlu renkler etkinken sayfanın renk bildirimlerinin
çoğu yok sayılır ve yerine sistemin belirlediği sınırlı bir palet konur. Bu, kullanıcı için
kesin bir gereksinimdir; sayfanın yapması gereken, renk yerine **başka** bir ayrım aracı
bırakmaktır. Yalnız renkle ayrılan bir durum — örneğin eksik ölçümü kırmızıyla göstermek —
zorunlu renklerde tamamen kaybolur; aynı bilgi bir simge ya da metinle de verilmelidir.

#### Tarayıcının Kendi Parçaları

İkinci tablo, sayfanın renklerini değiştirmenin yetmediği bir alanı gösteriyor.

Kaydırma çubuğu, form denetimlerinin varsayılan görünümü, metin seçimi rengi — bunlar tarayıcının
çizdiği parçalardır ve sayfanın renk bildirimlerinden etkilenmezler. Hangi şemayı kullanacaklarını
`color-scheme` bildirimi söyler.

Bildirim yazılmadığında ya da yalnız `light` yazıldığında, kullanıcı koyu şema tercih etse bile
denetimler açık kalır: koyu bir sayfada parlak beyaz bir kaydırma çubuğu ve açık zeminli girdiler
görünür. `color-scheme: light dark` bildirimi iki şemayı da desteklediğini bildirir ve denetimler
kullanıcının tercihini izler.

Bildirimin ikinci bir sonucu daha vardır: `light-dark()` fonksiyonu ancak bir şema seçili
olduğunda çalışır. Fonksiyon iki değer alır ve etkin şemaya göre birini seçer; koyu şema
kurallarını ayrı bir sorgu bloğuna yazmak yerine değer düzeyinde ayırmayı sağlar.

#### Hareket Azaltma Neyi Azaltır

Üçüncü tablo, bu dersin bıraktığı asıl soruya geliyor.

`prefers-reduced-motion` tercihinin adı yanıltıcı olabilir: tercih, **animasyonun kapatılmasını**
değil hareketin azaltılmasını ister. Ayrım, hangi özelliğin değiştiğine bakılarak yapılır.

Bir ögenin **yer değiştirmesi** — kayması, dönmesi, ölçeklenmesi — göz için bir devinim
üretir. Vestibüler duyarlılığı olan kullanıcılarda büyük ya da beklenmedik devinimler baş
dönmesi ve mide bulantısına yol açabilir. Tercihi bildiren kullanıcı bu devinimden kaçınmak
ister.

Bir ögenin **görünürlüğünün** ya da **renginin** değişmesi devinim üretmez. Bir düğmenin
üzerine gelindiğinde arka planının 150 milisaniyede değişmesi, hareket azaltma tercihinde de
korunabilir; hatta korunmalıdır, çünkü bu geçiş durum değişikliğinin okunmasını kolaylaştırır.

Tablodaki karar sütunu bu ayrımı uyguluyor: yer değiştirme üreten ve sürekli tekrarlanan
hareketler kaldırılıyor, görünürlük ve renk geçişleri korunuyor. Bütün geçişleri tek bir kuralla
sıfırlamak — evrensel seçiciye sıfır süre yazmak — kolay bir çözümdür ama arayüzü ani ve okunması
zor hâle getirir.

WCAG 2.3.3 ölçütü bu politikayı doğrudan tarif eder: etkileşimle tetiklenen hareket
animasyonları, işlev için gerekli değilse kapatılabilmelidir.

```css
/* duzen.css — 12. adım: kullanıcı tercihleri */
:root {
  color-scheme: light dark;

  --zemin:       hsl(203 18% 97%);
  --metin:       hsl(211 29% 16%);
  --metin-soluk: hsl(211 12% 45%);
  --uyari:       hsl(0 66% 33%);
  --cizgi:       hsl(207 15% 86%);
  --gecis-suresi: 200ms;
}

@media (prefers-color-scheme: dark) {
  :root {
    --zemin:       hsl(211 22% 12%);
    --metin:       hsl(211 20% 92%);
    --metin-soluk: hsl(211 12% 53%);
    --uyari:       hsl(0 66% 61%);
  }
}

@media (prefers-contrast: more) {
  :root {
    --metin-soluk: var(--metin);
    --cizgi: var(--metin);
  }
}

@media (prefers-reduced-motion: reduce) {
  :root { --gecis-suresi: 1ms; }
  html { scroll-behavior: auto; }
}

@media (forced-colors: active) {
  .olcum-cizelgesi .eksik { forced-color-adjust: none; }
  .olcum-cizelgesi .eksik::before { content: "! "; }
}
```

Beş blok, beş ayrı soru. Taban blok açık şemayı ve varsayılan süreyi tanımlıyor; kalan dördü
yalnız değişen özel özellikleri yazıyor. Koyu şemadaki aydınlık değerleri yukarıdaki hesabın
sonucu: soluk metin 53, uyarı 61. Zorunlu renkler bloğu iki şey yapıyor: `forced-color-adjust:
none` ile eksik ölçümün anlamlı rengini zorunlu paletin dışında tutuyor, `::before` ile de renge
hiç bağlı olmayan bir metin işareti ekliyor.

Bütün bildirimler özel özellikler üzerinden geçtiği için bileşen kuralları hiç değişmiyor. Bir
kart, bir çizelge satırı ya da bir düğme `var(--metin)` yazdığında dört tercihin bileşimi zaten
çözülmüş olur.

#### Özet

- Dört ortam özelliği kullanıcı tercihini bildirir: renk şeması, kontrast, hareket azaltma ve
  zorunlu renkler; hepsi ortam sorgularıyla aynı biçimde yazılır.
- Taban biçem tercih bildirmemiş kullanıcıyı tarif eder; sorgu blokları yalnız değişeni yazar ve
  hiçbirini geri almaz.
- Koyu şema, açık şemanın çevrilmesi değildir: zemin değiştiğinde her ön plan renginin oranı
  yeniden hesaplanır; istasyon paletinde uyarı renginin aydınlık değeri 33'ten 61'e çıkmak
  zorundadır.
- Tercihler birbirinden bağımsızdır ve birlikte bildirildiklerinde hepsi uygulanır; zorunlu
  renkler etkinken yalnız renkle verilen ayrımlar kaybolur ve metin ya da simge yedeği gerekir.
- `color-scheme` bildirimi tarayıcının kendi çizdiği parçaların hangi şemayı izleyeceğini söyler;
  yazılmadığında koyu bir sayfada açık denetimler kalır.
- Hareket azaltma tercihi yer değiştirme üreten ve sürekli tekrarlanan hareketleri kaldırır;
  görünürlük ve renk geçişleri korunur, çünkü devinim üretmezler.

#### Sonraki Adım

Son tablo bir ayrım kurdu ama tanımı yapmadan kullandı: "yer değiştirme üreten" özellik hangisi?
Bir ögeyi kaydıran, döndüren ve ölçekleyen bildirimlerin ortak bir adı ve ortak bir matematiği
vardır; ikisini de görmeden hangi hareketin azaltılacağına karar vermek sezgiye kalır. Sonraki
konu buradan başlar: bir öge kendi kutusunu bozmadan nasıl ötelenir, ölçeklenir ve döndürülür,
bu işlemler hangi sırayla uygulanır ve sıra neden sonucu değiştirir?

## Hareket ve Dönüşüm

Dönüşümlerin matris karşılığı ve sıra bağımlılığı, geçiş ile anahtar kare animasyonlarının zamanlaması, hareketin çizim hattındaki maliyeti ve hareket azaltma tercihine saygı.

### Dönüşümler

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/donusumler

Öteleme, ölçekleme, döndürme ve eğme fonksiyonlarının matris karşılığı; fonksiyon sırasının sonucu değiştirmesi, dönüşüm kaynağının etkisi ve dönüşümün düzenden sonra uygulanması.

Bir önceki ders kullanıcı tercihlerini okudu: renk şeması ve hareket azaltma. Renk şeması
tercihinin neyi değiştirdiği bellidir — renk bildirimlerini. Hareket azaltma tercihi ise
"yer değiştirme üreten" bildirimleri kaldırır ve o bildirimlerin ne olduğu henüz
tanımlanmadı.

Bu konu o tanımı kurar. Sayfadaki hareket üç katmandan gelir: kutuyu kendi yerinde öteleyen,
ölçekleyen ve döndüren geometrik işlemler, iki durum arasındaki değişimin zamana yayılması
ve zaman çizelgesiyle sürülen anahtar kare dizileri. Bu ders birincisini ele alır. Üç
işlemin ortak bir adı ve ortak bir matematiği vardır; ikisi de görüldüğünde hangi hareketin
neyi hareket ettirdiği ölçülebilir bir soru hâline gelir.

#### Dönüşüm Düzenden Sonra Uygulanır

**Dönüşüm (transform)** bir ögenin kutusunu ötelemek, ölçeklemek, döndürmek veya eğmek için
kullanılır. Belirleyici özelliği, düzen hesabına **dokunmamasıdır**.

Sıra şudur: tarayıcı düzeni hesaplar, kutunun yerini ve ölçüsünü bulur, sonra dönüşümü o
kutuya uygular. Ögenin düzendeki yeri değişmez; komşuları hiçbir şey olmamış gibi durur.
Bir ögeyi `transform: translateX(40px)` ile sağa itmek, `margin-inline-start: 40px` ile
itmekten bu yüzden farklıdır: ikincisi kapsayan bloğun içindeki bütün akışı yeniden
hesaplatır, birincisi yalnız o kutunun çizimini değiştirir.

Bunun üç sonucu vardır:

1. Dönüşmüş öge komşularının üzerine taşabilir; taşma hesabı düzenden gelir, dönüşümden
   değil.
2. Yüzde değerleri **ögenin kendi kenarlık kutusuna** göre okunur. `translateX(50%)` ögenin
   kendi genişliğinin yarısıdır; oysa `left: 50%` kapsayan bloğun genişliğinin yarısıdır.
3. `none` dışında bir değer taşıyan dönüşüm, yeni bir **yığılma bağlamı** kurar ve içindeki
   `fixed` ve `absolute` konumlu bütün torunlar için **kapsayan blok** olur. Bu kural CSS
   ile Görsel Sunum kursunun Yığılma Bağlamı dersinde tanımlanmıştı; hareket yazarken sık
   sık geri döner.

Üçüncü madde bir hata kaynağıdır. Sayfanın tamamını saran bir kapsayıcıya küçük bir dönüşüm
yazmak, o kapsayıcının içindeki sabit konumlu bir başlığı ekrana değil kapsayıcıya
sabitlenmiş duruma getirir.

#### Fonksiyonlar ve Matris Karşılıkları

Dönüşüm özelliği bir **fonksiyon listesi** alır. Listedeki her fonksiyon bir $3 \times 3$
matrise karşılık gelir ve liste bu matrislerin çarpımıdır. İki boyutta matrisin son satırı
her zaman $(0, 0, 1)$ olduğu için altı sayı yeterlidir; `matrix()` fonksiyonunun aldığı
sayılar bunlardır.

$$
\begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{pmatrix}
\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}
=
\begin{pmatrix} ax + cy + e \\ bx + dy + f \end{pmatrix}
$$

Bir nokta bu çarpımla yeni yerine taşınır. Aşağıdaki program fonksiyonları matrise çevirir,
listeleri çarpar ve bir kartın dört köşesini izler.

```js
// donusum.mjs — donusum fonksiyonlarinin matris karsiligi ve sira bagimliligi
// matris [a, b, c, d, e, f] -> | a c e |
//                              | b d f |
//                              | 0 0 1 |
const carp = (m, n) => [
  m[0] * n[0] + m[2] * n[1],
  m[1] * n[0] + m[3] * n[1],
  m[0] * n[2] + m[2] * n[3],
  m[1] * n[2] + m[3] * n[3],
  m[0] * n[4] + m[2] * n[5] + m[4],
  m[1] * n[4] + m[3] * n[5] + m[5],
];

const rad = (d) => (d * Math.PI) / 180;
const oteleme = (tx, ty) => [1, 0, 0, 1, tx, ty];
const olcekleme = (sx, sy) => [sx, 0, 0, sy, 0, 0];
const dondurme = (d) => [Math.cos(rad(d)), Math.sin(rad(d)), -Math.sin(rad(d)), Math.cos(rad(d)), 0, 0];
const egmeX = (d) => [1, 0, Math.tan(rad(d)), 1, 0, 0];
const egmeY = (d) => [1, Math.tan(rad(d)), 0, 1, 0, 0];

const BIRIM = [1, 0, 0, 1, 0, 0];
const zincir = (liste) => liste.reduce((t, m) => carp(t, m), BIRIM);
const yaz = (m) => "matrix(" + m.map((v) => (Math.abs(v) < 1e-9 ? 0 : +v.toFixed(4))).join(", ") + ")";
const nokta = (m, [x, y]) => [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
const p = ([x, y]) => `(${x.toFixed(1)}, ${y.toFixed(1)})`;

console.log("--- tek fonksiyonlarin matris karsiligi ---");
console.log("translate(40px, 12px) ->", yaz(oteleme(40, 12)));
console.log("scale(1.25, 1.25)     ->", yaz(olcekleme(1.25, 1.25)));
console.log("rotate(30deg)         ->", yaz(dondurme(30)));
console.log("skewX(12deg)          ->", yaz(egmeX(12)));
console.log("skewY(12deg)          ->", yaz(egmeY(12)));

console.log("\n--- sira bagimliligi: ayni iki fonksiyon, iki yazim ---");
const A = zincir([oteleme(120, 0), dondurme(30)]);
const B = zincir([dondurme(30), oteleme(120, 0)]);
console.log("translate(120px, 0) rotate(30deg) ->", yaz(A));
console.log("rotate(30deg) translate(120px, 0) ->", yaz(B));

const KOSE = [[0, 0], [220, 0], [220, 120], [0, 120]];   // 220x120 birimlik kart
console.log("\nkose noktalari (sol ust, sag ust, sag alt, sol alt):");
console.log("  yazim 1:", KOSE.map((k) => p(nokta(A, k))).join(" "));
console.log("  yazim 2:", KOSE.map((k) => p(nokta(B, k))).join(" "));

console.log("\n--- transform-origin: T(o) * M * T(-o) ---");
const ORIJIN = [[0, 0], [110, 60], [220, 120]];          // sol ust, merkez, sag alt
for (const [ox, oy] of ORIJIN) {
  const M = zincir([oteleme(ox, oy), dondurme(30), oteleme(-ox, -oy)]);
  console.log(`origin (${ox}, ${oy})`.padEnd(18), yaz(M));
  console.log("  koseler:", KOSE.map((k) => p(nokta(M, k))).join(" "));
}

console.log("\n--- olcekleme ve otelemenin sirasi ---");
const C = zincir([olcekleme(2, 2), oteleme(50, 0)]);
const D = zincir([oteleme(50, 0), olcekleme(2, 2)]);
console.log("scale(2) translate(50px, 0) ->", yaz(C), " sol ust kose:", p(nokta(C, [0, 0])));
console.log("translate(50px, 0) scale(2) ->", yaz(D), " sol ust kose:", p(nokta(D, [0, 0])));

console.log("\n--- tek matrise indirgeme: uc fonksiyon, tek carpim ---");
const E = zincir([oteleme(0, -8), dondurme(-3), olcekleme(1.04, 1.04)]);
console.log("translate(0, -8px) rotate(-3deg) scale(1.04) ->", yaz(E));
console.log("kart koseleri:", KOSE.map((k) => p(nokta(E, k))).join(" "));
```

```
--- tek fonksiyonlarin matris karsiligi ---
translate(40px, 12px) -> matrix(1, 0, 0, 1, 40, 12)
scale(1.25, 1.25)     -> matrix(1.25, 0, 0, 1.25, 0, 0)
rotate(30deg)         -> matrix(0.866, 0.5, -0.5, 0.866, 0, 0)
skewX(12deg)          -> matrix(1, 0, 0.2126, 1, 0, 0)
skewY(12deg)          -> matrix(1, 0.2126, 0, 1, 0, 0)

--- sira bagimliligi: ayni iki fonksiyon, iki yazim ---
translate(120px, 0) rotate(30deg) -> matrix(0.866, 0.5, -0.5, 0.866, 120, 0)
rotate(30deg) translate(120px, 0) -> matrix(0.866, 0.5, -0.5, 0.866, 103.923, 60)

kose noktalari (sol ust, sag ust, sag alt, sol alt):
  yazim 1: (120.0, 0.0) (310.5, 110.0) (250.5, 213.9) (60.0, 103.9)
  yazim 2: (103.9, 60.0) (294.4, 170.0) (234.4, 273.9) (43.9, 163.9)

--- transform-origin: T(o) * M * T(-o) ---
origin (0, 0)      matrix(0.866, 0.5, -0.5, 0.866, 0, 0)
  koseler: (0.0, 0.0) (190.5, 110.0) (130.5, 213.9) (-60.0, 103.9)
origin (110, 60)   matrix(0.866, 0.5, -0.5, 0.866, 44.7372, -46.9615)
  koseler: (44.7, -47.0) (235.3, 63.0) (175.3, 167.0) (-15.3, 57.0)
origin (220, 120)  matrix(0.866, 0.5, -0.5, 0.866, 89.4744, -93.923)
  koseler: (89.5, -93.9) (280.0, 16.1) (220.0, 120.0) (29.5, 10.0)

--- olcekleme ve otelemenin sirasi ---
scale(2) translate(50px, 0) -> matrix(2, 0, 0, 2, 100, 0)  sol ust kose: (100.0, 0.0)
translate(50px, 0) scale(2) -> matrix(2, 0, 0, 2, 50, 0)  sol ust kose: (50.0, 0.0)

--- tek matrise indirgeme: uc fonksiyon, tek carpim ---
translate(0, -8px) rotate(-3deg) scale(1.04) -> matrix(1.0386, -0.0544, 0.0544, 1.0386, 0, -8)
kart koseleri: (0.0, -8.0) (228.5, -20.0) (235.0, 104.7) (6.5, 116.6)
```

İlk blok dört ailenin matriste nereye yazıldığını gösteriyor. **Öteleme (translate)** yalnız
son iki sayıya, yani $e$ ve $f$ değerlerine dokunur; sol üstteki $2 \times 2$ bölüm birim
matris kalır. **Ölçekleme (scale)** köşegene yazar. **Döndürme (rotate)** dört hücreyi de
kullanır ve değerler açının kosinüs ile sinüsüdür. **Eğme (skew)** köşegen dışı tek bir
hücreyi doldurur: `skewX` üstteki $c$ hücresini, `skewY` alttaki $b$ hücresini.

Eğmenin bir yan sonucu vardır: köşegen dışı bir hücre dolduğunda dik açılar korunmaz.
Ölçekleme ve döndürme dikdörtgeni dikdörtgen bırakır, eğme paralelkenara çevirir.

#### Sıra Bağımlılığı

Matris çarpımı **değişmeli değildir**. Çıktının ikinci bloğu bunu iki yazımla gösteriyor.
Aynı iki fonksiyon, farklı sırada yazıldığında iki farklı matris veriyor: birinde öteleme
$(120, 0)$, diğerinde $(103{,}9;\ 60)$.

Kural şudur: liste soldan sağa çarpılır, noktaya ise **sağdan sola** uygulanır. Son yazılan
fonksiyon noktaya ilk dokunandır.

- `translate(120px, 0) rotate(30deg)` — kart önce kendi yerinde döner, sonra dönmüş hâliyle
  sayfanın eksenlerinde 120 birim sağa gider. Öteleme miktarı dönmeden etkilenmez.
- `rotate(30deg) translate(120px, 0)` — kart önce 120 birim ötelenir, sonra bu öteleme de
  dâhil her şey döner. Öteleme dönmüş eksende gerçekleşmiş gibi görünür: 120 birimlik
  hareket $(103{,}9;\ 60)$ noktasına düşer, çünkü $120 \cos 30° \approx 103{,}9$ ve
  $120 \sin 30° = 60$.

Ölçekleme ile öteleme çiftinde aynı olay daha çıplaktır. `scale(2) translate(50px, 0)`
yazımında öteleme de ikiye katlanır ve kart 100 birim gider; ters yazımda 50 birim gider.
Ölçeklemeden **sonra** yazılan uzunluklar ölçeklenmiş birimlerdedir.

Bu, hareket yazarken en sık kırılan kuraldır. Bir kartın hem büyüyüp hem yukarı kalkması
isteniyorsa iki fonksiyonun sırası, hareketin yönünü ve miktarını belirler.

#### Dönüşüm Kaynağı

Döndürme ve ölçekleme sabit bir noktayı yerinde bırakır. Bu noktaya **dönüşüm kaynağı
(transform-origin)** denir ve varsayılan değeri kutunun **merkezidir**.

Matris karşılığı ayrı bir mekanizma değildir: kaynak, dönüşümü üç adımlık bir zincire
çevirir. Kaynak noktası başlangıca taşınır, dönüşüm uygulanır, nokta yerine geri götürülür.
Çıktının üçüncü bloğu üç kaynak için bu zinciri hesaplıyor.

Sonuçların okunuşu şöyledir: kaynak sol üst köşedeyken $(0, 0)$ noktası yerinde kalıyor;
kaynak sağ alt köşedeyken $(220, 120)$ noktası yerinde kalıyor. Aradaki fark $e$ ve $f$
değerlerine yansıyor — döndürme matrisinin ilk dört sayısı üç durumda da aynı.

`transform-origin` iki ya da üç değer alır; yüzdeler ögenin kendi kenarlık kutusuna göre
okunur. Kart açılırken sol kenarından açılsın isteniyorsa kaynak `left center` yazılır.

#### Üç Boyuta Geçiş

İki boyutlu listeye `translateZ`, `rotateX`, `rotateY` gibi bir fonksiyon girdiğinde matris
$4 \times 4$ olur ve `matrix3d()` on altı sayı alır. Kural aynı kalır: liste çarpılır,
noktaya sağdan sola uygulanır.

Üç boyutta iki ek bildirim devreye girer. `perspective` bir bakış uzaklığı tanımlar; küçük
değer güçlü, büyük değer zayıf bir derinlik etkisi verir. Bu değer ögenin kendisine
`transform` listesinde ya da **ana ögesine** `perspective` özelliğiyle yazılabilir; ikincisi
kardeş ögelerin ortak bir bakış noktasını paylaşmasını sağlar.

`transform-style: preserve-3d` ise alt ögelerin düzleştirilmeden kendi derinliklerini
korumasını söyler. Varsayılan `flat` değeri, alt ağacı ana ögenin düzlemine bastırır.
`backface-visibility: hidden` bildirimi, ögenin arka yüzü izleyiciye döndüğünde
çizilmemesini bildirir — iki yüzü olan bir kartın ters yüzünü gizlemek için kullanılır.

Üç boyutlu dönüşümlerin bir bedeli vardır ve bu konunun dördüncü dersinde ele alınır:
derinlik taşıyan bir öge, çizimde kendi katmanına ayrılır.

#### İstasyon Sayfasında Dönüşüm

Ölçüm kartlarına dokunma göstergesi ekleyelim. Kart, işaretçi üzerine geldiğinde bir miktar
yükselsin ve çok az büyüsün.

```css
/* hareket.css — 1. adım: kart vurgusu */
.olcum-kartlari > .kart {
  transform-origin: center bottom;
}

.olcum-kartlari > .kart:hover,
.olcum-kartlari > .kart:focus-within {
  transform: translateY(-8px) scale(1.04);
}
```

Sıra bilinçlidir. `translateY(-8px)` önce yazıldığı için 8 birimlik yükselme
ölçeklenmemiştir; ters sırada yazılsaydı yükselme $8 \times 1{,}04 = 8{,}32$ birim olurdu.
Fark küçüktür, ama aynı kural 200 birimlik bir öteleme için sekiz birimlik bir sapma üretir.

`transform-origin: center bottom` kartın alt kenarını sabitler: kart kendi tabanından
büyür, komşu kartın üzerine simetrik olarak taşmaz.

`:focus-within` sözde sınıfı, `:hover` ile aynı bildirimi klavye kullanıcısına da verir.
Kart içindeki bir bağlantı odak aldığında kart aynı biçimde yükselir.

Bu dosya `duzen.css` dosyasının yanına konur. Üçüncü bir dosyanın gerekip gerekmediği —
görsel, düzen ve hareket bildirimlerinin nasıl bölüneceği — kursun son konusunun sorusudur.

#### Özet

- Dönüşüm düzen hesabından sonra uygulanır; ögenin düzendeki yeri ve komşularının konumu
  değişmez, yalnızca çizilen kutu taşınır.
- Dönüşüm listesindeki her fonksiyon bir matristir; liste bu matrislerin çarpımıdır ve
  `matrix()` yazımı iki boyutlu bir dönüşümün altı sayısını verir.
- Çarpım değişmeli olmadığı için fonksiyon sırası sonucu değiştirir: liste soldan sağa
  çarpılır, noktaya sağdan sola uygulanır ve ölçeklemeden sonra yazılan uzunluklar
  ölçeklenmiş birimlerdedir.
- `transform-origin` ayrı bir mekanizma değil, dönüşümün önüne ve arkasına eklenen bir
  öteleme çiftidir; varsayılanı kutunun merkezidir.
- `none` dışında bir dönüşüm değeri yığılma bağlamı kurar ve sabit ile mutlak konumlu
  torunlar için kapsayan blok olur.

#### Sonraki Adım

Bu dersin bildirimleri hareketi **anlık** yapar: işaretçi kartın üzerine geldiği anda kart
sekiz birim yukarıda ve yüzde dört büyüktür, arada bir şey yoktur. Oysa hareket dediğimiz
şey iki durum arasındaki **yol**dur. Sonraki ders bu yolu tanımlayan bildirimleri ele alır:
hangi özellik ne kadar sürede, hangi hız eğrisiyle eski değerinden yenisine gider?

### Geçişler

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/gecisler

İki durum arasındaki değişimin zamana yayılması; geçebilen ve geçemeyen değerler, kübik Bézier zamanlama eğrisinin hesabı, kesilen geçişin davranışı ve kesikli zamanlama.

Bir önceki ders kartın vurgulu durumunu tanımladı: sekiz birim yukarıda, yüzde dört büyük.
Bildirim iki durumu da veriyor ama aralarında bir **yol** yok. İşaretçi kartın üzerine
geldiği anda kart yeni yerindedir.

Hareket, iki durum arasındaki yolun zamana yayılmasıdır. Bu ders o yolu tanımlayan
bildirimleri ele alır: hangi özellik ne kadar sürede, hangi hız eğrisiyle eski değerinden
yenisine gider ve yol yarıda kesilirse ne olur?

#### Geçiş İki Değer Arasını Doldurur

**Geçiş (transition)**, bir özelliğin hesaplanmış değeri değiştiğinde eski değerden yenisine
zaman içinde ilerlemesini sağlar. Değişimi geçiş başlatmaz; geçiş yalnız zaten olacak bir
değişimi yayar.

Bir uyarı gerekir: CSS ile Görsel Sunum kursunda "geçiş" sözcüğü renkler arasındaki
hesaplanmış görsel için kullanılmıştı. Bu kursta o anlam **renk geçişi** olarak anılır;
tek başına "geçiş", bu dersteki zaman içinde değişim anlamındadır.

Geçişin dört ayrı bildirimi vardır:

| Bildirim | Yanıtladığı soru |
|---|---|
| `transition-property` | Hangi özellikler yayılacak |
| `transition-duration` | Yol ne kadar sürecek |
| `transition-timing-function` | Yol boyunca hız nasıl dağılacak |
| `transition-delay` | Değişimden ne kadar sonra başlayacak |

Kısayol `transition`, bu dördünü tek satırda alır ve virgülle ayrılmış listeler her özellik
için ayrı değer yazmayı sağlar. Süre yazılmazsa varsayılan sıfırdır; süresiz bir geçiş
bildirimi hiçbir şey yapmaz.

`transition-delay` eksi değer de alabilir. Eksi gecikme, geçişi başlangıcından değil
**ortasından** başlatır: 240 milisaniyelik bir geçişe $-60$ milisaniye gecikme yazmak,
geçişi yüzde 25 ilerlemiş durumda açar.

#### Geçebilen ve Geçemeyen Değerler

Geçiş, iki değer arasında ara değer üretebilmeyi gerektirir. Uzunluk, sayı, renk ve dönüşüm
listesi için bu tanımlıdır. Anahtar sözcükler için tanımlı değildir.

Bunun sonuçları vardır. `height: auto` değerinden sabit bir yüksekliğe geçiş çalışmaz, çünkü
`auto` bir sayı değildir. `display: none` ile görünür durum arasında ara değer yoktur; görüntüleme
türü **kesikli (discrete)** bir özelliktir ve kesikli özellikler varsayılan olarak geçişe hiç
katılmaz; `transition-behavior: allow-discrete` yazıldığında katılırlar, ama ortada değil sonunda
ya da başında değişerek — açılış yönünde başlangıç değerinin ayrıca `@starting-style` ile
verilmesi gerekir.

Bir açılır bölümün yüksekliğinin yayılması isteniyorsa üç yol vardır: sabit bir üst sınır
üzerinden geçmek, ızgara şeridinin pay değerini geçirmek ya da yüksekliği hiç oynatmadan
dönüşüm ve saydamlıkla çalışmak. Üçüncüsü, sonraki derslerin göstereceği nedenle en
ucuzudur.

`visibility` özelliği bir istisnadır: kesikli olduğu hâlde ara değerlenir — uçlardan biri
`visible` olmak koşuluyla değer geçiş boyunca `visible` kalır ve yalnız uçta öteki değere düşer.
`opacity` ile birlikte yazılmasının nedeni budur: sönme bittiğinde öge etkileşime kapanır, yoksa
saydamlığı sıfırlanmış öge tıklanabilir kalırdı.

#### Zamanlama Eğrisi

**Zamanlama fonksiyonu (timing function)**, geçen sürenin oranını ilerleme oranına çevirir.
Girdi $0$ ile $1$ arasında zaman, çıktı $0$ ile $1$ arasında ilerlemedir.

Adlı fonksiyonların tamamı, $(0,0)$ ve $(1,1)$ uçlarını iki denetim noktasıyla birleştiren
**kübik Bézier** eğrisidir. `cubic-bezier(x1, y1, x2, y2)` yazımı bu iki noktayı verir.

Eğrinin değerlendirilmesi tek adımda yapılamaz: eğri $t$ parametresine göre tanımlıdır,
oysa elimizdeki zaman $x$ eksenindedir. Önce $x$ değerini veren $t$ bulunur, sonra o $t$
için $y$ okunur.

```js
// gecis.mjs — kubik Bezier zamanlama egrisinin degerlendirilmesi ve ara deger hesabi
// Zamanlama egrisi (0,0) ile (1,1) arasinda, denetim noktalari (x1,y1) ve (x2,y2).
const bez = (a, b, t) => {
  const u = 1 - t;
  return 3 * u * u * t * a + 3 * u * t * t * b + t * t * t;   // p0=0, p3=1
};

// Verilen x icin egri parametresi t ikili aramayla bulunur, sonra y okunur.
function egri(x1, y1, x2, y2) {
  return (x) => {
    if (x <= 0) return 0;
    if (x >= 1) return 1;
    let alt = 0, ust = 1, t = x;
    for (let i = 0; i < 60; i++) {
      t = (alt + ust) / 2;
      if (bez(x1, x2, t) < x) alt = t; else ust = t;
    }
    return bez(y1, y2, t);
  };
}

const ADLI = {
  linear: [0, 0, 1, 1],
  ease: [0.25, 0.1, 0.25, 1],
  "ease-in": [0.42, 0, 1, 1],
  "ease-out": [0, 0, 0.58, 1],
  "ease-in-out": [0.42, 0, 0.58, 1],
};

console.log("--- adli zamanlama fonksiyonlarinin ilerleme degerleri ---");
console.log("gecen sure".padEnd(12) + Object.keys(ADLI).map((k) => k.padStart(12)).join(""));
for (let i = 0; i <= 10; i++) {
  const x = i / 10;
  const satir = Object.values(ADLI).map((c) => egri(...c)(x).toFixed(4).padStart(12)).join("");
  console.log(`%${(x * 100).toFixed(0)}`.padEnd(12) + satir);
}

console.log("\n--- ayni ilerlemenin iki ozellige uygulanmasi (240 ms, ease-out) ---");
const f = egri(...ADLI["ease-out"]);
const SURE = 240;
const ara = (bas, son, p) => bas + (son - bas) * p;
console.log("ms".padStart(5), "ilerleme".padStart(10), "translateY".padStart(12), "scale".padStart(10));
for (const ms of [0, 30, 60, 90, 120, 180, 240]) {
  const p = f(ms / SURE);
  console.log(
    String(ms).padStart(5),
    p.toFixed(4).padStart(10),
    (ara(0, -8, p).toFixed(2) + "px").padStart(12),
    ara(1, 1.04, p).toFixed(4).padStart(10),
  );
}

console.log("\n--- gecisin ortasinda kesilmesi ---");
const KESME = 72;                     // ms; isaretci 72. ms'de karttan cikiyor
const durum = ara(0, -8, f(KESME / SURE));
console.log(`kesme aninda translateY = ${durum.toFixed(2)}px`);
console.log("geri donus, ayni sure ve ayni egriyle bu degerden 0'a gider:");
for (const ms of [0, 30, 60, 90, 120, 180, 240]) {
  const p = f(ms / SURE);
  console.log(`  ${String(ms).padStart(3)} ms -> ${ara(durum, 0, p).toFixed(2)}px`);
}

console.log("\n--- steps() ile kesikli ilerleme, 4 adim ---");
const basamak = (n, kip) => (x) => {
  if (x <= 0) return kip === "start" ? 1 / n : 0;
  if (x >= 1) return 1;
  const k = Math.floor(x * n);
  return (kip === "start" ? k + 1 : k) / n;
};
const bStart = basamak(4, "start");
const bEnd = basamak(4, "end");
console.log("gecen sure".padEnd(12) + "steps(4, start)".padStart(16) + "steps(4, end)".padStart(16));
for (let i = 0; i <= 10; i++) {
  const x = i / 10;
  console.log(
    `%${(x * 100).toFixed(0)}`.padEnd(12) +
    bStart(x).toFixed(2).padStart(16) +
    bEnd(x).toFixed(2).padStart(16),
  );
}
```

```
--- adli zamanlama fonksiyonlarinin ilerleme degerleri ---
gecen sure        linear        ease     ease-in    ease-out ease-in-out
%0                0.0000      0.0000      0.0000      0.0000      0.0000
%10               0.1000      0.0948      0.0170      0.1606      0.0197
%20               0.2000      0.2952      0.0623      0.3084      0.0817
%30               0.3000      0.5133      0.1296      0.4452      0.1874
%40               0.4000      0.6825      0.2149      0.5709      0.3319
%50               0.5000      0.8024      0.3154      0.6846      0.5000
%60               0.6000      0.8852      0.4291      0.7851      0.6681
%70               0.7000      0.9408      0.5548      0.8704      0.8126
%80               0.8000      0.9756      0.6916      0.9377      0.9183
%90               0.9000      0.9943      0.8394      0.9830      0.9803
%100              1.0000      1.0000      1.0000      1.0000      1.0000

--- ayni ilerlemenin iki ozellige uygulanmasi (240 ms, ease-out) ---
   ms   ilerleme   translateY      scale
    0     0.0000       0.00px     1.0000
   30     0.1986      -1.59px     1.0079
   60     0.3781      -3.03px     1.0151
   90     0.5405      -4.32px     1.0216
  120     0.6846      -5.48px     1.0274
  180     0.9065      -7.25px     1.0363
  240     1.0000      -8.00px     1.0400

--- gecisin ortasinda kesilmesi ---
kesme aninda translateY = -3.56px
geri donus, ayni sure ve ayni egriyle bu degerden 0'a gider:
    0 ms -> -3.56px
   30 ms -> -2.85px
   60 ms -> -2.21px
   90 ms -> -1.64px
  120 ms -> -1.12px
  180 ms -> -0.33px
  240 ms -> 0.00px

--- steps() ile kesikli ilerleme, 4 adim ---
gecen sure   steps(4, start)   steps(4, end)
%0                      0.25            0.00
%10                     0.25            0.00
%20                     0.25            0.00
%30                     0.50            0.25
%40                     0.50            0.25
%50                     0.75            0.50
%60                     0.75            0.50
%70                     0.75            0.50
%80                     1.00            0.75
%90                     1.00            0.75
%100                    1.00            1.00
```

İlk tablo beş eğriyi aynı zaman noktalarında karşılaştırıyor. `linear` ve `ease-in-out`
dışındakiler zamanın yarısında yolun yarısında değil.

`ease` yolun yarısını sürenin yaklaşık yüzde 30'unda tamamlar; hızlı başlar, uzun bir
yavaşlama kuyruğu bırakır. `ease-in` yavaş başlar, sona doğru hızlanır — sürenin yarısında
yolun ancak üçte biri alınmıştır. `ease-out` bunun tersidir. `ease-in-out` simetriktir ve
tam ortada yolun tam yarısındadır.

Seçim keyfî değildir. Bir öge ekrana **giriyorsa** yavaşlayarak durması beklenir
(`ease-out`); ekrandan **çıkıyorsa** hızlanarak gitmesi beklenir (`ease-in`). Yerinde kalıp
biçim değiştiren bir ögede simetrik eğri kullanılır.

#### Ara Değerin Hesabı

Çıktının ikinci bloğu, tek bir ilerleme değerinin iki ayrı özelliğe nasıl uygulandığını
gösteriyor. 240 milisaniyelik geçişin 60. milisaniyesinde ilerleme $0{,}3781$; öteleme bu
oranda $-3{,}03$ birim, ölçek çarpanı $1{,}0151$.

Formül her özellik için aynıdır:

$$
\text{değer}(t) = \text{başlangıç} + (\text{bitiş} - \text{başlangıç}) \times \text{ilerleme}(t)
$$

Dönüşüm listelerinde bu hesap fonksiyon fonksiyon yapılır — iki listede aynı fonksiyonlar
aynı sırada varsa her fonksiyonun kendi sayıları ara değerlenir. Listeler uyuşmuyorsa
tarayıcı ikisini de matrise indirger ve matrisleri ara değerler; sonuç genellikle beklenen
yol değildir. Geçişe girecek iki dönüşüm listesinin **aynı fonksiyonları aynı sırada**
taşıması bu yüzden bir yazım kuralıdır.

#### Kesilme ve Geri Dönüş

Çıktının üçüncü bloğu, işaretçinin 72. milisaniyede karttan çıktığı durumu hesaplıyor. O
anda öteleme $-3{,}56$ birimdir ve geri dönüş bu değerden başlar, $-8$ biriminden değil.

Kural şudur: bir geçiş kesildiğinde yeni geçiş, özelliğin **o andaki hesaplanmış
değerinden** başlar. Bu, hareketin sıçramamasını sağlar.

Ama bir yan etki bırakır: geri dönüş de tam süreyi kullanır. Kart 72 milisaniye yükselip
240 milisaniye boyunca iner. Kısa bir dokunuş, uzun bir geri dönüşle yanıtlanır ve hareket
gecikmeli hissettirir. Bunun önüne geçmek için giriş ve çıkış süreleri ayrı yazılır: çıkış
süresi girişten kısa tutulur.

Bir geçiş bildirimi, ögenin **temel durumuna** yazıldığında hem gidiş hem dönüş için
geçerlidir; yalnız `:hover` kuralına yazıldığında yalnız gidiş yayılır, dönüş anlık olur.

#### Kesikli Zamanlama

`steps(n, kip)` fonksiyonu ilerlemeyi sürekli değil **basamaklı** üretir. Çıktının son
bloğu dört adımlık iki kipi karşılaştırıyor.

`end` kipi sıfırdan başlar ve son basamağı sonda atar; `start` kipi ilk basamağı hemen
atar ve sıfır değerini hiç göstermez. Dört adımda ilerleme yalnız $0$, $0{,}25$, $0{,}5$,
$0{,}75$, $1$ değerlerini alır; aradaki her şey atlanır.

Bu, kare kare ilerleyen bir gösterge ya da sayaç için kullanılır. Süreklilik istenmeyen
yerlerde kesikli zamanlama, hareketi daha az dikkat dağıtıcı yapar.

#### İstasyon Sayfasında Geçiş

```css
/* hareket.css — 2. adım: kart vurgusunun zamana yayılması */
.olcum-kartlari > .kart {
  transform-origin: center bottom;
  transition:
    transform 240ms ease-out,
    box-shadow 240ms ease-out;
}

.olcum-kartlari > .kart:hover,
.olcum-kartlari > .kart:focus-within {
  transform: translateY(-8px) scale(1.04);
  transition-duration: 160ms;
}
```

Geçiş bildirimi temel duruma yazıldı; dönüş de yayılıyor. Vurgulu durumda süre 160
milisaniyeye çekildi: giriş kısa, dönüş biraz uzun. Sıralama böylece hem tepkili hem
yumuşak olur.

Listede `transform` ve `box-shadow` tek tek sayıldı. `transition-property: all` yazmak daha
az yazıdır ama her hesaplanmış değer değişimini geçişe sokar; sonradan eklenen bir renk ya
da ölçü bildirimi istemeden yayılmaya başlar. Özellikleri saymak bu sürprizi önler.

#### Özet

- Geçiş bir değişimi başlatmaz; hesaplanmış değeri zaten değişen bir özelliğin eski
  değerinden yenisine ilerlemesini zamana yayar.
- Ara değer üretilemeyen değerler geçemez: `auto`, `display` gibi kesikli özellikler ve
  anahtar sözcükler yol değil sıçrama üretir.
- Zamanlama fonksiyonu geçen sürenin oranını ilerleme oranına çevirir; adlı fonksiyonlar
  kübik Bézier eğrileridir ve `linear` ile `ease-in-out` dışında zamanın yarısı yolun
  yarısı değildir.
- Kesilen bir geçiş, özelliğin o andaki hesaplanmış değerinden yeniden başlar ve tam
  süresini kullanır; giriş ile çıkış süreleri ayrı yazılarak dengelenir.
- `steps(n, kip)` ilerlemeyi basamaklı üretir; `start` ilk basamağı hemen atar, `end` son
  basamağı sonda atar.

#### Sonraki Adım

Geçiş bir koşula bağlıdır: değer değişmelidir. Değer kendiliğinden değişmiyorsa — bir
yükleme göstergesi dönmeye devam ediyorsa, bir uyarı iki renk arasında gidip geliyorsa —
geçişin tetikleyecek bir olayı yoktur. Sonraki ders, zaman çizelgesini bildirimin kendisine
yazan mekanizmayı ele alır: ara duraklar, yineleme sayısı ve yön.

### Anahtar Kare Animasyonları

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/anahtar-kare-animasyonlari

Kendi zaman çizelgesini taşıyan animasyonlar; anahtar kare kümesinin tanımı, yerel ilerlemenin yineleme ve yön değerlerinden çözülmesi, doldurma kipinin animasyon dışındaki değerleri ve zamanlama fonksiyonunun kare aralıklarına uygulanması.

Geçiş bir koşula bağlıdır: bir değerin değişmesi gerekir. Değeri değiştirecek bir olay
yoksa geçişin tetikleyicisi de yoktur. Ölçüm istasyonunun bağlantı göstergesi, kimse
sayfaya dokunmadan da "veri bekleniyor" durumunu bildirmelidir.

Bunun aracı, zaman çizelgesini bildirimin kendisine yazan mekanizmadır. Bu ders çizelgenin
nasıl tanımlandığını, verilen bir anda hangi değerin geçerli olduğunu ve çizelgenin
dışındaki zamanda ne olduğunu ele alır.

#### Anahtar Kare Kümesi

`@keyframes` kuralı bir ad ve bir **anahtar kare (keyframe)** listesi tanımlar. Her kare bir
konum ve o konumda geçerli bildirimleri taşır. Konum yüzdedir; `from` ve `to` anahtar
sözcükleri `%0` ile `%100` karşılığıdır.

```css
@keyframes nabiz {
  0%   { opacity: 1;    transform: scale(1);    animation-timing-function: ease-in-out; }
  60%  { opacity: 0.35; transform: scale(1.06); }
  100% { opacity: 1;    transform: scale(1); }
}
```

Dört kural bu tanımı okumayı belirler:

1. `%0` ya da `%100` yazılmamışsa o uç, ögenin **kendi bildirimlerinden gelen** değerle
   doldurulur. Bu değere taban değer denir.
2. Aynı konumda iki kare yazılırsa sonraki kazanır; aynı adla iki `@keyframes` kuralı
   yazılırsa sonraki öncekini bütünüyle değiştirir — kareler birleştirilmez.
3. Bir kareye yazılan `animation-timing-function`, o kareden **sonraki** aralık için
   geçerlidir. Son kareye yazılırsa etkisizdir.
4. Karelerde geçemeyen değer yazılırsa o bildirim yok sayılır; `!important` taşıyan bir
   bildirim de yok sayılır.

Üçüncü madde bir yanlış anlamanın kaynağıdır: ögeye yazılan `animation-timing-function`
animasyonun **tamamına** değil, her kare aralığına ayrı ayrı uygulanır. Üç kareli bir
animasyonda `ease-in-out` yazmak iki ayrı yavaşla–hızlan–yavaşla eğrisi üretir.

#### Animasyon Bildirimleri

| Bildirim | Yanıtladığı soru |
|---|---|
| `animation-name` | Hangi anahtar kare kümesi |
| `animation-duration` | Bir tur ne kadar sürecek |
| `animation-timing-function` | Kare aralıklarında hız nasıl dağılacak |
| `animation-delay` | Başlangıç ne kadar ertelenecek |
| `animation-iteration-count` | Kaç tur dönecek |
| `animation-direction` | Hangi turlar ters yönde oynatılacak |
| `animation-fill-mode` | Çizelge dışındaki zamanda hangi değer geçerli |
| `animation-play-state` | Çalışıyor mu, duraklatılmış mı |

Kısayol `animation` bunların tümünü alır. Virgülle ayrılmış liste, aynı ögeye birden çok
animasyon bağlamayı sağlar; aynı özelliğe yazan iki animasyondan listede **sonra** gelen
kazanır.

#### Zaman Çizelgesinin Çözülmesi

Verilen bir anda geçerli değeri bulmak dört adımdır: geçen süreden tur numarası çıkarılır,
tur içindeki ham ilerleme bulunur, yön değerine göre gerekiyorsa ters çevrilir, sonra
ilerlemeye karşılık gelen kare çifti aranır.

```js
// anahtarkare.mjs — anahtar kare cozumlemesi, yineleme, yon ve doldurma kipi
const bez = (a, b, t) => { const u = 1 - t; return 3*u*u*t*a + 3*u*t*t*b + t*t*t; };
const egri = (x1, y1, x2, y2) => (x) => {
  if (x <= 0) return 0;
  if (x >= 1) return 1;
  let alt = 0, ust = 1, t = x;
  for (let i = 0; i < 60; i++) { t = (alt + ust) / 2; if (bez(x1, x2, t) < x) alt = t; else ust = t; }
  return bez(y1, y2, t);
};
const DOGRUSAL = egri(0, 0, 1, 1);
const EASE_IN_OUT = egri(0.42, 0, 0.58, 1);

// @keyframes nabiz { 0% {...} 60% {...} 100% {...} }
const KARELER = [
  { konum: 0,   saydamlik: 1.00, olcek: 1.00, egri: EASE_IN_OUT },
  { konum: 60,  saydamlik: 0.35, olcek: 1.06, egri: DOGRUSAL },
  { konum: 100, saydamlik: 1.00, olcek: 1.00, egri: DOGRUSAL },
];

// yerel ilerlemeden (0..1) deger uretimi: kare cifti bulunur, o cifte ait egri uygulanir
function deger(yerel) {
  const y = yerel * 100;
  let i = 0;
  while (i < KARELER.length - 2 && y >= KARELER[i + 1].konum) i++;
  const a = KARELER[i], b = KARELER[i + 1];
  const oran = (y - a.konum) / (b.konum - a.konum);
  const e = a.egri(Math.min(Math.max(oran, 0), 1));
  return {
    cift: `%${a.konum}-%${b.konum}`,
    saydamlik: a.saydamlik + (b.saydamlik - a.saydamlik) * e,
    olcek: a.olcek + (b.olcek - a.olcek) * e,
  };
}

const SURE = 1400, GECIKME = 200, YINELEME = 3;

// yon degerine gore turun ilerlemesi ters cevrilir mi?
const ters = (yon, tur) =>
  yon === "reverse" ? true :
  yon === "alternate" ? tur % 2 === 1 :
  yon === "alternate-reverse" ? tur % 2 === 0 : false;

console.log(`sure=${SURE}ms gecikme=${GECIKME}ms yineleme=${YINELEME} yon=alternate`);
console.log("\n  ms |  tur | ham ilerleme | yerel ilerleme | kare cifti | saydamlik | olcek");
for (const ms of [0, 200, 620, 1040, 1600, 2200, 2600, 3000, 3400, 4600]) {
  const gecen = ms - GECIKME;
  if (gecen < 0) { console.log(`${String(ms).padStart(4)} | gecikme suresi, animasyon henuz baslamadi`); continue; }
  if (gecen >= SURE * YINELEME) { console.log(`${String(ms).padStart(4)} | animasyon bitti`); continue; }
  const tur = Math.floor(gecen / SURE);
  const ham = (gecen % SURE) / SURE;
  const yerel = ters("alternate", tur) ? 1 - ham : ham;
  const d = deger(yerel);
  console.log(
    `${String(ms).padStart(4)} | ${String(tur).padStart(4)} | ${ham.toFixed(4).padStart(12)} | ${yerel.toFixed(4).padStart(14)} | ${d.cift.padStart(10)} | ${d.saydamlik.toFixed(4).padStart(9)} | ${d.olcek.toFixed(4)}`,
  );
}

console.log("\n--- yon degerlerinin turlere etkisi (t = ters cevrilir) ---");
console.log("yon".padEnd(20) + [0, 1, 2, 3].map((n) => `tur ${n}`.padStart(8)).join(""));
for (const yon of ["normal", "reverse", "alternate", "alternate-reverse"]) {
  console.log(yon.padEnd(20) + [0, 1, 2, 3].map((n) => (ters(yon, n) ? "t" : "-").padStart(8)).join(""));
}

// Doldurma kipi tek turluk bir giris animasyonunda gorunur olur:
// @keyframes belirme { from { opacity: 0; translateY 8px } to { opacity: 1; translateY 0 } }
console.log("\n--- doldurma kipi: 'belirme' animasyonunun disindaki degerler ---");
const BELIRME = { bas: { saydamlik: 0, oteleme: 8 }, son: { saydamlik: 1, oteleme: 0 } };
const g = (d) => `saydamlik=${d.saydamlik.toFixed(2)} translateY=${d.oteleme}px`;
// Taban deger = ogenin kendi bildirimlerinden gelen deger; iki ayri taban denenir.
for (const TEMEL of [{ saydamlik: 1, oteleme: 0 }, { saydamlik: 0, oteleme: 8 }]) {
  console.log(`\ntaban deger: ${g(TEMEL)}`);
  for (const kip of ["none", "forwards", "backwards", "both"]) {
    const once = (kip === "backwards" || kip === "both") ? BELIRME.bas : TEMEL;
    const sonra = (kip === "forwards" || kip === "both") ? BELIRME.son : TEMEL;
    console.log(`  ${kip.padEnd(10)} gecikme sirasinda: ${g(once).padEnd(34)} bittikten sonra: ${g(sonra)}`);
  }
}
```

```
sure=1400ms gecikme=200ms yineleme=3 yon=alternate

  ms |  tur | ham ilerleme | yerel ilerleme | kare cifti | saydamlik | olcek
   0 | gecikme suresi, animasyon henuz baslamadi
 200 |    0 |       0.0000 |         0.0000 |     %0-%60 |    1.0000 | 1.0000
 620 |    0 |       0.3000 |         0.3000 |     %0-%60 |    0.6750 | 1.0300
1040 |    0 |       0.6000 |         0.6000 |   %60-%100 |    0.3500 | 1.0600
1600 |    1 |       0.0000 |         1.0000 |   %60-%100 |    1.0000 | 1.0000
2200 |    1 |       0.4286 |         0.5714 |     %0-%60 |    0.3528 | 1.0597
2600 |    1 |       0.7143 |         0.2857 |     %0-%60 |    0.7016 | 1.0275
3000 |    2 |       0.0000 |         0.0000 |     %0-%60 |    1.0000 | 1.0000
3400 |    2 |       0.2857 |         0.2857 |     %0-%60 |    0.7016 | 1.0275
4600 | animasyon bitti

--- yon degerlerinin turlere etkisi (t = ters cevrilir) ---
yon                    tur 0   tur 1   tur 2   tur 3
normal                     -       -       -       -
reverse                    t       t       t       t
alternate                  -       t       -       t
alternate-reverse          t       -       t       -

--- doldurma kipi: 'belirme' animasyonunun disindaki degerler ---

taban deger: saydamlik=1.00 translateY=0px
  none       gecikme sirasinda: saydamlik=1.00 translateY=0px      bittikten sonra: saydamlik=1.00 translateY=0px
  forwards   gecikme sirasinda: saydamlik=1.00 translateY=0px      bittikten sonra: saydamlik=1.00 translateY=0px
  backwards  gecikme sirasinda: saydamlik=0.00 translateY=8px      bittikten sonra: saydamlik=1.00 translateY=0px
  both       gecikme sirasinda: saydamlik=0.00 translateY=8px      bittikten sonra: saydamlik=1.00 translateY=0px

taban deger: saydamlik=0.00 translateY=8px
  none       gecikme sirasinda: saydamlik=0.00 translateY=8px      bittikten sonra: saydamlik=0.00 translateY=8px
  forwards   gecikme sirasinda: saydamlik=0.00 translateY=8px      bittikten sonra: saydamlik=1.00 translateY=0px
  backwards  gecikme sirasinda: saydamlik=0.00 translateY=8px      bittikten sonra: saydamlik=0.00 translateY=8px
  both       gecikme sirasinda: saydamlik=0.00 translateY=8px      bittikten sonra: saydamlik=1.00 translateY=0px
```

İlk tablonun her satırı bir anı çözüyor. 1040. milisaniyede geçen süre 840 milisaniye, tur
sıfır, ham ilerleme $0{,}6$; bu değer üçüncü kareye tam denk geldiği için çift `%60-%100`
olarak okunuyor ve değerler o karenin bildirimleridir.

2200. milisaniyede iki adım birden görünür. Tur bir, ham ilerleme $0{,}4286$; yön
`alternate` olduğu için tek numaralı turda çizelge ters çevrilir ve yerel ilerleme
$1 - 0{,}4286 = 0{,}5714$ olur. Kare çifti bu yerel değere göre aranır.

Kare aralıklarının uzunluğu eşit değildir: ilk aralık turun yüzde 60'ı, ikincisi yüzde
40'ıdır. `%0` karesine yazılan `ease-in-out` yalnız birinci aralıkta çalışır ve etkisi
2200. milisaniyede görünür. Yerel ilerleme $0{,}5714$, bunun birinci aralık içindeki oranı
$0{,}9523$; eğri bu oranı $0{,}9956$ değerine çeker ve saydamlık $0{,}3528$ olur — doğrusal
ara değer $0{,}3810$ verirdi. Aralığın tam ortasında ise eğri simetrik olduğu için fark
sıfırdır: 620. milisaniyedeki $0{,}675$ değeri iki yolla da aynı çıkar.

#### Yön ve Yineleme

`animation-iteration-count` bir sayı ya da `infinite` alır. Kesirli değer de geçerlidir:
$2{,}5$ yazmak animasyonu üçüncü turun ortasında keser.

İkinci tablo dört yön değerinin hangi turları ters çevirdiğini gösteriyor. `normal` hiçbir
turu, `reverse` hepsini ters çevirir. `alternate` çift numaralı turları düz, tek numaralı
turları ters oynatır; `alternate-reverse` bunun tersidir.

Ayrım sayı değil davranıştır: `alternate` ile animasyon başladığı yere geri döner ve
sıçrama olmaz. `normal` ile üç tur dönen bir animasyon, her turun sonunda son kareden ilk
kareye **atlar**. Kareler uçlarda aynı değeri taşımıyorsa bu atlama görünür.

#### Doldurma Kipi

Bir animasyon iki zaman aralığının dışında kalır: gecikme süresi ve bitişten sonrası. Bu
aralıklarda hangi değerin geçerli olduğunu `animation-fill-mode` bildirir.

Üçüncü blok, iki farklı taban değer için dört kipi hesaplıyor ve seçimi bir kurala bağlıyor.
Taban değer `opacity: 1` iken — yani öge biçem dosyasında zaten görünürken — `forwards` ile
`none` aynı sonucu veriyor, çünkü animasyonun son karesi de tabanla aynı. Ayrımı yapan
`backwards`: gecikme süresince ilk kareyi uygular ve ögenin bir an görünüp sonra kaybolmasını
önler.

Taban değer `opacity: 0` iken durum tersine döner. Öge biçem dosyasında gizlidir; `none` ile
animasyon bittiğinde gizli hâline geri döner ve girmiş olduğu boşa gider. Burada gereken
`forwards` değeridir.

Kural şudur: doldurma kipi, animasyonun uçlarındaki değerlerle ögenin taban değeri
uyuşmadığında gerekir. İkisi uyuşuyorsa hangi kip yazıldığı görünmez.

#### İstasyon Sayfasında Nabız

```css
/* hareket.css — 3. adım: veri bekleyen ölçüm göstergesi */
@keyframes nabiz {
  0%   { opacity: 1;    transform: scale(1);    animation-timing-function: ease-in-out; }
  60%  { opacity: 0.35; transform: scale(1.06); }
  100% { opacity: 1;    transform: scale(1); }
}

.istasyon-durumu[data-durum="bekliyor"] .gosterge {
  animation: nabiz 1400ms 200ms infinite alternate;
  transform-origin: center;
}

.istasyon-durumu[data-durum="cevrimdisi"] .gosterge {
  animation: none;
}
```

Gösterge yalnız bekleme durumunda hareket eder. Durum bilgisi `data-durum` özniteliğinden
okunur; öznitelik seçicileri CSS ile Görsel Sunum kursunda tanımlanmıştı. Çevrimdışı durumda
animasyon açıkça kapatılır — sessizlik de bir bildirimdir.

Hareket süresiz döndüğü için burada bir sorumluluk doğar: sürekli dönen bir animasyon,
kullanıcının durduramayacağı bir dikkat çekicidir. Bu konunun son dersi bu sorumluluğu ele
alır.

#### Özet

- `@keyframes` bir zaman çizelgesi tanımlar; yazılmayan uçlar ögenin taban değeriyle
  doldurulur ve aynı adla ikinci bir kural öncekini bütünüyle değiştirir.
- Bir andaki değer dört adımda çözülür: tur numarası, tur içindeki ham ilerleme, yön
  değerine göre ters çevirme, kare çiftinin bulunması.
- Zamanlama fonksiyonu animasyonun tamamına değil **her kare aralığına** ayrı uygulanır;
  bir kareye yazılan eğri o kareden sonraki aralık için geçerlidir.
- `alternate` tek numaralı turları ters oynatarak tur sonlarındaki sıçramayı kaldırır;
  `normal` ile son kareden ilk kareye atlanır.
- Doldurma kipi yalnız animasyonun uç değerleri ögenin taban değerinden farklıysa görünür
  bir etki üretir.

#### Sonraki Adım

Bu üç dersin bildirimleri hareketi tanımladı ama bedelini hiç konuşmadı. Ölçek değiştiren
bir animasyonla genişlik değiştiren bir animasyon aynı görünür; tarayıcı için ikisi aynı iş
değildir. Sonraki ders, hareketin çizim hattındaki karşılığını ele alır: hangi özellik hangi
aşamayı yeniden çalıştırır ve bu neden hareketin akıcılığını belirler?

### Animasyon Başarımı

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/animasyon-basarimi

Hareketin çizim hattındaki karşılığı; hangi özellik değişiminin yerleşim, boyama ve bileşim aşamalarını yeniden çalıştırdığı, bileşik katmanın ne olduğu, zorlanmış yerleşim hesabı ve kare bütçesi.

Önceki üç ders hareketi tanımladı: dönüşüm geometriyi verdi, geçiş ve anahtar kareler
zamanı verdi. Bedelinden hiç söz edilmedi.

Bir kartı sağa kaydırmanın iki yolu var: `left` değerini artırmak ya da `transform`
listesine öteleme yazmak. Ekranda ikisi aynı görünür. Tarayıcı için ikisi aynı iş değildir.
Bu ders farkın nereden geldiğini kural olarak kurar.

#### Çizim Hattı

Bir sayfanın ekrana gelmesi sıralı aşamalardan geçer. Hareket açısından dördü önemlidir:

1. **Stil hesabı.** Hangi bildirimin hangi ögeye uygulandığı çözülür; basamaklama ve
   kalıtım burada işler. Her değişim bu aşamayı çalıştırır.
2. **Yerleşim.** Her kutunun ölçüsü ve yeri hesaplanır. Bir kutunun genişliği değişirse
   kardeşleri, ana ögesi ve sonraki akış etkilenebilir; hesap yerel kalmayabilir.
3. **Boyama.** Kutuların içeriği — renk, kenarlık, gölge, metin — piksellere çevrilir.
4. **Bileşim.** Boyanmış yüzeyler sıralanır, dönüştürülür ve ekrana yerleştirilir.

Aşamalar sıralıdır: yerleşim yeniden çalışırsa boyama ve bileşim de çalışır. Ama tersi geçerli
değildir; bileşim tek başına çalışabilir.

#### Hangi Özellik Hangi Aşamayı Tetikler

```js
// basarim.mjs — ozellik degisiminin cizim hattinda tetikledigi asamalar ve zorlanmis duzen sayimi
const HAT = ["stil", "duzen", "boyama", "bilesim"];

// Kural: bir ozellik hangi asamadan itibaren yeniden calisir?
const BASLANGIC = {
  width: "duzen", height: "duzen", "margin-block-start": "duzen", padding: "duzen",
  "border-width": "duzen", "font-size": "duzen", "line-height": "duzen",
  top: "duzen", left: "duzen", display: "duzen", "flex-basis": "duzen",
  color: "boyama", "background-color": "boyama", "background-image": "boyama",
  "border-radius": "boyama", "box-shadow": "boyama", outline: "boyama",
  visibility: "boyama", "text-decoration": "boyama",
  transform: "bilesim", opacity: "bilesim",
};

console.log("--- ozellik degisimi hangi asamadan itibaren yeniden calisir ---");
console.log("ozellik".padEnd(20) + HAT.map((a) => a.padStart(10)).join("") + "   asama sayisi");
for (const [ozellik, bas] of Object.entries(BASLANGIC)) {
  const i = HAT.indexOf(bas);
  const isaret = HAT.map((_, k) => (k === 0 || k >= i ? "x" : "-").padStart(10)).join("");
  console.log(ozellik.padEnd(20) + isaret + String(HAT.length - i + (i > 0 ? 1 : 0)).padStart(15));
}

console.log("\n--- ayni gorsel sonucu veren iki yazim ---");
for (const [a, b] of [["left", "transform"], ["width", "transform"], ["visibility", "opacity"]]) {
  const ia = HAT.indexOf(BASLANGIC[a]), ib = HAT.indexOf(BASLANGIC[b]);
  console.log(
    `${a.padEnd(12)} -> ${HAT.slice(ia).join(" > ").padEnd(26)}` +
    `${b.padEnd(12)} -> ${HAT.slice(ib).join(" > ")}`,
  );
}

// --- zorlanmis duzen (forced synchronous layout) sayimi ---
// Model: yazma islemi duzeni gecersiz kilar; olcu okuma islemi gecersiz duzeni
// okumak icin duzeni yeniden hesaplatir. Kare sonunda bir hesap zaten yapilir.
function duzenSayimi(islemler) {
  let kirli = false, zorlanmis = 0;
  const iz = [];
  for (const [tip, ad] of islemler) {
    if (tip === "yaz") { kirli = true; iz.push(`yaz ${ad}`); }
    else {
      if (kirli) { zorlanmis++; kirli = false; iz.push(`oku ${ad} -> DUZEN HESABI`); }
      else iz.push(`oku ${ad}`);
    }
  }
  return { zorlanmis, kareSonu: kirli ? 1 : 0, iz };
}

const KARISIK = [
  ["oku", "genislik(kart 1)"], ["yaz", "width(kart 1)"],
  ["oku", "genislik(kart 2)"], ["yaz", "width(kart 2)"],
  ["oku", "genislik(kart 3)"], ["yaz", "width(kart 3)"],
];
const AYRIK = [
  ["oku", "genislik(kart 1)"], ["oku", "genislik(kart 2)"], ["oku", "genislik(kart 3)"],
  ["yaz", "width(kart 1)"], ["yaz", "width(kart 2)"], ["yaz", "width(kart 3)"],
];

for (const [ad, islemler] of [["okuma ve yazma ic ice", KARISIK], ["once okuma, sonra yazma", AYRIK]]) {
  const s = duzenSayimi(islemler);
  console.log(`\n--- ${ad} ---`);
  for (const satir of s.iz) console.log("  " + satir);
  console.log(`  zorlanmis duzen hesabi: ${s.zorlanmis}, kare sonundaki hesap: ${s.kareSonu}`);
}

console.log("\n--- kare butcesi (yenileme hizindan aritmetik) ---");
for (const hz of [60, 90, 120, 144]) {
  console.log(`${String(hz).padStart(3)} kare/s -> kare basina ${(1000 / hz).toFixed(2)} ms`);
}
```

```
--- ozellik degisimi hangi asamadan itibaren yeniden calisir ---
ozellik                   stil     duzen    boyama   bilesim   asama sayisi
width                        x         x         x         x              4
height                       x         x         x         x              4
margin-block-start           x         x         x         x              4
padding                      x         x         x         x              4
border-width                 x         x         x         x              4
font-size                    x         x         x         x              4
line-height                  x         x         x         x              4
top                          x         x         x         x              4
left                         x         x         x         x              4
display                      x         x         x         x              4
flex-basis                   x         x         x         x              4
color                        x         -         x         x              3
background-color             x         -         x         x              3
background-image             x         -         x         x              3
border-radius                x         -         x         x              3
box-shadow                   x         -         x         x              3
outline                      x         -         x         x              3
visibility                   x         -         x         x              3
text-decoration              x         -         x         x              3
transform                    x         -         -         x              2
opacity                      x         -         -         x              2

--- ayni gorsel sonucu veren iki yazim ---
left         -> duzen > boyama > bilesim  transform    -> bilesim
width        -> duzen > boyama > bilesim  transform    -> bilesim
visibility   -> boyama > bilesim          opacity      -> bilesim

--- okuma ve yazma ic ice ---
  oku genislik(kart 1)
  yaz width(kart 1)
  oku genislik(kart 2) -> DUZEN HESABI
  yaz width(kart 2)
  oku genislik(kart 3) -> DUZEN HESABI
  yaz width(kart 3)
  zorlanmis duzen hesabi: 2, kare sonundaki hesap: 1

--- once okuma, sonra yazma ---
  oku genislik(kart 1)
  oku genislik(kart 2)
  oku genislik(kart 3)
  yaz width(kart 1)
  yaz width(kart 2)
  yaz width(kart 3)
  zorlanmis duzen hesabi: 0, kare sonundaki hesap: 1

--- kare butcesi (yenileme hizindan aritmetik) ---
 60 kare/s -> kare basina 16.67 ms
 90 kare/s -> kare basina 11.11 ms
120 kare/s -> kare basina 8.33 ms
144 kare/s -> kare basina 6.94 ms
```

Tablo bir ölçüm değil, bir **kural kümesidir**: özellikler tanımlarına göre üç gruba
ayrılır. Geometriye dokunanlar yerleşimden itibaren, yalnız görünüşe dokunanlar boyamadan
itibaren, kutunun ne ölçüsünü ne içeriğini değiştirenler ise yalnız bileşimden itibaren
çalışır.

Sınıflandırmanın kenarında birkaç özellik vardır — süzgeç ve karışım kipleri gibi — ve
bunların hangi aşamadan başladığı çizim gerçekleştirimine göre değişir. Kesin bilgi
tarayıcının geliştirici araçlarındaki çizim kaydından okunur; kural olarak
ezberlenmemelidir.

#### Aynı Görünüm, Farklı Maliyet

Çıktının ikinci bloğu üç çifti karşılaştırıyor. `left` ile ötelemek üç aşama çalıştırır,
`transform` ile ötelemek bir aşama. `visibility` ile gizlemek boyamayı yeniden çalıştırır,
`opacity` ile soldurmak çalıştırmaz.

Buradan hareket yazımının en pratik kuralı çıkar: **animasyonlar dönüşüm ve saydamlık
üzerinden yazılır.** Bir kutunun büyümesi gerekiyorsa `width` yerine `scale`, yer
değiştirmesi gerekiyorsa `left` yerine `translate` kullanılır.

Kuralın bir bedeli vardır. `scale` kutunun içeriğini de ölçekler; metin, kutu büyürken
büyür. `width` değiştirmek ise metni yeniden satırlara böler. İkisi aynı görsel sonucu
vermez ve seçim, istenen sonucun hangisi olduğuna bağlıdır.

#### Bileşik Katman

Dönüşümün ucuz olmasının nedeni, uygulandığı yerdir. Boyanmış bir yüzey bir kez üretilir;
dönüşüm ve saydamlık o yüzeye bileşim aşamasında uygulanır. Yüzey yeniden boyanmaz,
yalnızca farklı yerleştirilir.

Bunun için ögenin kendi yüzeyine, yani **bileşik katmana (compositing layer)** ayrılmış
olması gerekir. Katman ayrımını tarayıcı kendi karar vererek yapar; `will-change` bildirimi
bu kararı önceden bildirmenin yoludur:

```css
.olcum-kartlari > .kart:hover { will-change: transform; }
```

`will-change` bir garanti değil, bir **ipucudur**. Üç kuralı vardır. Katman ayırmak bellek
harcar; her ögeye yazmak yararı tersine çevirir. Bildirim, hareket başlamadan kısa süre
önce açılıp bittikten sonra kaldırılmalıdır — kalıcı yazılan bir ipucu kalıcı bir maliyettir.
Ve `will-change` de yığılma bağlamı kurar; ilk dersteki uyarı burada da geçerlidir.

#### Zorlanmış Yerleşim Hesabı

Çıktının üçüncü ve dördüncü blokları, betikle sürülen bir hareketin sık düştüğü tuzağı bir
model üzerinde sayıyor. Model şudur: bir yazma işlemi yerleşimi geçersiz kılar; sonraki bir
ölçü okuma işlemi güncel değeri vermek zorunda olduğu için yerleşimi hemen yeniden hesaplatır.
Buna **zorlanmış yerleşim hesabı (forced synchronous layout)** denir.

Okuma ve yazma iç içe yapıldığında model iki ek hesap sayıyor. Aynı işlemler önce bütün
okumalar, sonra bütün yazmalar biçiminde sıralandığında ek hesap kalmıyor; kare sonunda
zaten yapılacak olan tek hesap yeterli oluyor.

Kural yazımdan bağımsızdır: ölçü okuyan işlemler bir öbekte, ölçü değiştiren işlemler ayrı
bir öbekte toplanır. Öbek sayısı arttıkça hesap sayısı da artar.

#### Kare Bütçesi

Son blok aritmetik bir dönüşümdür: saniyede 60 kare üreten bir ekranda kare başına düşen
süre $1000 / 60 \approx 16{,}67$ milisaniyedir. Bu süre, bir karenin bütün aşamalarının —
betik, stil, yerleşim, boyama, bileşim — paylaştığı bütçedir.

Bütçe aşıldığında kare atlanır ve hareket takılır. Bu yüzden hareket yazarken sorulacak soru
"ne kadar sürüyor" değil, "kare başına hangi aşamalar çalışıyor"dur. Aşama sayısını
düşürmek, süreyi tahmin etmeye çalışmaktan daha güvenilir bir yoldur.

Yenileme hızı ekrana göre değişir; aynı animasyon farklı bütçelerle çalışır. Kare sayısına
bağlı yazılmış bir hareket hızlı ekranda hızlanır. Süre üzerinden yazılan bildirimler bu
sorunu taşımaz.

#### Boyama Alanını Sınırlamak

Bir ögenin yeniden boyanması komşularını da kapsayabilir; boyama alanı ögeden büyük olabilir.
`contain` bildirimi bu yayılmayı sınırlar: `contain: layout` bir ögenin yerleşim hesabının dışarı
taşmayacağını, `contain: paint` boyamanın kutunun dışına çıkmayacağını bildirir.

Bu bildirim bir söz verir; sözü tutmayan bir içerik bozulur. `paint` değeri yazılan bir
kutudan taşan gölge ya da açılır menü kırpılır. Bildirim, kutunun gerçekten kendi kendine
yeter olduğu yerlerde yazılır.

#### İstasyon Sayfasında Ölçüm

```css
/* hareket.css — 4. adım: hareketin çizim maliyetini sınırlamak */
.olcum-kartlari > .kart {
  transform-origin: center bottom;
  transition: transform 240ms ease-out, box-shadow 240ms ease-out;
  contain: layout paint;
}

.olcum-kartlari > .kart:hover,
.olcum-kartlari > .kart:focus-within {
  transform: translateY(-8px) scale(1.04);
  transition-duration: 160ms;
}

.istasyon-durumu[data-durum="bekliyor"] .gosterge {
  animation: nabiz 1400ms 200ms infinite alternate;
  will-change: transform, opacity;
}
```

Kart hareketi yalnız dönüşüm üzerinden yazıldı; `contain` bildirimi kartın yerleşim ve boyama
etkisini kendi kutusuyla sınırlıyor. Gösterge sürekli hareket ettiği için `will-change`
bildirimi orada kalıcı yazıldı — kalıcı hareket, kalıcı katmanı haklı çıkaran ender
durumlardan biridir.

Gölge geçişi bir ödünleşimdir: `box-shadow` boyamayı yeniden çalıştırır. Aynı görünüm,
kartın arkasına konan ayrı bir sözde elemanın saydamlığıyla da elde edilebilir ve o yazım
yalnız bileşim çalıştırır.

#### Özet

- Çizim hattı sıralıdır: yerleşim çalışırsa boyama ve bileşim de çalışır, ama bileşim tek
  başına çalışabilir.
- Geometriye dokunan özellikler yerleşimden, yalnız görünüşe dokunanlar boyamadan, dönüşüm ve
  saydamlık ise bileşimden itibaren çalışır.
- Hareket dönüşüm ve saydamlık üzerinden yazıldığında aşama sayısı düşer; ölçü değiştiren
  yazımlar aynı görünümü daha çok işle üretir.
- Bileşik katman dönüşümün ucuz olmasının nedenidir; `will-change` bu ayrımı bildiren bir
  ipucudur ve bellek harcadığı için geçici kullanılır.
- Ölçü okuma ile ölçü yazma işlemleri iç içe yapıldığında zorlanmış yerleşim hesapları oluşur;
  okumaları ve yazmaları öbeklemek bunu ortadan kaldırır.

#### Sonraki Adım

Buraya kadar hareketin nasıl yazıldığı ve neye mal olduğu kuruldu. Bir soru dışarıda kaldı:
hareket her kullanıcı için aynı şey midir? Bazı kullanıcılar için ekrandaki hareket
rahatsızlık, baş dönmesi ya da bulantı üretir ve işletim sistemi bu tercihi bildirir.
Sonraki ders, hareket azaltma tercihinin ne anlama geldiğini ve hangi hareketin kaldırılıp
hangisinin korunacağını ele alır.

### Erişilebilir Hareket

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/erisilebilir-hareket

Hareket duyarlılığının ne olduğu, büyük alanlı hareketin yerel hareketten ayrılması, hareket azaltma tercihinde toptan kapatmanın bedeli ve hareketi kaldırmak yerine değiştirmenin kuralları.

Önceki ders hareketin tarayıcıya maliyetini ele aldı. Bir maliyet daha vardır ve o
tarayıcıya değil kullanıcıya çıkar: ekrandaki hareket bazı kullanıcılarda baş dönmesi,
bulantı ya da odaklanma güçlüğü üretir.

Duyarlı Tasarım konusunda `prefers-reduced-motion` sorgusu tanıtıldı ve kullanıcının bu
tercihi işletim sisteminden bildirdiği gösterildi. Bu ders sorgunun içine ne yazılacağını
ele alır: hangi hareket kaldırılır, hangisi korunur ve toptan kapatmanın bedeli nedir?

#### Hareket Duyarlılığı Neye Tepki Verir

Hareket duyarlılığı, iç kulaktaki denge düzeneğiyle görsel algının uyuşmamasından doğar.
Ekranda büyük bir alan hareket ettiğinde görsel sistem kendi hareketimiz olduğu izlenimini
üretir; denge düzeneği bunu doğrulamaz ve uyuşmazlık rahatsızlık verir.

Tetikleyen hareket türleri belirlidir: geniş alanların kayması, farklı hızlarda kayan
katmanlar, yakınlaşma ve uzaklaşma, dönme, salınım. Tetiklemeyenler de belirlidir:
saydamlık değişimi, renk değişimi, küçük alanlı yer değiştirme.

Ayrım "animasyon var mı yok mu" değildir. Ayrım, hareketin **ne kadar alanı ne kadar yol
boyunca** taşıdığıdır.

#### Basamaklama ve Alan Hesabı

Aşağıdaki program iki şeyi hesaplıyor. Birincisi, hareket azaltma tercihinin iki durumu için
kazanan bildirimleri basamaklama kurallarıyla çözüyor. İkincisi, dört hareketi kapladıkları
alan ve kat ettikleri yola göre sınıflandırıyor.

```js
// hareketazalt.mjs — tercih altinda basamaklama cozumu ve hareketin ekranda kapladigi alan
// Basamaklama: once onem, sonra ozgulluk ucluleri, sonra yazim sirasi.
const KURALLAR = [
  { sira: 1, secici: ".gosterge",  ozgulluk: [0, 1, 0], kosul: null,           bildirim: { "animation-name": "nabiz", "animation-duration": "1400ms" } },
  { sira: 2, secici: ".kart",      ozgulluk: [0, 1, 0], kosul: null,           bildirim: { "transition-duration": "240ms" } },
  { sira: 3, secici: "*",          ozgulluk: [0, 0, 0], kosul: "reduce",       bildirim: { "animation-duration": "0.01ms", "transition-duration": "0.01ms" }, onemli: true },
  { sira: 4, secici: ".gosterge",  ozgulluk: [0, 1, 0], kosul: "reduce",       bildirim: { "animation-name": "none" } },
];

const uygular = (kural, tercih) => kural.kosul === null || kural.kosul === tercih;
const buyuk = (a, b) => {                       // ozgulluk uclulerinin karsilastirmasi
  for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
  return false;
};

function coz(tercih, ozellikler) {
  const sonuc = {};
  for (const ozellik of ozellikler) {
    let kazanan = null;
    for (const k of KURALLAR) {
      if (!uygular(k, tercih) || !(ozellik in k.bildirim)) continue;
      if (kazanan === null) { kazanan = k; continue; }
      const kOnem = !!k.onemli, zOnem = !!kazanan.onemli;
      if (kOnem !== zOnem) { if (kOnem) kazanan = k; continue; }
      if (buyuk(k.ozgulluk, kazanan.ozgulluk)) { kazanan = k; continue; }
      if (!buyuk(kazanan.ozgulluk, k.ozgulluk) && k.sira > kazanan.sira) kazanan = k;
    }
    sonuc[ozellik] = kazanan ? `${kazanan.bildirim[ozellik]}   (kural ${kazanan.sira}: ${kazanan.secici}${kazanan.onemli ? " !important" : ""})` : "bildirim yok";
  }
  return sonuc;
}

const OZELLIKLER = ["animation-name", "animation-duration", "transition-duration"];
for (const tercih of ["no-preference", "reduce"]) {
  console.log(`\n=== prefers-reduced-motion: ${tercih} ===`);
  const s = coz(tercih, OZELLIKLER);
  for (const o of OZELLIKLER) console.log(`  ${o.padEnd(20)} -> ${s[o]}`);
}

// --- hareketin ekranda kapladigi oran ---
// Buyuk alanli ve uzun yollu hareket, hareket duyarliligi olan kullanicilar icin
// kucuk alanli hareketten farkli bir sinifta degerlendirilir.
console.log("\n--- hareketin goruntu alanina orani (1280 x 800 birim) ---");
const EN = 1280, BOY = 800;
const HAREKETLER = [
  { ad: "kart vurgusu",       en: 220, boy: 120, yol: 8 },
  { ad: "gosterge nabzi",     en: 12,  boy: 12,  yol: 1 },
  { ad: "yan panel acilisi",  en: 320, boy: 800, yol: 320 },
  { ad: "arka plan kaymasi",  en: 1280, boy: 800, yol: 240 },
];
console.log("hareket".padEnd(20) + "alan orani".padStart(12) + "yol / ekran".padStart(14) + "  siniflandirma");
for (const h of HAREKETLER) {
  const alan = (h.en * h.boy) / (EN * BOY);
  const yol = h.yol / Math.max(EN, BOY);
  const buyukAlan = alan >= 0.25 && yol >= 0.05;
  console.log(
    h.ad.padEnd(20) +
    `%${(alan * 100).toFixed(1)}`.padStart(12) +
    `%${(yol * 100).toFixed(1)}`.padStart(14) +
    "  " + (buyukAlan ? "buyuk alanli hareket" : "yerel hareket"),
  );
}
```

```

=== prefers-reduced-motion: no-preference ===
  animation-name       -> nabiz   (kural 1: .gosterge)
  animation-duration   -> 1400ms   (kural 1: .gosterge)
  transition-duration  -> 240ms   (kural 2: .kart)

=== prefers-reduced-motion: reduce ===
  animation-name       -> none   (kural 4: .gosterge)
  animation-duration   -> 0.01ms   (kural 3: * !important)
  transition-duration  -> 0.01ms   (kural 3: * !important)

--- hareketin goruntu alanina orani (1280 x 800 birim) ---
hareket               alan orani   yol / ekran  siniflandirma
kart vurgusu                %2.6          %0.6  yerel hareket
gosterge nabzi              %0.0          %0.1  yerel hareket
yan panel acilisi          %25.0         %25.0  buyuk alanli hareket
arka plan kaymasi         %100.0         %18.8  buyuk alanli hareket
```

İkinci blok bir ayıklama ölçütü veriyor. Kart vurgusu görüntü alanının yüzde üçünden azını
kaplıyor ve sekiz birim yol alıyor; gösterge nabzı daha da küçük. Yan panel görüntü alanının
dörtte birini kaplıyor ve genişliği kadar yol alıyor; arka plan kayması bütün ekranı
kaplıyor.

Eşikler kesin sayılar değil, ayıklama araçlarıdır: kaplanan alan ile kat edilen yolun
ikisinin de büyük olduğu hareketler ayrı bir sınıfa konur. Bu sınıftaki hareketler, tercih
bildirildiğinde kaldırılır; yerel hareketler kısaltılarak korunabilir.

#### Toptan Kapatmanın Bedeli

İlk blok, sık yazılan bir kalıbı çözümlüyor: evrensel seçiciye `!important` ile sıfıra yakın
süre yazan bir kural. Sonuç okunduğunda kuralın gücü görünüyor — üçüncü kural, kendisinden
daha özgül olan birinci kuralın süresini de bastırıyor.

Süre neden $0$ değil de $0{,}01$ milisaniye? Sıfır süreli bir geçiş hiç çalışmaz ve bitiş
olayı da yayılmaz; hareketin bitmesini bekleyen betikler o olayı hiç almaz. Çok kısa ama
sıfırdan büyük bir süre, hareketi görünmez kılarken olay zincirini korur.

Kalıbın bedeli, ayrım yapmamasıdır. Aynı kural şunları da bastırır:

- Bir formun hata iletisinin yumuşak belirmesi — hareket değil, dikkat yönlendirmesidir.
- Bir açılır menünün kapanışı — anlık kapanma, ögenin nereye gittiğini gizler.
- Yükleniyor göstergesinin dönmesi — durum bilgisi taşır; kaldırılırsa yerine yazı konmalıdır.

Toptan kapatma bir başlangıç noktası olarak yazılabilir, ama bileşen bileşen gözden
geçirilmemişse yarım bir çözümdür. Dördüncü kuralın yaptığı budur: göstergenin animasyonunu
adıyla kapatır, yerine başka bir bildirim koyma sorumluluğunu üstlenir.

#### Kaldırmak Değil, Değiştirmek

Hareket azaltma tercihi "hiç animasyon istemiyorum" demek değildir; büyük alanlı ve yol
kateden hareketin azaltılmasını ister. Bu, hareketle taşınan bilgiyi de silmek anlamına
gelmez.

Üç kural yeterlidir:

1. **Yer değiştirmeyi saydamlıkla değiştir.** Kayarak giren bir panel, yerinde belirebilir.
   Bilgi korunur, yol ortadan kalkar.
2. **Süreyi kısalt, döngüyü kaldır.** Sonsuz yinelenen bir hareket, tercihte tek turluk bir
   duruma indirilir ya da durur.
3. **Durum taşıyan hareketin yerine yazı koy.** Dönen bir gösterge yerine "veri bekleniyor"
   metni aynı bilgiyi taşır ve hiç hareket etmez.

Kayan yazı, otomatik ilerleyen tanıtım şeritleri ve arka plan videoları ayrı bir başlıktır.
Erişilebilirlik ölçütlerinden 2.2.2, beş saniyeden uzun süren, kendiliğinden başlayan ve başka
içerikle birlikte sunulan hareketin durdurulabilir olmasını ister; 2.3.1 saniyede üçten fazla
parlamayı, parlama eşiklerinin altında kalmadıkça yasaklar; AAA düzeyindeki 2.3.3 ise etkileşimle
tetiklenen hareketin kapatılabilmesini ister. 2.2.2 ile 2.3.3'te hareket etkinliğin ya da iletilen
bilginin özündeyse ölçüt aranmaz; ilk ikisi tercih bildirilmemiş olsa bile geçerlidir.

#### Yazım Yönü Seçimi

İki yazım yönü vardır. Birincisi hareketi varsayılan yazar ve `reduce` sorgusunda kapatır.
İkincisi hareketsiz yazar ve `no-preference` sorgusunda açar.

İkincisinin bir üstünlüğü vardır: sorgu değerlendirilemediğinde koşul sağlanmaz ve hareket
açılmaz. Yani belirsizlik hareketsizlik lehine çözülür. Birinci yönde belirsizlik hareket
lehine çözülür.

Bedeli, hareketin varsayılan durumdan ayrı bir yere yazılmasıdır; bileşenin biçemi iki parçaya
bölünür. Seçim, hareketin sayfadaki ağırlığına göre yapılır: sayfa boyunca birkaç küçük geçiş
varsa birinci yön yeterlidir; hareket tasarımın merkezindeyse ikinci yön güvenlidir.

#### İstasyon Sayfasında

```css
/* hareket.css — 5. adım: hareket azaltma tercihi */
@media (prefers-reduced-motion: reduce) {
  .olcum-kartlari > .kart {
    transition-duration: 80ms;
  }

  .olcum-kartlari > .kart:hover,
  .olcum-kartlari > .kart:focus-within {
    transform: none;
    box-shadow: 0 0 0 2px var(--marka-koyu);
  }

  .istasyon-durumu[data-durum="bekliyor"] .gosterge {
    animation: none;
    will-change: auto;
  }

  .yan-bilgi {
    scroll-behavior: auto;
  }
}
```

Kart vurgusu kaldırılmadı, **değiştirildi**: yükselme ve büyüme yerine bir çerçeve geliyor.
Kullanıcı hangi kartın vurgulandığını yine görüyor ve kart yer değiştirmiyor. Süre kısaltıldı ama
sıfırlanmadı; ani değişim de rahatsız edicidir.

Gösterge animasyonu kapatıldı ve katman ipucu geri alındı. Durum bilgisinin yazıyla da
verilmesi gerekir; bu, biçem dosyasının değil belgenin sorumluluğudur ve `.gosterge`
ögesinin yanındaki metin bunu üstlenir.

`scroll-behavior: auto` bildirimi, yumuşak kaydırmayı ani kaydırmaya çevirir. Kaydırma tüm
görüntü alanını hareket ettirdiği için büyük alanlı hareket sınıfındadır.

#### Özet

- Hareket duyarlılığı, görsel algı ile denge düzeneğinin uyuşmamasından doğar; tetikleyen
  şey animasyonun varlığı değil, kaplanan alan ile kat edilen yolun büyüklüğüdür.
- Kaplanan alan ve yol oranı hesaplanarak hareketler ayıklanır: büyük alanlı olanlar
  kaldırılır, yerel olanlar kısaltılarak korunabilir.
- Evrensel seçiciye yazılan `!important` süre kuralı bütün bildirimleri bastırır; bitiş
  olaylarının yayılması için süre sıfır değil sıfıra yakın yazılır.
- Hareket kaldırılırken taşıdığı bilgi kaldırılmaz: yer değiştirme saydamlığa çevrilir,
  döngü durdurulur, durum taşıyan hareketin yerine yazı konur.
- Hareketi varsayılan yazıp kapatmakla, hareketsiz yazıp açmak arasındaki seçim,
  belirsizliğin hangi yöne çözüleceğini belirler.

#### Sonraki Adım

Bu konu boyunca `hareket.css` adında üçüncü bir biçem dosyası büyüdü. Artık aynı ögeye üç
ayrı dosyadan bildirim geliyor: görsel bildirimler `istasyon.css` dosyasında, yerleşim
`duzen.css` dosyasında, hareket burada. Sınıf adları da çoğaldı ve hangi adın hangi
bileşene ait olduğu yalnız alışkanlıkla biliniyor. Sonraki konu bu düzensizliği bir soruya
çevirir: biçem katmanı hangi kurallarla adlandırılır ve bölünür ki büyüdükçe okunur kalsın?

## Biçem Mimarisi

Adlandırma düzenleriyle özgüllüğü düz tutmak, özel özelliklerle tema ve varyant yönetmek, derleme zamanı araçlarının işlevi, katman ve kapsamla sızıntıyı denetlemek ve biçemin başarım maliyeti.

### Adlandırma Yöntemleri

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/adlandirma-yontemleri

Blok–eleman–değiştirici yaklaşımı; özgüllüğün düz tutulması, adın sahipliği bildirmesi, değiştirici ile durum ayrımı ve kalıbın denetlenebilir olması.

Bu kursta üç biçem dosyası birikti. Görsel bildirimler `istasyon.css` dosyasında, yerleşim
`duzen.css` dosyasında, hareket `hareket.css` dosyasında. Sınıf adları da çoğaldı:
`.kart`, `.baslik`, `.genis`, `.ozet`, `.gosterge`.

Bu adların hiçbiri kime ait olduğunu söylemiyor. `.baslik` ölçüm kartının başlığı mı, yan
bilginin başlığı mı? Sayfaya yeni bir bölüm eklendiğinde aynı adı kullanmak güvenli mi? Bir
kural silindiğinde başka nereyi bozar?

Bu konu bu soruların yanıtını bir mimariye bağlar. Bu ders ilk adımı atar: adların kendisi.

#### Adlandırma Neyi Çözer

Bir biçem dosyası büyüdükçe üç sorun ortaya çıkar.

**Sahiplik.** Bir sınıf adının hangi bileşene ait olduğu addan okunmuyorsa, o adı kimse
güvenle değiştiremez. Değişiklik yapılacak yer bulunamaz.

**Özgüllük tırmanışı.** Bir bildirimi bastırmak için seçiciye bir sınıf daha eklenir; sonra
onu bastırmak için bir tane daha. Basamaklama ve Özgüllük dersinde tanımlanan üçlü büyümeye
başlar ve sonunda `!important` yazılır.

**Silinebilirlik.** Bir kuralın hangi belge parçasına uyduğu bilinmiyorsa o kural silinemez.
Biçem dosyası yalnızca büyür.

Adlandırma yöntemi bu üçünü tek bir kararla ele alır: **ad, ögenin belgedeki yerinden değil,
ait olduğu bileşenden türetilir.**

#### Blok, Eleman, Değiştirici

Yaygın yöntem adı üç parçaya ayırır.

**Blok**, kendi başına anlamlı ve taşınabilir bir bileşendir. Sayfanın neresine konursa
konsun aynı şeydir: ölçüm kartı, gezinme çubuğu, ölçüm süzgeci.

**Eleman**, bloğun bir parçasıdır ve blok olmadan anlamı yoktur: kartın başlığı, kartın
değeri, kartın birimi. Ad, bloğun adıyla başlar ve iki alt çizgiyle ayrılır:
`olcum-kart__baslik`.

**Değiştirici**, bloğun ya da elemanın bir varyantıdır: geniş kart, uyarı kartı, kısa
başlık. İki tireyle ayrılır: `olcum-kart--genis`.

Bir terim ayrımı gerekir: burada **eleman**, bloğun parçası anlamındadır. HTML belgesindeki
düğümler bu kursta ve önceki kurslarda **öge** olarak anılır. İki sözcük farklı şeyleri
adlandırır; `olcum-kart__baslik` bir eleman adıdır ve bir `h3` ögesine yazılabilir.

#### Özgüllüğün Düz Tutulması

Yöntemin kritik sonucu, her kuralın **tek bir sınıf seçicisi** olmasıdır. Aşağıdaki program
seçicilerin özgüllük üçlülerini hesaplıyor ve iki yazımın dağılımını karşılaştırıyor.

```js
// adlandirma.mjs — secici ozgullugu ve ad kaliplarinin denetimi
// Ozgulluk uclusu (a, b, c): a=kimlik, b=sinif/oznitelik/sozde sinif, c=tur/sozde eleman

// :where(...) icerigi ozgulluge katilmaz; :is(...) icindeki en ozgul secici katilir.
function ozgulluk(secici) {
  let s = secici;
  let ek = [0, 0, 0];
  s = s.replace(/:where\([^()]*\)/g, " ");                       // katkisi yok
  s = s.replace(/:is\(([^()]*)\)/g, (_, ic) => {                 // en ozgul dal sayilir
    const dallar = ic.split(",").map((d) => ozgulluk(d.trim()));
    dallar.sort((x, y) => y[0] - x[0] || y[1] - x[1] || y[2] - x[2]);
    ek = ek.map((v, i) => v + dallar[0][i]);
    return " ";
  });
  const a = (s.match(/#[\w-]+/g) || []).length;
  const b = (s.match(/\.[\w-]+/g) || []).length
          + (s.match(/\[[^\]]+\]/g) || []).length
          + (s.match(/:(?!:)[\w-]+(\([^()]*\))?/g) || []).length;
  const c = (s.match(/::[\w-]+/g) || []).length
          + (s.replace(/::[\w-]+/g, " ").replace(/:(?!:)[\w-]+(\([^()]*\))?/g, " ")
             .match(/(^|[\s>+~(])([a-zA-Z][\w-]*)/g) || []).length;
  return [a + ek[0], b + ek[1], c + ek[2]];
}

const SECICILER = [
  "#icerik .olcum-bolumu ul li a.etkin",
  ".istasyon-duzeni .olcum-bolumu .olcum-kartlari .kart .baslik",
  "section.olcum-bolumu > ul > li",
  ".olcum-kart__baslik",
  ".olcum-kart--uyari",
  ".olcum-kart:hover",
  ".olcum-kart[data-durum='eksik']",
  ":where(.olcum-kartlari) .olcum-kart__baslik",
  ":is(h2, .baslik).olcum-kart__baslik",
];

console.log("--- secici ozgullukleri ---");
console.log("secici".padEnd(58) + "ozgulluk".padStart(12) + "  derinlik");
for (const s of SECICILER) {
  const [a, b, c] = ozgulluk(s);
  const derinlik = s.split(/[\s>+~]+/).filter(Boolean).length;
  console.log(s.padEnd(58) + `(${a}, ${b}, ${c})`.padStart(12) + String(derinlik).padStart(10));
}

// --- ad kalibinin denetimi: blok__eleman--degistirici ---
const KALIP = /^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z][a-z0-9]*(-[a-z0-9]+)*)?(--[a-z][a-z0-9]*(-[a-z0-9]+)*)?$/;
const ADLAR = [
  "olcum-kart", "olcum-kart__baslik", "olcum-kart--genis", "olcum-kart__deger--eksik",
  "olcum-kart__govde__satir", "OlcumKart", "olcum_kart", "kart--", "olcum-kart__baslik--kisa",
];

console.log("\n--- ad kalibi denetimi ---");
for (const ad of ADLAR) {
  const parcali = ad.split("__");
  const cokluEleman = parcali.length > 2;
  const uygun = KALIP.test(ad) && !cokluEleman;
  const neden = cokluEleman ? "ic ice eleman adi" : !KALIP.test(ad) ? "kalip disi yazim" : "-";
  console.log(`  ${ad.padEnd(28)} ${(uygun ? "uygun" : "uygun degil").padEnd(12)} ${neden}`);
}

// --- ayni bilesenin iki yazimla ureteceği ozgulluk dagilimi ---
console.log("\n--- bir bilesenin butun kurallarinin ozgullugu ---");
const DERIN = [
  ".olcum-kartlari .kart",
  ".olcum-kartlari .kart .baslik",
  ".olcum-kartlari .kart .baslik span",
  ".olcum-kartlari .kart.genis .baslik",
];
const DUZ = [".olcum-kart", ".olcum-kart__baslik", ".olcum-kart__birim", ".olcum-kart--genis .olcum-kart__baslik"];
const dagilim = (liste) => liste.map((s) => ozgulluk(s).join("."));
console.log("  ic ice yazim:", dagilim(DERIN).join("  "));
console.log("  duz yazim   :", dagilim(DUZ).join("  "));
```

```
--- secici ozgullukleri ---
secici                                                        ozgulluk  derinlik
#icerik .olcum-bolumu ul li a.etkin                          (1, 2, 3)         5
.istasyon-duzeni .olcum-bolumu .olcum-kartlari .kart .baslik   (0, 5, 0)         5
section.olcum-bolumu > ul > li                               (0, 1, 3)         3
.olcum-kart__baslik                                          (0, 1, 0)         1
.olcum-kart--uyari                                           (0, 1, 0)         1
.olcum-kart:hover                                            (0, 2, 0)         1
.olcum-kart[data-durum='eksik']                              (0, 2, 0)         1
:where(.olcum-kartlari) .olcum-kart__baslik                  (0, 1, 0)         2
:is(h2, .baslik).olcum-kart__baslik                          (0, 2, 0)         2

--- ad kalibi denetimi ---
  olcum-kart                   uygun        -
  olcum-kart__baslik           uygun        -
  olcum-kart--genis            uygun        -
  olcum-kart__deger--eksik     uygun        -
  olcum-kart__govde__satir     uygun degil  ic ice eleman adi
  OlcumKart                    uygun degil  kalip disi yazim
  olcum_kart                   uygun degil  kalip disi yazim
  kart--                       uygun degil  kalip disi yazim
  olcum-kart__baslik--kisa     uygun        -

--- bir bilesenin butun kurallarinin ozgullugu ---
  ic ice yazim: 0.2.0  0.3.0  0.3.1  0.4.0
  duz yazim   : 0.1.0  0.1.0  0.1.0  0.2.0
```

Son blok yöntemin bütün gerekçesini iki satıra sığdırıyor. İç içe yazımda aynı bileşenin
dört kuralı dört farklı özgüllükte; birini diğerinden bastırmak için hangi seçicinin ne
kadar özgül olduğunu bilmek gerekir. Düz yazımda üç kural aynı özgüllükte ve aralarındaki
sıralamayı yalnız **yazım sırası** belirler.

Aynı özgüllükteki kurallar arasında karar vermek, farklı özgüllüktekiler arasında karar
vermekten kolaydır: sonra yazılan kazanır, başka bir hesap yoktur.

Üstteki tablo iki araç daha gösteriyor. `:where()` içine alınan seçici özgüllüğe hiç
katılmaz; kapsayıcıyı yazıp üçlüyü büyütmemenin yoludur. `:is()` ise içindeki en özgül dalı
sayar, dolayısıyla üçlüyü büyütür.

#### Kalıbın Denetlenebilir Olması

Bir adlandırma yöntemi, ancak makineyle denetlenebiliyorsa uygulanabilir kalır. İkinci blok
dokuz adı tek bir düzenli ifadeyle sınıyor ve dördünü geri çeviriyor.

Geri çevrilenlerden üçü yazım kuralı hatası: büyük harf, alt çizgi ayracının yanlış
kullanımı, boş değiştirici. Dördüncüsü daha ilginçtir: `olcum-kart__govde__satir` yazım
kurallarına uygundur ama **iki eleman adı** içerir.

Bu yasak bilinçlidir. İç içe eleman adı, belgenin ağaç yapısını ada gömer; başlık kartın
gövdesinden çıkarıldığında ad yanlış olur. Blok düzdür: bütün elemanlar doğrudan bloğa aittir ve
aralarındaki ağaç ilişkisi ada yazılmaz.

Aynı denetim, biçem dosyasında bulunup belgede kullanılmayan adları da bulur. Ad kalıbı
belirli olduğu için iki kümenin karşılaştırılması bir dizgi işidir.

#### Değiştirici mi, Durum mu

Değiştirici, bileşenin **tasarım zamanında** bilinen bir varyantıdır: geniş kart, uyarı
kartı. Belge üretilirken yazılır ve yaşam boyu değişmez.

Durum ise **çalışma zamanında** değişir: açık, seçili, yükleniyor, hatalı. İkisi aynı
mekanizmayla yazılırsa, betiğin hangi sınıfı değiştirebileceği belirsizleşir.

Ayrımı korumanın yolu durumu ayrı bir mekanizmaya taşımaktır. İki seçenek vardır ve ikisi de
`data-` öznitelikleri ya da `aria-` öznitelikleriyle çalışır:

```css
.olcum-kart[data-durum="eksik"] { border-color: var(--uyari); }
.olcum-kart[aria-expanded="true"] .olcum-kart__govde { display: block; }
```

İkincisi yeğlenir. Durum zaten erişilebilirlik ağacına bildiriliyorsa, biçem o bildirimi
okur; tek bir kaynak iki işi görür. Ayrı bir durum sınıfı yazmak, aynı bilgiyi iki yerde
tutmak demektir ve ikisi ayrışabilir.

#### Katman Önekleri

Adlandırma yöntemleri bileşen içini düzenler; bileşenler arasını düzenlemek için ada bir
**katman öneki** eklemek yaygındır. Önekler yaklaşımlara göre değişir ama işlev aynıdır:
addan, kuralın hangi katmandan geldiği okunur.

Ayrılan katmanlar genellikle şunlardır: sayfa iskeletini kuran düzen kuralları, taşınabilir
bileşenler, tek bildirimlik yardımcı sınıflar ve çalışma zamanı durumları. Bu ayrım
`@layer` ile de kurulabilir ve o zaman ada yazmaya gerek kalmaz — basamaklama katmanları
kuralın sırasını da bildirir. Katmanlar bu konunun altıncı dersinde ele alınacaktır.

#### İstasyon Sayfasının Yeniden Adlandırılması

```css
/* bilesenler/olcum-kart.css — 1. adım: bileşenin bütün kuralları tek dosyada */
.olcum-kart {
  display: grid;
  gap: var(--aralik-0);
  padding: var(--aralik-1);
  border: 1px solid var(--cizgi);
  transition: transform 240ms ease-out;
}

.olcum-kart__baslik { font-size: 1rem; color: var(--metin-soluk); }
.olcum-kart__deger  { font-size: 2rem; font-variant-numeric: tabular-nums; }
.olcum-kart__birim  { color: var(--metin-soluk); }

.olcum-kart--genis  { grid-column: span 2; }
.olcum-kart--ozet   { grid-column: 1 / -1; }

.olcum-kart[data-durum="eksik"] .olcum-kart__deger { color: var(--uyari); }

.olcum-kart:hover,
.olcum-kart:focus-within { transform: translateY(-8px) scale(1.04); }
```

Bileşenin görsel, düzen ve hareket bildirimleri tek dosyada toplandı. Üç dosyaya bölünmüş
hâlde bir kartın nasıl göründüğünü anlamak üç dosya okumayı gerektiriyordu; şimdi bileşen
başına bir dosya var ve dosya adı sınıf adının kökünden okunuyor.

Kapsayıcının ızgara tanımı — kartların yan yana nasıl dizildiği — bu dosyada değildir.
Bileşen kendi içini bilir, dışını bilmez; dış yerleşim `.olcum-kartlari` kuralında kalır.

#### Özet

- Adlandırma yöntemi adı ögenin belgedeki yerinden değil ait olduğu bileşenden türetir;
  sahiplik, özgüllük tırmanışı ve silinebilirlik sorunlarını birlikte ele alır.
- Blok taşınabilir bileşendir, eleman bloğa ait parçadır, değiştirici varyanttır; eleman
  adları iç içe geçmez, çünkü ağaç yapısı ada yazılmaz.
- Düz yazımda bileşenin bütün kuralları aynı özgüllüktedir ve sıralamayı yalnız yazım sırası
  belirler; iç içe yazımda her kural farklı özgüllüğe düşer.
- `:where()` özgüllüğe katılmaz, `:is()` içindeki en özgül dalı sayar; ikisi üçlüyü denetim
  altında tutmanın araçlarıdır.
- Tasarım zamanı varyantı değiştiriciyle, çalışma zamanı durumu öznitelikle yazılır;
  erişilebilirlik öznitelikleri varsa biçem onları okur.

#### Sonraki Adım

Bileşen dosyasındaki her kural `var(--cizgi)`, `var(--uyari)`, `var(--aralik-1)` gibi
adlara başvuruyor. Bu adlar CSS ile Görsel Sunum kursunda tanımlandı ve kök ögede tutuluyor.
Bir bileşenin kendi ölçeğini kurması, bir bölümün kendi temasını taşıması ya da bir
değiştiricinin yalnız birkaç değeri değiştirmesi gerektiğinde bu tek merkezli yapı yetmez.
Sonraki ders özel özellikleri bir mimari araç olarak ele alır.

### Özel Özellikler

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/ozel-ozellikler

Özel özelliklerin bildirildiği ögede hesaplanması, kalıtımın hesaplanmış değeri taşıması, yedek değer zincirleri, hesaplanan değerde geçersizlik ve bileşenin biçem arayüzünün kurulması.

Önceki dersin bileşen dosyasındaki her kural bir ada başvuruyor: `var(--cizgi)`,
`var(--uyari)`, `var(--aralik-1)`. Bu adlar CSS ile Görsel Sunum kursunda kök ögede
tanımlandı ve o kurs için yeterliydi.

Bileşen mimarisi daha fazlasını ister. Bir kart varyantı yalnız zemin rengini değiştirmeli;
yan bilgi bölümü kendi renk ölçeğini taşımalı; bir bileşen, dışarıdan hangi değerlerin
ayarlanabileceğini bildirmeli. Bu ders özel özelliklere mimari bir araç olarak bakar ve
önce hesaplanma anını netleştirir.

#### Hesaplanma Anı

**Özel özellik (custom property)** iki tire ile başlayan bir addır ve `var()` ile okunur.
Belirleyici kuralı, değerinin **bildirildiği ögede** hesaplanmasıdır.

Bu, kalıtımın ham metni değil **hesaplanmış değeri** taşıdığı anlamına gelir. Kök ögede
`--kart-zemin: var(--zemin)` yazıldığında, kökteki `--zemin` değeri yerine konur ve alt
ağaca giden şey artık o renktir. Bir alt ögede `--zemin` yeniden tanımlansa bile kökte
hesaplanmış `--kart-zemin` değişmez.

Aşağıdaki program bunu bir ağaç üzerinde çözüyor.

```js
// ozelozellik.mjs — ozel ozelliklerin hesaplanma ani, kalitim, yedek deger ve gecersizlik davranisi
// Kural: bir ozel ozelligin degeri, BILDIRILDIGI ogede hesaplanir; alt agaca hesaplanmis
// deger kalitilir. Alt agacta bir kaynak ozelligin yeniden tanimlanmasi, ustte hesaplanmis
// bir tureve geriye donuk etki etmez.
const AGAC = {
  ad: ":root",
  bildirim: { "--zemin": "hsl(203 18% 97%)", "--metin": "hsl(211 29% 16%)",
              "--kart-zemin": "var(--zemin)", "--aralik-1": "1.5rem" },
  cocuk: [{
    ad: ".yan-bilgi",
    bildirim: { "--zemin": "hsl(203 18% 92%)" },            // yalniz --zemin yeniden tanimli
    cocuk: [{
      ad: ".olcum-kart",
      bildirim: {},
      cocuk: [
        { ad: ".olcum-kart--uyari", bildirim: { "--kart-zemin": "hsl(0 66% 96%)" }, cocuk: [] },
        { ad: ".olcum-kart--sade",  bildirim: { "--kart-zemin": "yanlis-deger" },   cocuk: [] },
        { ad: ".olcum-kart--yerel", bildirim: { "--kart-zemin": "var(--zemin)" },   cocuk: [] },
      ],
    }],
  }],
};

const VAR_KALIBI = /var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/;

// metin icindeki var() basvurularini coz; ara(ad) bir ozel ozelligin degerini dondurur
function coz(metin, ara) {
  let s = metin, adim = 0;
  while (VAR_KALIBI.test(s)) {
    if (++adim > 50) return { deger: null, neden: "dongu" };
    const [tam, ad, yedek] = s.match(VAR_KALIBI);
    const bulunan = ara(ad);
    if (bulunan && bulunan.neden) return bulunan;                       // dongu yukari tasinir
    if (!bulunan || bulunan.deger === null) {
      if (yedek === undefined) return { deger: null, neden: `${ad} tanimsiz, yedek yok` };
      s = s.replace(tam, yedek);
    } else {
      s = s.replace(tam, bulunan.deger);
    }
  }
  return { deger: s.trim(), neden: null };
}

// bir ogenin ozel ozellik haritasi: kalitilan harita + kendi bildirimlerinin hesaplanmasi
function harita(dugum, kalitilan) {
  const sonuc = { ...kalitilan };
  const cozuluyor = new Set();
  const ara = (ad) => {
    if (ad in dugum.bildirim) {
      if (cozuluyor.has(ad)) return { deger: null, neden: "dongu" };
      cozuluyor.add(ad);
      const c = coz(dugum.bildirim[ad], ara);
      cozuluyor.delete(ad);
      return c;
    }
    return ad in kalitilan ? { deger: kalitilan[ad], neden: null } : null;
  };
  for (const ad of Object.keys(dugum.bildirim)) {
    const c = ara(ad);
    sonuc[ad] = c.deger;                                   // gecersizse null kalir
  }
  return sonuc;
}

const GECERLI_RENK = /^(hsl\(|rgb\(|#)/;
function gez(dugum, kalitilan = {}, yol = []) {
  const h = harita(dugum, kalitilan);
  const ad = [...yol, dugum.ad];
  const c = coz("var(--kart-zemin, var(--zemin))", (n) => (n in h ? { deger: h[n], neden: null } : null));
  let sonuc;
  if (c.deger === null) sonuc = `GECERSIZ (${c.neden})`;
  else if (!GECERLI_RENK.test(c.deger)) sonuc = `"${c.deger}" -> hesaplanan degerde gecersiz`;
  else sonuc = c.deger;
  console.log("  " + ad.join(" > ").padEnd(62) + sonuc);
  for (const c2 of dugum.cocuk) gez(c2, h, ad);
}

console.log("--- agacta background-color: var(--kart-zemin, var(--zemin)) ---");
gez(AGAC);

console.log("\n--- kok ogedeki yedek deger zincirleri ---");
const KOK = harita(AGAC, {});
const kokAra = (n) => (n in KOK ? { deger: KOK[n], neden: null } : null);
for (const yazim of [
  "var(--zemin)",
  "var(--yok)",
  "var(--yok, var(--zemin))",
  "var(--yok, var(--de-yok, hsl(0 0% 100%)))",
  "calc(var(--aralik-1) * 2)",
]) {
  const c = coz(yazim, kokAra);
  console.log(`  ${yazim.padEnd(42)} -> ${c.deger === null ? "GECERSIZ: " + c.neden : c.deger}`);
}

console.log("\n--- dongu ---");
const D = { ad: ":root", bildirim: { "--a": "var(--b)", "--b": "var(--a)" }, cocuk: [] };
console.log("  --a: var(--b); --b: var(--a);  -> --a degeri:", harita(D, {})["--a"]);
```

```
--- agacta background-color: var(--kart-zemin, var(--zemin)) ---
  :root                                                         hsl(203 18% 97%)
  :root > .yan-bilgi                                            hsl(203 18% 97%)
  :root > .yan-bilgi > .olcum-kart                              hsl(203 18% 97%)
  :root > .yan-bilgi > .olcum-kart > .olcum-kart--uyari         hsl(0 66% 96%)
  :root > .yan-bilgi > .olcum-kart > .olcum-kart--sade          "yanlis-deger" -> hesaplanan degerde gecersiz
  :root > .yan-bilgi > .olcum-kart > .olcum-kart--yerel         hsl(203 18% 92%)

--- kok ogedeki yedek deger zincirleri ---
  var(--zemin)                               -> hsl(203 18% 97%)
  var(--yok)                                 -> GECERSIZ: --yok tanimsiz, yedek yok
  var(--yok, var(--zemin))                   -> hsl(203 18% 97%)
  var(--yok, var(--de-yok, hsl(0 0% 100%)))  -> hsl(0 0% 100%)
  calc(var(--aralik-1) * 2)                  -> calc(1.5rem * 2)

--- dongu ---
  --a: var(--b); --b: var(--a);  -> --a degeri: null
```

İkinci satır dersin en sık atlanan noktasıdır. `.yan-bilgi` ögesi `--zemin` değerini
değiştiriyor, ama zemini `hsl(203 18% 97%)` kalıyor — çünkü okuduğu `--kart-zemin`, kök
ögede zaten hesaplanmıştır.

Son satır çözümü gösteriyor: `--kart-zemin` alt ağaçta yeniden bildirildiğinde, `var(--zemin)`
o ögede çözülür ve `hsl(203 18% 92%)` çıkar. Kural şudur: türev bir değerin bağlamdan
etkilenmesi isteniyorsa, türev o bağlamda bildirilmelidir.

`calc(var(--aralik-1) * 2)` satırı bir başka ayrıntıyı gösteriyor: yerine koyma yalnız
metin düzeyindedir. Değerin anlamlı olup olmadığı, yerine koyma bittikten sonra sınanır.

#### Yedek Değer ve Geçersizlik

`var()` ikinci bir argüman alır: adın tanımsız olduğu durumda kullanılacak **yedek değer**.
Yedek değerler iç içe yazılabilir ve çıktının ikinci bloğu iki katlı bir zinciri çözüyor.

Yedeğin devreye girmediği bir durum vardır ve tuzak oradadır: ad **tanımlıysa** ama değeri
kullanıldığı yerde anlamsızsa, yedek çalışmaz. `--kart-zemin: yanlis-deger` bildirimi
tanımlıdır; `background-color` bu değeri alır ve o noktada geçersiz olur.

Bu duruma **hesaplanan değerde geçersizlik (invalid at computed-value time)** denir ve
sonucu, ayrıştırma sırasındaki geçersizlikten farklıdır:

- Ayrıştırma sırasında geçersiz bir bildirim **atılır**; aynı özelliğe yazan önceki bildirim
  yerinde kalır.
- Hesaplanan değerde geçersiz bir bildirim atılmaz; özelliği `unset` durumuna getirir. Yani
  kalıtılan özelliklerde kalıtılan değer, kalıtılmayanlarda başlangıç değeri geçerli olur.

Önceki kural yerinde kalmadığı için sonuç genellikle beklenmedik olur. Bir renk bildirimi
yanlış bir özel özellik yüzünden düştüğünde, ögenin rengi ana ögesinden gelir.

Döngü ayrı bir durumdur: birbirine başvuran iki özel özellik hiçbir değer üretmez ve ikisi
de tanımsız sayılır.

#### Kayıtlı Özellikler

`@property` kuralı bir özel özelliği **kaydeder**: değerinin sözdizimini, kalıtılıp
kalıtılmayacağını ve başlangıç değerini bildirir.

```css
@property --kart-zemin {
  syntax: "<color>";
  inherits: true;
  initial-value: hsl(203 18% 97%);
}
```

Kaydın üç sonucu vardır. Birincisi tip denetimidir: bildirilen sözdizimine uymayan bir değer
hesaplanan değerde geçersiz sayılır, ama kayıtsız bir özellikte olduğu gibi `unset`'e düşmez —
kaydın başlangıç değerine döner. İkincisi kalıtımın denetlenebilmesidir; `inherits: false` yazılan
bir özellik yalnız bildirildiği ögede geçerli olur. Üçüncüsü hareketle ilgilidir: kayıtsız bir
özel özellik yalnız metindir ve ara değeri hesaplanamaz, kayıtlı bir renk ya da uzunluk ise geçişe
ve anahtar karelere girebilir.

Üçüncü sonuç, önceki konudaki hareket bildirimleriyle birleşir: bir renk ölçeğinin kendisi
animasyona sokulabilir hâle gelir.

#### Bileşenin Biçem Arayüzü

Buraya kadarki kurallar bir mimari kalıba varır. Bileşen, dışarıya açtığı ayar noktalarını
özel özellik olarak tanımlar; içeride yalnız bu adları okur.

```css
.olcum-kart {
  --kart-zemin: var(--zemin);
  --kart-cerceve: var(--cizgi);
  --kart-vurgu-yolu: -8px;

  background-color: var(--kart-zemin);
  border: 1px solid var(--kart-cerceve);
}

.olcum-kart:hover { transform: translateY(var(--kart-vurgu-yolu)); }

.olcum-kart--uyari { --kart-zemin: hsl(0 66% 96%); --kart-cerceve: var(--uyari); }
.olcum-kart--durgun { --kart-vurgu-yolu: 0px; }
```

Değiştiriciler artık bildirim ezmiyor, **değer veriyor**. `.olcum-kart--uyari` kuralı
`background-color` yazmıyor; yalnız bileşenin okuduğu adı değiştiriyor. Bunun üç sonucu var:
özgüllük yarışı çıkmaz, varyant sayısı arttıkça kural sayısı artmaz ve bileşenin neyin
ayarlanabilir olduğu dosyanın ilk satırlarından okunur.

Ad öneki de bir sözleşmedir: `--kart-` ile başlayanlar bileşenin arayüzüdür, `--zemin` gibi
öneksiz adlar sayfa ölçeğinden gelir. İkisi karışırsa bileşenin dışarıdan ne beklediği
belirsizleşir.

#### Tema Yönetimi

Tema, aynı arayüzü farklı değerlerle doldurmaktır. İki katmanlı bir ölçek bunu ayırır:
ham değerleri tutan adlar ve işlevi adlandıran adlar. Tema yalnız ikinci katmanı değiştirir.

```css
:root {
  --gri-95: hsl(203 18% 97%);
  --gri-15: hsl(211 29% 16%);

  --zemin: var(--gri-95);
  --metin: var(--gri-15);
}

[data-tema="koyu"] {
  --zemin: var(--gri-15);
  --metin: var(--gri-95);
}
```

Tema bir öznitelikle bildirildiği için alt ağaç ölçeğinde de uygulanabilir: sayfanın tamamı
açık, tek bir bölüm koyu olabilir. Kalıtım bunu kendiliğinden sağlar; ek bir kural gerekmez.

Aynı yapı ortam sorgusuyla da sürülebilir — kullanıcının renk şeması tercihi, öznitelik
yerine sorgu içinde aynı iki bildirimi yazar. İki kaynak birlikte kullanılıyorsa hangisinin
öncelikli olduğu bir karardır: kullanıcının sayfa içinde yaptığı seçim genellikle sistem
tercihini ezer, çünkü daha sonra ve daha özgül yazılır.

#### Özet

- Özel özellik bildirildiği ögede hesaplanır; alt ağaca hesaplanmış değer kalıtılır, ham
  metin değil.
- Bir türev değerin bağlamdan etkilenmesi için o bağlamda yeniden bildirilmesi gerekir;
  kaynak adı alt ağaçta değiştirmek üstte hesaplanmış türevi değiştirmez.
- Yedek değer yalnız ad **tanımsızsa** çalışır; tanımlı ama anlamsız bir değer hesaplanan
  değerde geçersizlik üretir ve özelliği `unset` durumuna getirir.
- `@property` kaydı sözdizimi, kalıtım ve başlangıç değeri bildirir; kayıtlı özellikler tip
  denetimi kazanır ve geçişe girebilir.
- Bileşen, ayar noktalarını önekli özel özellik olarak açtığında varyantlar bildirim ezmek
  yerine değer verir; özgüllük düz kalır ve arayüz okunur olur.

#### Sonraki Adım

Buradaki bütün adlar tarayıcıda, çalışma zamanında çözülüyor. Bir başka araç sınıfı aynı
işlerin bir bölümünü daha belge tarayıcıya ulaşmadan yapar: iç içe yazımı düzleştirmek,
yinelenen blokları tek yerde tutmak, dosyaları birleştirmek. Sonraki ders derleme zamanında
çalışan biçem araçlarını ele alır ve hangi işin çalışma zamanına bırakılması gerektiğini
ayırır.

### Ön ve Son İşlemciler

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/on-ve-son-islemciler

Derleme zamanında çalışan biçem araçları; iç içe yazımın düzleştirilmesi, gövde kopyalamanın bedeli, derleme zamanı değeriyle çalışma zamanı değerinin ayrımı ve son işlemcilerin dönüştürmeleri.

Önceki dersteki bütün adlar tarayıcıda, sayfa çalışırken çözülüyordu. Bir başka araç sınıfı
işlerin bir bölümünü daha erken yapar: dosya tarayıcıya ulaşmadan önce, derleme sırasında.

Bu ders o araçların ne yaptığını, hangi işin derleme zamanına hangisinin çalışma zamanına
ait olduğunu ve derleme adımının bedelini ele alır. Belirli bir araç değil, iki araç sınıfı
anlatılır.

#### İki Araç Sınıfı

**Ön işlemci (preprocessor)**, CSS olmayan bir kaynak dilini girdi alır ve CSS üretir.
Kaynak dil CSS'in üzerine yazım kolaylıkları ekler: iç içe kural yazımı, derleme zamanı
değişkenleri, yeniden kullanılan bildirim gövdeleri, dosya bölme ve içe alma, döngü ve
koşul.

**Son işlemci (postprocessor)**, CSS alır ve CSS üretir. Yaptığı iş dönüştürmedir: yazımı
başka bir yazıma çevirmek, gereksiz karakterleri atmak, kuralları birleştirmek, kullanılmayan
kuralları çıkarmak.

Sınır kesin değildir; ikisi de derleme zamanında çalışan program dönüştürücüleridir. Ayrımı
yapan, girdinin CSS olup olmadığıdır. Bir yapı zincirinde ikisi art arda kullanılabilir.

#### İç İçe Yazımın Düzleştirilmesi

En çok kullanılan kolaylık iç içe yazımdır. Kaynakta kurallar birbirinin içine yazılır,
derleme sonunda düz kurallara açılır.

```js
// onisleme.mjs — ic ice yazimin duzlestirilmesi ve derleme zamani yinelemesinin sayimi
// Ic ice bir kural agaci, derleme zamaninda duz kurallara acilir.
const KAYNAK = {
  secici: ".olcum-kart",
  bildirim: ["display: grid", "gap: var(--aralik-0)"],
  ic: [
    { secici: "&__baslik", bildirim: ["color: var(--metin-soluk)"], ic: [] },
    { secici: "&__deger", bildirim: ["font-size: 2rem"], ic: [
      { secici: "&--eksik", bildirim: ["color: var(--uyari)"], ic: [] },
    ] },
    { secici: "&:hover", bildirim: ["transform: translateY(-8px)"], ic: [] },
    { secici: ".olcum-kartlari &", bildirim: ["margin: 0"], ic: [] },
    { secici: "p", bildirim: ["margin-block: 0"], ic: [] },              // & yok: alt oge birlestiricisi
    { secici: "@media (min-width: 48rem)", bildirim: [], ic: [
      { secici: "&", bildirim: ["gap: var(--aralik-1)"], ic: [] },
    ] },
  ],
};

function duzlestir(dugum, ustSecici = null, kosul = null, cikti = []) {
  const kosullu = dugum.secici.startsWith("@");
  const yeniKosul = kosullu ? dugum.secici : kosul;
  let secici = ustSecici;
  if (!kosullu) {
    if (ustSecici === null) secici = dugum.secici;
    else if (dugum.secici.includes("&")) secici = dugum.secici.replaceAll("&", ustSecici);
    else secici = `${ustSecici} ${dugum.secici}`;
  }
  if (dugum.bildirim.length > 0) cikti.push({ kosul: yeniKosul, secici, bildirim: dugum.bildirim });
  for (const c of dugum.ic) duzlestir(c, secici, yeniKosul, cikti);
  return cikti;
}

// ozgulluk uclusu: a=kimlik, b=sinif/oznitelik/sozde sinif, c=tur
const ozgulluk = (s) => [
  (s.match(/#[\w-]+/g) || []).length,
  (s.match(/\.[\w-]+/g) || []).length + (s.match(/\[[^\]]+\]/g) || []).length + (s.match(/:(?!:)[\w-]+/g) || []).length,
  (s.replace(/:(?!:)[\w-]+/g, " ").match(/(^|[\s>+~])([a-zA-Z][\w-]*)/g) || []).length,
];

console.log("--- ic ice kaynagin duzlestirilmesi ---");
for (const k of duzlestir(KAYNAK)) {
  const [a, b, c] = ozgulluk(k.secici);
  const on = k.kosul ? `${k.kosul} { ` : "";
  const arka = k.kosul ? " }" : "";
  console.log(`  ${(on + k.secici + " { " + k.bildirim.join("; ") + " }" + arka).padEnd(78)} (${a}, ${b}, ${c})`);
}

// --- derleme zamani yinelemesi: her cagrida govde kopyalanir ---
console.log("\n--- ayni bildirim kumesini paylasmanin iki yolu ---");
const GOVDE = ["border: 1px solid var(--cizgi)", "border-radius: 4px", "padding: var(--aralik-1)"];
const govdeBayt = GOVDE.join("; ").length + 2;
const BILESENLER = [".olcum-kart", ".yan-bilgi__kutu", ".kunye", ".konum-bolumu__pano", ".olcum-suzgeci"];

let kopyali = 0;
for (const b of BILESENLER) kopyali += b.length + 3 + govdeBayt;
const ortakSecici = BILESENLER.join(", ");
const ortak = ortakSecici.length + 3 + govdeBayt;

console.log(`  govde: ${GOVDE.length} bildirim, ${govdeBayt} bayt`);
console.log(`  ${BILESENLER.length} bilesene govdeyi kopyalayan yazim : ${kopyali} bayt`);
console.log(`  ortak secici listesiyle tek kural          : ${ortak} bayt`);
console.log(`  fark: ${kopyali - ortak} bayt (%${(((kopyali - ortak) / kopyali) * 100).toFixed(1)} daha az)`);

// --- derleme zamani degeri ile calisma zamani degerinin ayrimi ---
console.log("\n--- ayni ad, iki cozum ani ---");
const DERLEME = { "$aralik": "1.5rem" };                    // derleme zamaninda yerine konur
const uretilen = "padding: $aralik;".replace(/\$[\w-]+/g, (m) => DERLEME[m]);
console.log("  derleme zamani degiskeni -> uretilen CSS:", uretilen);
console.log("  calisma zamani ozelligi  -> uretilen CSS: padding: var(--aralik-1);");
console.log("  ilk yazimda uretilen dosyada 'aralik' adi yoktur; deger sabittir.");
```

```
--- ic ice kaynagin duzlestirilmesi ---
  .olcum-kart { display: grid; gap: var(--aralik-0) }                            (0, 1, 0)
  .olcum-kart__baslik { color: var(--metin-soluk) }                              (0, 1, 0)
  .olcum-kart__deger { font-size: 2rem }                                         (0, 1, 0)
  .olcum-kart__deger--eksik { color: var(--uyari) }                              (0, 1, 0)
  .olcum-kart:hover { transform: translateY(-8px) }                              (0, 2, 0)
  .olcum-kartlari .olcum-kart { margin: 0 }                                      (0, 2, 0)
  .olcum-kart p { margin-block: 0 }                                              (0, 1, 1)
  @media (min-width: 48rem) { .olcum-kart { gap: var(--aralik-1) } }             (0, 1, 0)

--- ayni bildirim kumesini paylasmanin iki yolu ---
  govde: 3 bildirim, 78 bayt
  5 bilesene govdeyi kopyalayan yazim : 471 bayt
  ortak secici listesiyle tek kural          : 155 bayt
  fark: 316 bayt (%67.1 daha az)

--- ayni ad, iki cozum ani ---
  derleme zamani degiskeni -> uretilen CSS: padding: 1.5rem;
  calisma zamani ozelligi  -> uretilen CSS: padding: var(--aralik-1);
  ilk yazimda uretilen dosyada 'aralik' adi yoktur; deger sabittir.
```

Düzleştirme tek bir kurala dayanır: iç kuralda `&` varsa üst seçicinin yerine konur, yoksa
üst seçiciyle arasına alt öge birleştiricisi girer.

Bu ayrım çıktının yedinci satırında görünüyor. `p` seçicisi `&` taşımadığı için
`.olcum-kart p` oluyor; üçlüsü de $(0, 1, 1)$'e çıkıyor. Dördüncü satırda ise `&__deger`
yazımı seçiciyi **birleştirerek** tek sınıfa çeviriyor ve üçlü $(0, 1, 0)$ kalıyor. Önceki
dersin adlandırma yöntemi iç içe yazımla ancak bu ikinci biçimde birlikte çalışır.

Altıncı satır `&` işaretinin sonda da yazılabileceğini gösteriyor: `.olcum-kartlari &` yazımı
bileşeni bir bağlamın içine yerleştirir ve üçlüyü büyütür.

Bir uyarı gerekir: iç içe yazım CSS'in kendisinde de tanımlıdır. Bu durumda düzleştirmeyi
derleyici değil tarayıcı yapar ve kural aynı kalır. Yani iç içe yazım tek başına bir derleme
adımını gerektirmez.

#### Yinelemenin Bedeli

Ön işlemcilerin ikinci yaygın kolaylığı, bir bildirim gövdesini adlandırıp birden çok yerde
çağırmaktır. Çağrı derleme zamanında **kopyalanır**; üretilen dosyada aynı bildirimler her
seçicinin altında ayrı ayrı yer alır.

Çıktının ikinci bloğu beş bileşen için bunu sayıyor: kopyalayan yazım 471 bayt, ortak seçici
listesiyle yazılan tek kural 155 bayt. Fark, gövdenin kaç kez çağrıldığıyla doğru orantılı
büyür.

Bu, gövde paylaşımının yasak olduğu anlamına gelmez; kararın ölçütünü verir. Gövde birkaç
bildirimden uzunsa ve çok sayıda yerde çağrılıyorsa ortak bir seçici listesi ya da ortak bir
sınıf daha az bayt üretir. Kısa ve az sayıda çağrılan gövdelerde kopyalama okunurluğu artırır.

#### Derleme Zamanı Değeri ile Çalışma Zamanı Değeri

Üçüncü blok iki değişken türünü aynı sonuç üzerinden karşılaştırıyor. Derleme zamanı
değişkeni üretilen dosyada **iz bırakmaz**: `padding: 1.5rem` yazılıdır, `aralik` adı yoktur.
Çalışma zamanı özelliği ise dosyada durur ve tarayıcıda çözülür.

Ayrım, hangisinin seçileceğini belirler:

- Değer bağlama göre değişecekse — ortam sorgusuna, kullanıcı tercihine, bir öznitelik
  değerine, betiğe göre — **çalışma zamanı** özelliği gerekir. Derleme zamanı değeri
  bunların hiçbirini göremez.
- Değer yalnız kaynağı düzenlemek içinse — bir dosya yolu, bir kesme noktası sayısı, bir
  döngü sınırı — derleme zamanı değişkeni yeterlidir ve üretilen dosyayı küçültür.

Karışık kullanım da olağandır: kaynak ölçek derleme zamanında hesaplanır, hesaplanan
değerler çalışma zamanı özelliklerine yazılır.

#### Son İşlemcilerin İşleri

Son işlemcilerin dönüştürmeleri dört öbekte toplanabilir.

**Yazım genişletme.** Bir özelliğin henüz her yerde çözülmeyen yazımı, eşdeğer eski yazımla
birlikte üretilir. Kaynak tek yazımla kalır, üretilen dosya iki yazımı da taşır.

**Küçültme.** Boşluk, yorum ve gereksiz noktalı virgüller atılır; renkler ve sayılar en kısa
eşdeğer yazımlarına çevrilir. Anlam değişmez, bayt sayısı düşer.

**Birleştirme.** Aynı koşulu taşıyan ortam sorguları tek bloğa toplanır; aynı bildirim
kümesine sahip seçiciler tek kuralda birleştirilir.

**Kullanılmayan kural ayıklama.** Belgede eşleşmeyen seçiciler çıkarılır. Bu dönüştürme
risklidir: sınıf adı çalışma zamanında üretiliyorsa — bir öznitelik değerinden birleştirilerek
ya da betikle eklenerek — ayıklayıcı o adı göremez ve gerekli kuralı siler. Bu yüzden
adların kaynakta tam metin olarak geçmesi bir yazım kuralına dönüşür.

#### Derleme Adımının Bedeli

Derleme adımı üç maliyet getirir. Birincisi kurulumdur: kaynak dosyaların doğrudan
çalışmaması, her değişikliğin bir yapı komutundan geçmesi.

İkincisi hata ayıklama uzaklığıdır. Tarayıcı üretilen dosyayı gösterir, yazdığınız dosyayı
değil. **Kaynak eşlemesi (source map)** bu uzaklığı kapatır: üretilen dosyadaki her konumun
kaynaktaki karşılığını tutan bir yan dosya üretilir ve geliştirici araçları kuralı kaynak
satırıyla gösterir.

Üçüncüsü bağımlılıktır. Kaynak dil CSS değilse, dosyalar o dilin araç zinciri olmadan
okunamaz. Bu bedel, kaynak dilin sağladığı kolaylıklar platformun kendisine geçtikçe azalır.

#### İstasyon Sayfasının Kaynağı

```css
/* bilesenler/olcum-kart.css — 3. adım: iç içe yazımla aynı bileşen */
.olcum-kart {
  display: grid;
  gap: var(--aralik-0);

  &__baslik { color: var(--metin-soluk); }
  &__deger  { font-size: 2rem; font-variant-numeric: tabular-nums; }
  &__deger--eksik { color: var(--uyari); }

  &:hover,
  &:focus-within { transform: translateY(var(--kart-vurgu-yolu, -8px)); }

  @media (prefers-reduced-motion: reduce) {
    &:hover,
    &:focus-within { transform: none; }
  }
}
```

Bileşenin bütün kuralları tek bir blokta toplandı ve hepsi `&` ile birleşen tek sınıf
seçicileri üretiyor. Ortam sorgusu da bileşenin içinde duruyor; hareket azaltma kuralı artık
ayrı bir dosyada değil, ait olduğu bileşenin yanında.

Bu yazımın sınırı, iç içe girmenin derinliğidir. İki düzeyden fazla iç içe yazım, üretilen
seçicilerin okunmasını zorlaştırır ve `&` taşımayan bir iç kural üçlüyü sessizce büyütür.

#### Özet

- Ön işlemci CSS olmayan bir kaynak dilinden CSS üretir; son işlemci CSS alıp CSS üretir.
  İkisi de derleme zamanında çalışır ve sınırları kesin değildir.
- İç içe yazım tek kuralla düzleşir: `&` varsa üst seçicinin yerine konur, yoksa alt öge
  birleştiricisi girer ve özgüllük üçlüsü büyür.
- Adlandırılmış bir bildirim gövdesinin her çağrısı üretilen dosyaya kopyalanır; çok sayıda
  çağrıda ortak bir seçici listesi daha az bayt üretir.
- Derleme zamanı değeri üretilen dosyada iz bırakmaz ve bağlamı göremez; bağlama göre
  değişmesi gereken her değer çalışma zamanı özelliği olmalıdır.
- Kullanılmayan kural ayıklama, sınıf adları kaynakta tam metin olarak geçmiyorsa gerekli
  kuralları silebilir.

#### Sonraki Adım

Derleme adımı bir sorunu çözmedi: adlar hâlâ küresel. İki ayrı bileşen dosyasında aynı sınıf
adı yazılırsa kurallar birbirine karışır ve önceki dersteki adlandırma yöntemi bunu yalnız
**sözleşmeyle** önler — kimse denetlemezse çakışma olur. Sonraki ders adların kapsamını
gerçekten sınırlayan yaklaşımları ele alır.

### Kapsamlı Biçem Yaklaşımları

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/kapsamli-bicem-yaklasimlari

Adların kapsamını gerçekten sınırlayan yaklaşımlar; üretilmiş yerel adlar, bildirim karmasına göre sınıf paylaşımı, belge kapsamı bildirimi, gölge ağaç kapsüllemesi ve yaklaşımların verdiği güvencelerin karşılaştırılması.

Önceki dersin sonunda bir sorun açıkta kaldı. Adlandırma yöntemi çakışmayı önler ama bunu
yalnız **sözleşmeyle** yapar: iki geliştirici aynı bloğa aynı adı verirse hiçbir araç
uyarmaz, kurallar sessizce birbirine karışır.

Bu ders adın kapsamını sözleşmeden çıkarıp mekanizmaya bağlayan yaklaşımları ele alır. Önce
"kapsam" sözcüğünün neyi güvenceye aldığını ayırmak gerekir.

#### Kapsam Neyi Güvenceye Alır

Biçem sızıntısı tek bir olay değildir; üç ayrı olaydır.

**Ad çakışması.** İki ayrı bileşen aynı sınıf adını kullanır; birinin kuralları diğerinin
ögelerine de uyar.

**Dışarıdan sızma.** Bileşenin dışında yazılmış genel bir kural — `p`, `a`, `* + *` gibi —
bileşenin içindeki ögelere uyar.

**Dışarı sızma.** Bileşenin içinde yazılmış bir kural, bileşenin dışındaki ögelere uyar.
Alt öge birleştiricisiyle yazılmış her kuralın taşıdığı risktir.

Bir yaklaşımın "kapsamlı" sayılması, üçünü birden çözdüğü anlamına gelmez. Aşağıdaki program
önce iki üretim yöntemini hesaplıyor, sonra beş yaklaşımın hangi yönü engellediğini
karşılaştırıyor.

```js
// kapsam.mjs — yerel kapsamli ad uretimi ve bildirim karmasina gore sinif paylasimi
import { createHash } from "node:crypto";

const karma = (metin, uzunluk = 5) =>
  createHash("sha256").update(metin).digest("hex").slice(0, uzunluk);

// --- 1. yerel ad + dosya yolu -> benzersiz uretilmis ad ---
const uret = (yol, yerelAd) => `${yerelAd}_${karma(`${yol}#${yerelAd}`)}`;

const GIRDILER = [
  ["bilesenler/olcum-kart.css", "kart"],
  ["bilesenler/olcum-kart.css", "baslik"],
  ["bilesenler/yan-bilgi.css", "kart"],          // ayni yerel ad, baska dosya
  ["bilesenler/yan-bilgi.css", "baslik"],
  ["bilesenler/olcum-kart.css", "kart"],         // ayni girdi: ayni ad uretilmeli
];

console.log("--- yerel adlarin uretilmis adlara eslenmesi ---");
const uretilen = new Map();
for (const [yol, ad] of GIRDILER) {
  const u = uret(yol, ad);
  console.log(`  ${yol.padEnd(30)} .${ad.padEnd(10)} -> .${u}`);
  uretilen.set(`${yol}#${ad}`, u);
}
const cakisma = new Set([...uretilen.values()]).size !== uretilen.size;
console.log(`  ayri girdi sayisi: ${uretilen.size}, uretilen ad sayisi: ${new Set([...uretilen.values()]).size}, cakisma: ${cakisma}`);

// --- 2. bildirim kumesinin karmasina gore sinif paylasimi ---
const duzenle = (bildirimler) =>
  bildirimler.map((b) => b.replace(/\s+/g, " ").trim()).sort().join(";");
const sinif = (bildirimler) => "b" + karma(duzenle(bildirimler), 7);

const BILESENLER = {
  "olcum-kart":      ["padding: 1.5rem", "border: 1px solid #ccd", "border-radius: 4px"],
  "yan-bilgi-kutu":  ["border-radius: 4px", "padding:  1.5rem", "border: 1px solid #ccd"],  // ayni kume
  "kunye":           ["padding: 1.5rem", "border: 1px solid #ccd"],
  "konum-panosu":    ["padding: 1.5rem", "border: 1px solid #ccd", "border-radius: 4px"],
};

console.log("\n--- bildirim kumesi karmasina gore uretilen sinif ---");
const uretilenKural = new Map();
for (const [ad, bildirimler] of Object.entries(BILESENLER)) {
  const s = sinif(bildirimler);
  uretilenKural.set(s, duzenle(bildirimler));
  console.log(`  ${ad.padEnd(18)} -> .${s}`);
}
console.log(`  bilesen sayisi: ${Object.keys(BILESENLER).length}, uretilen kural sayisi: ${uretilenKural.size}`);
for (const [s, govde] of uretilenKural) console.log(`    .${s} { ${govde.replaceAll(";", "; ")} }`);

// --- 3. kapsamlama yaklasimlarinin verdigi guvenceler ---
console.log("\n--- hangi yaklasim neyi guvenceye alir ---");
const YAKLASIMLAR = [
  { ad: "yalniz adlandirma sozlesmesi", adCakismasi: false, disaridanSizma: false, disariSizma: false },
  { ad: "uretilmis yerel ad",           adCakismasi: true,  disaridanSizma: false, disariSizma: true },
  { ad: "calisma zamani uretimi",       adCakismasi: true,  disaridanSizma: false, disariSizma: true },
  { ad: "belge kapsami bildirimi",      adCakismasi: false, disaridanSizma: false, disariSizma: true },
  { ad: "golge agac kapsullemesi",      adCakismasi: true,  disaridanSizma: true,  disariSizma: true },
];
console.log("yaklasim".padEnd(30) + "ad cakismasi".padStart(14) + "disaridan sizma".padStart(18) + "disari sizma".padStart(15));
for (const y of YAKLASIMLAR) {
  const i = (v) => (v ? "engellenir" : "engellenmez");
  console.log(y.ad.padEnd(30) + i(y.adCakismasi).padStart(14) + i(y.disaridanSizma).padStart(18) + i(y.disariSizma).padStart(15));
}
```

```
--- yerel adlarin uretilmis adlara eslenmesi ---
  bilesenler/olcum-kart.css      .kart       -> .kart_39622
  bilesenler/olcum-kart.css      .baslik     -> .baslik_fc1e4
  bilesenler/yan-bilgi.css       .kart       -> .kart_28bdd
  bilesenler/yan-bilgi.css       .baslik     -> .baslik_4d665
  bilesenler/olcum-kart.css      .kart       -> .kart_39622
  ayri girdi sayisi: 4, uretilen ad sayisi: 4, cakisma: false

--- bildirim kumesi karmasina gore uretilen sinif ---
  olcum-kart         -> .b3fd5e52
  yan-bilgi-kutu     -> .b3fd5e52
  kunye              -> .bc3527a6
  konum-panosu       -> .b3fd5e52
  bilesen sayisi: 4, uretilen kural sayisi: 2
    .b3fd5e52 { border-radius: 4px; border: 1px solid #ccd; padding: 1.5rem }
    .bc3527a6 { border: 1px solid #ccd; padding: 1.5rem }

--- hangi yaklasim neyi guvenceye alir ---
yaklasim                        ad cakismasi   disaridan sizma   disari sizma
yalniz adlandirma sozlesmesi     engellenmez       engellenmez    engellenmez
uretilmis yerel ad                engellenir       engellenmez     engellenir
calisma zamani uretimi            engellenir       engellenmez     engellenir
belge kapsami bildirimi          engellenmez       engellenmez     engellenir
golge agac kapsullemesi           engellenir        engellenir     engellenir
```

#### Üretilmiş Yerel Adlar

Birinci yaklaşımda kaynak dosyalarda kısa, okunur adlar yazılır: `.kart`, `.baslik`. Bir
derleme adımı bu adları dosya yoluyla birlikte karmalayarak benzersiz adlara çevirir ve
belgeyi üreten şablon da aynı eşlemeyi kullanır.

Çıktının ilk bloğu iki özelliği gösteriyor. İki ayrı dosyadaki `.kart` adı farklı adlara
çevriliyor — çakışma yapısal olarak olanaksız. Aynı girdi ise her zaman aynı adı üretiyor;
üretim **belirlenimcidir**, iki ayrı derlemede aynı çıktı verir.

Üretilen adın başına yerel adın konması bilinçlidir: geliştirici araçlarında `.kart_39622`
görüldüğünde kaynağın hangi ad olduğu okunur. Yalnız karmadan oluşan adlar bu okunurluğu
kaybeder.

Yaklaşımın sınırı, adın **çalışma zamanında birleştirilememesidir**. Ad bir dizgi
birleştirmesiyle üretiliyorsa derleyici o adı göremez ve eşleyemez; adlar kaynakta tam metin
olarak geçmelidir.

#### Bildirim Karmasına Göre Paylaşım

İkinci yaklaşımda sınıf adı, bildirimlerin kendisinden türetilir. Bildirim kümesi
sıralanarak tek bir metne indirgenir ve o metnin karması sınıf adı olur.

Çıktının ikinci bloğu dört bileşen için iki kural üretiyor. Üç bileşenin bildirimleri aynı —
yazım sırası ve fazladan boşluk farklı olsa da düzenlenmiş metin aynı — ve üçü de aynı sınıfı
paylaşıyor. Bildirim kümesi farklı olan tek bileşen kendi sınıfını alıyor.

Sonuç, üretilen dosyanın bileşen sayısıyla değil **ayrı bildirim kümesi sayısıyla** büyümesidir.
Aynı gövdeyi kopyalayan önceki dersin yazımıyla karşılaştırıldığında yineleme kendiliğinden
ortadan kalkar.

Bedeli üç başlıkta toplanır. Üretim çalışma zamanında yapılıyorsa kuralların belgeye
eklenmesi ilk çizimden sonraya düşebilir. Kuralların basamaklamadaki sırası üretim sırasına
bağlıdır ve kaynağı okuyarak öngörülemez. Karma adlar okunur değildir; hata ayıklama aracı
kuralı gösterir ama hangi bileşenden geldiğini göstermez.

#### Belge Kapsamı Bildirimi

CSS'in kendi aracı `@scope` kuralıdır. Bir **kapsam kökü** ve isteğe bağlı bir **kapsam
sınırı** alır; içindeki kurallar yalnız kökün alt ağacında, sınırın dışında kalan ögelere
uyar.

```css
@scope (.olcum-kart) to (.olcum-kart__govde) {
  p { margin-block: 0; }
  a { color: var(--marka-koyu); }
}
```

Buradaki `p` kuralı yalnız ölçüm kartının içinde ve kart gövdesine girmeden önce geçerlidir.
Aynı kural kapsamsız yazılsaydı sayfadaki bütün paragraflara uyardı.

`@scope` bir yenilik daha getirir: aynı özelliğe yazan iki kapsamlı kural arasında, kapsam kökü
ögeye **daha yakın** olan kazanır. Bu ölçüt özgüllükten sonra, belge sırasından önce
değerlendirilir. İç içe geçmiş iki tema kapsamında içteki temanın kazanması bundandır.

Tablonun gösterdiği sınır önemlidir: `@scope` dışarı sızmayı engeller, ad çakışmasını
engellemez. İki bileşen aynı sınıf adını kullanıyorsa ve ikisi de kapsamsız kural yazıyorsa
sorun sürer.

#### Gölge Ağaç Kapsüllemesi

Üç yönü birden engelleyen tek yaklaşım gölge ağaçtır. Web Components ile kurulan bir gölge
kökün içindeki biçem dosyası yalnız o ağaca uygulanır; dışarıdaki seçiciler içeriye,
içerideki seçiciler dışarıya uymaz.

Kapsüllemenin bilinçli üç deliği vardır. Kalıtılan özellikler sınırı geçer: yazı tipi ve
renk dışarıdan içeriye akar. Özel özellikler de kalıtılır, dolayısıyla bileşenin biçem
arayüzü aynı mekanizmayla dışarıdan doldurulur. `::part` sözde elemanı ise bileşenin
dışarıya açtığı parçaları hedeflenebilir kılar.

Bu delikler kusur değil tasarımdır: tam yalıtım, bileşeni temaya kapatırdı.

#### Seçim Ölçütü

Yaklaşımlar birbirini dışlamaz ve seçim üç soruya bağlıdır. Bileşenler ayrı takımlarca mı
yazılıyor — ad çakışması gerçek bir risk mi? Sayfaya dışarıdan biçem geliyor mu — dışarıdan
sızma korunması gerekiyor mu? Bir derleme adımı zaten var mı?

Küçük bir sayfada adlandırma sözleşmesi ile `@scope` birlikte yeterlidir; ikisi de derleme
adımı gerektirmez. Bileşenlerin bağımsız dağıtıldığı bir yerde üretilmiş adlar ya da gölge
ağaç gerekir.

#### İstasyon Sayfasında

```css
/* bilesenler/olcum-kart.css — 4. adım: kapsamlı yazım */
@scope (.olcum-kart) {
  :scope {
    display: grid;
    gap: var(--aralik-0);
    background-color: var(--kart-zemin, var(--zemin));
  }

  .olcum-kart__deger { font-size: 2rem; font-variant-numeric: tabular-nums; }

  p  { margin-block: 0; }
  dt { color: var(--metin-soluk); }
}
```

`:scope` sözde sınıfı kapsam kökünün kendisini hedefler. Tür seçicileri artık güvenle
yazılabiliyor: `p` ve `dt` kuralları yalnız kartın içinde geçerli, sayfanın geri kalanına
sızmıyor.

Sınıf adları yine de yöntemli yazıldı. Kapsam dışarı sızmayı kesiyor, ama sayfada başka bir
`.olcum-kart__deger` tanımlanırsa iki kural yine yarışır.

#### Özet

- Biçem sızıntısı üç ayrı olaydır: ad çakışması, dışarıdan sızma ve dışarı sızma; bir
  yaklaşım bunların hepsini birden engellemeyebilir.
- Üretilmiş yerel adlar, yerel adı dosya yoluyla karmalayarak çakışmayı yapısal olarak
  önler ve belirlenimcidir; adın çalışma zamanında birleştirilmemesi koşuluyla çalışır.
- Bildirim karmasına göre üretilen sınıflar aynı bildirim kümesini paylaştırır; üretilen
  dosya bileşen sayısıyla değil ayrı bildirim kümesi sayısıyla büyür.
- `@scope` bir kök ve sınır alır, dışarı sızmayı keser ve yakınlık ölçütünü özgüllükten
  sonra uygular; ad çakışmasını engellemez.
- Gölge ağaç üç yönü de kapatır; kalıtılan özellikler, özel özellikler ve `::part` bilinçli
  olarak açık bırakılmış geçitlerdir.

#### Sonraki Adım

Buraya kadarki yaklaşımların hepsi aynı varsayımı paylaşıyor: biçem bileşen başına yazılır
ve her bileşenin kendi kuralları vardır. Bunun tersini savunan bir yaklaşım daha vardır —
bileşene özel kural hiç yazmamak, belgeyi önceden tanımlı ve tek bildirimlik sınıflardan
kurmak. Sonraki ders bu yaklaşımı ve dayandığı kısıt düşüncesini ele alır.

### Yardımcı Sınıf Yaklaşımı

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/yardimci-sinif-yaklasimi

Kısıt tabanlı biçem sistemleri; kapalı ölçekten değer alan tek bildirimlik sınıflar, kümenin bileşen sayısından bağımsız büyümesi, yinelemenin biçem dosyasından belgeye taşınması ve yaklaşımın kırıldığı yerler.

Buraya kadarki yaklaşımların ortak varsayımı vardı: her bileşenin kendi kuralları olur ve
biçem dosyası bileşen bileşen büyür. Bunun tersini savunan bir yaklaşım daha vardır.

Fikir şudur: bileşene özel kural hiç yazılmaz. Belge, önceden tanımlı ve tek bildirimlik
sınıflardan kurulur. Kutu tasarımı biçem dosyasında değil, belgede yapılır.

#### Kısıt Düşüncesi

**Yardımcı sınıf (utility class)**, tek bir bildirim yazan ve değerini kapalı bir ölçekten
alan sınıftır. Adı yaptığı işi bildirir: `p-1` bir iç boşluk değeri, `gap-0` bir aralık
değeri, `bg-zemin` bir zemin rengi verir.

Yaklaşımın adı kısalığı çağrıştırır ama asıl savunusu kısalık değil **kısıttır**. Ölçek
kapalıdır: iç boşluk için beş değer varsa, altıncı bir değer yazmanın yolu yoktur. Tasarım
tutarlılığı bir gözden geçirme sorusu olmaktan çıkar; sistemin kendisi rastgele değeri
imkânsız kılar.

İkinci sonuç adlandırmayla ilgilidir. Yardımcı sınıf yaklaşımında yeni ad **üretilmez**;
kümedeki adlar baştan bellidir. Önceki üç dersin sorunları — ad çakışması, sahiplik,
silinebilirlik — ortadan kalkmaz, konu dışı kalır: sınıf kimseye ait değildir.

#### Kümenin Sınırlı Büyümesi

```js
// yardimci.mjs — yardimci sinif kumesinin sinirli buyumesi ve bilesen yazimiyla karsilastirmasi
// Yardimci sinif: tek bildirim yazan, degerini kapali bir olcekten alan sinif.

// Kapali olcek: her ozellik icin secilebilecek deger sayisi sabittir.
const OLCEK = {
  "padding":       5,   // --aralik-0 .. --aralik-4
  "margin-block":  5,
  "gap":           5,
  "font-size":     6,
  "color":         8,
  "background":    8,
  "border-radius": 3,
  "display":       4,
};
const ONEKLER = ["", "hover:", "focus:", "gs:"];   // gs: genis ekran kesme noktasi

const yardimciSayisi = Object.values(OLCEK).reduce((t, n) => t + n, 0) * ONEKLER.length;
const YARDIMCI_BAYT = 26;                          // ornek: ".p-3{padding:var(--aralik-3)}"
const yardimciToplam = yardimciSayisi * YARDIMCI_BAYT;

console.log("--- yardimci sinif kumesi (bilesen sayisindan bagimsiz) ---");
console.log(`  olcek: ${Object.keys(OLCEK).length} ozellik, toplam ${Object.values(OLCEK).reduce((t, n) => t + n, 0)} deger`);
console.log(`  onek sayisi: ${ONEKLER.length} (${ONEKLER.map((o) => o || "(oneksiz)").join(", ")})`);
console.log(`  uretilen sinif sayisi: ${yardimciSayisi}`);
console.log(`  yaklasik boyut: ${yardimciToplam} bayt`);

// Bilesen yazimi: her yeni bilesen yeni kurallar ve yeni bayt ekler.
const BILESEN_KURAL = 5;                           // bilesen basina ortalama kural
const KURAL_BAYT = 62;                             // ortalama secici + bildirim govdesi

console.log("\n--- iki yazimin bilesen sayisina gore boyutu ---");
console.log("bilesen".padStart(8) + "bilesen yazimi".padStart(18) + "yardimci yazimi".padStart(18) + "  kucuk olan");
for (const n of [5, 10, 20, 40, 60, 80, 120]) {
  const bilesenToplam = n * BILESEN_KURAL * KURAL_BAYT;
  console.log(
    String(n).padStart(8) +
    `${bilesenToplam} bayt`.padStart(18) +
    `${yardimciToplam} bayt`.padStart(18) +
    "  " + (bilesenToplam < yardimciToplam ? "bilesen yazimi" : "yardimci yazimi"),
  );
}
const esik = Math.ceil(yardimciToplam / (BILESEN_KURAL * KURAL_BAYT));
console.log(`  esik: ${esik} bilesenden sonra yardimci kume daha kucuk kalir`);

// --- ayni bildirimin bilesenler arasinda kac kez yinelendigi ---
const BILESENLER = {
  "olcum-kart":     ["padding: var(--aralik-1)", "border-radius: 4px", "display: grid", "gap: var(--aralik-0)"],
  "yan-bilgi-kutu": ["padding: var(--aralik-1)", "border-radius: 4px", "display: grid"],
  "kunye":          ["padding: var(--aralik-1)", "display: flex", "gap: var(--aralik-0)"],
  "olcum-suzgeci":  ["display: flex", "gap: var(--aralik-0)", "padding: var(--aralik-0)"],
};
const sayac = new Map();
for (const bildirimler of Object.values(BILESENLER))
  for (const b of bildirimler) sayac.set(b, (sayac.get(b) ?? 0) + 1);

console.log("\n--- bildirimlerin bilesenler arasinda yinelenmesi ---");
const toplamBildirim = [...sayac.values()].reduce((t, n) => t + n, 0);
for (const [b, n] of [...sayac.entries()].sort((x, y) => y[1] - x[1]))
  console.log(`  ${b.padEnd(30)} ${n} bilesende`);
console.log(`  toplam bildirim: ${toplamBildirim}, ayri bildirim: ${sayac.size}, yineleme: ${toplamBildirim - sayac.size}`);

// --- belgedeki sinif listesi uzunlugu ---
console.log("\n--- ayni kutunun iki yazimi ---");
const kartYardimci = "grid gap-0 p-1 br-1 bg-zemin";
const kartBilesen = "olcum-kart";
console.log(`  yardimci yazimi : class="${kartYardimci}"   (${kartYardimci.length} karakter, ${kartYardimci.split(" ").length} sinif)`);
console.log(`  bilesen yazimi  : class="${kartBilesen}"   (${kartBilesen.length} karakter, 1 sinif)`);
console.log(`  kart belgede 12 kez geciyorsa fark: ${(kartYardimci.length - kartBilesen.length) * 12} karakter`);
```

```
--- yardimci sinif kumesi (bilesen sayisindan bagimsiz) ---
  olcek: 8 ozellik, toplam 44 deger
  onek sayisi: 4 ((oneksiz), hover:, focus:, gs:)
  uretilen sinif sayisi: 176
  yaklasik boyut: 4576 bayt

--- iki yazimin bilesen sayisina gore boyutu ---
 bilesen    bilesen yazimi   yardimci yazimi  kucuk olan
       5         1550 bayt         4576 bayt  bilesen yazimi
      10         3100 bayt         4576 bayt  bilesen yazimi
      20         6200 bayt         4576 bayt  yardimci yazimi
      40        12400 bayt         4576 bayt  yardimci yazimi
      60        18600 bayt         4576 bayt  yardimci yazimi
      80        24800 bayt         4576 bayt  yardimci yazimi
     120        37200 bayt         4576 bayt  yardimci yazimi
  esik: 15 bilesenden sonra yardimci kume daha kucuk kalir

--- bildirimlerin bilesenler arasinda yinelenmesi ---
  padding: var(--aralik-1)       3 bilesende
  gap: var(--aralik-0)           3 bilesende
  border-radius: 4px             2 bilesende
  display: grid                  2 bilesende
  display: flex                  2 bilesende
  padding: var(--aralik-0)       1 bilesende
  toplam bildirim: 13, ayri bildirim: 6, yineleme: 7

--- ayni kutunun iki yazimi ---
  yardimci yazimi : class="grid gap-0 p-1 br-1 bg-zemin"   (28 karakter, 5 sinif)
  bilesen yazimi  : class="olcum-kart"   (10 karakter, 1 sinif)
  kart belgede 12 kez geciyorsa fark: 216 karakter
```

İlk iki blok iki büyüme eğrisini karşılaştırıyor. Yardımcı küme **sabittir**: sekiz özellik,
kırk dört değer ve dört önek 176 sınıf üretiyor ve bu sayı kaç bileşen yazıldığından
etkilenmiyor. Bileşen yazımı ise doğrusal büyüyor.

Modelin verdiği eşik on beş bileşen. Bu sayı kesin bir değer değildir; ölçek genişliğine,
önek sayısına ve bileşen başına kural sayısına bağlıdır. Verdiği bilgi eşiğin kendisi değil,
**iki eğrinin biçimidir**: biri sabit, diğeri artan.

Üçüncü blok yinelemeyi sayıyor. Dört bileşende on üç bildirim var ama bunların yalnız altısı
ayrı; yedi bildirim yineleniyor. Yardımcı sınıflar tam olarak bu altı bildirimi tek kural
hâline getirir.

#### Yineleme Kaybolmaz, Yer Değiştirir

Dördüncü blok yaklaşımın bedelini gösteriyor. Aynı kutu bileşen yazımında on karakter,
yardımcı yazımında yirmi sekiz karakter; kart belgede on iki kez geçtiğinde fark iki yüz on
altı karaktere çıkıyor.

Biçem dosyasından silinen yineleme belgeye taşınmıştır. Toplam bayt hesabı yapılırken bu
taraf da sayılmalıdır; iki dosya birlikte gönderilir.

Ama iki yineleme aynı türden değildir. Biçem dosyasındaki yineleme **elle** yönetilir: bir
değeri değiştirmek dört kuralı bulmayı gerektirir. Belgedeki yineleme genellikle bir şablon
ya da bileşen soyutlamasının içindedir; kart bir kez yazılır, on iki kez üretilir. Yardımcı
sınıf yaklaşımı bu yüzden şablon üreten bir katmanı **varsayar**; düz yazılmış belgelerde
yineleme gerçekten elle yönetilir.

#### Durum ve Kesme Noktası Önekleri

Yardımcı sınıf tek bildirim yazdığı için sözde sınıflara ve ortam sorgularına doğrudan yer
yoktur. Çözüm, koşulu **ada** yazmaktır:

```css
.p-1        { padding: var(--aralik-1); }
.hover\:p-2:hover { padding: var(--aralik-2); }

@media (min-width: 48rem) {
  .gs\:p-2 { padding: var(--aralik-2); }
}
```

Ad içindeki iki nokta üst üste, seçicide ters eğik çizgiyle kaçırılır; belgede `hover:p-2`
yazılır, seçicide `.hover\:p-2` olarak eşleşir.

Bunun bedeli kümenin çarpan kadar büyümesidir. Çıktının ilk bloğunda dört önek, kırk dört
değeri 176 sınıfa çıkarıyor. Her yeni durum ve her yeni kesme noktası kümeyi bir kat daha
büyütür; üretilmiş kümeden yalnız belgede geçen sınıfların ayıklanması bu yüzden bu
yaklaşımın ayrılmaz parçasıdır — ve üçüncü dersteki uyarı burada da geçerlidir: adlar
kaynakta tam metin olarak geçmelidir.

#### Nerede Kırılır

Üç yerde. Birincisi, ölçek dışında bir değer gerektiğinde: bir görselin belirli bir en–boy
oranı ya da içerikten gelen bir ölçü kapalı ölçekte yoktur ve tek seferlik bir kural yazmak
gerekir.

İkincisi okunurluktur. Uzun bir sınıf listesi kutunun **ne olduğunu** değil nasıl göründüğünü
söyler. `class="olcum-kart"` yazan bir belge kendini açıklar; on sınıflık bir liste
açıklamaz.

Üçüncüsü karmaşık seçici gerektiren durumlardır. Kardeşe göre biçim, alt ögeye göre biçim ya
da bir üst ögenin durumuna bağlı biçim tek bildirimlik sınıflara sığmaz.

#### Karma Kullanım

İki yaklaşım birbirini dışlamaz ve pratikte sınır şu ölçütle çizilir: **yapı yinelenmiyorsa
yardımcı sınıf; yineleniyorsa bileşen sınıfı.**

Sayfada bir kez geçen bir bölümün iç boşluğunu ayarlamak için ad üretmek gereksizdir; on iki
kez geçen bir kartın beş bildirimini on iki yere kopyalamak da gereksizdir. Ölçek her iki
yazımda da aynı özel özelliklerden okunur, dolayısıyla iki katman aynı tasarım kısıtını
paylaşır.

#### İstasyon Sayfasında

```css
/* yardimcilar.css — kapalı ölçekten üretilen tek bildirimlik sınıflar */
.p-0 { padding: var(--aralik-0); }
.p-1 { padding: var(--aralik-1); }
.gap-0 { gap: var(--aralik-0); }
.gap-1 { gap: var(--aralik-1); }
.grid { display: grid; }
.flex { display: flex; }
.bg-zemin { background-color: var(--zemin); }
.metin-soluk { color: var(--metin-soluk); }
```

Bu küme, ölçüm istasyonu sayfasında tek seferlik ayarlar için kullanılır: künyenin alt
boşluğu, konum bölümünün iç boşluğu, kaynak notunun rengi. Ölçüm kartı ise bileşen sınıfı
olarak kalır — sayfada on iki kez geçer ve kendi varyantları vardır.

Ayrım bir kurala bağlanabilir: bir sınıf adı belgede bir kez geçiyorsa yardımcı sınıflarla
yazılabilir; iki kereden fazla geçiyorsa bileşen olmayı hak etmiştir.

#### Özet

- Yardımcı sınıf tek bildirim yazar ve değerini kapalı bir ölçekten alır; yaklaşımın asıl
  savunusu kısalık değil, ölçek dışı değeri imkânsız kılan kısıttır.
- Yardımcı kümenin boyutu bileşen sayısından bağımsızdır; bileşen yazımı doğrusal büyür ve
  iki eğri belirli bir bileşen sayısında kesişir.
- Yineleme ortadan kalkmaz, biçem dosyasından belgeye taşınır; yaklaşım şablon üreten bir
  katmanı varsayar.
- Durum ve kesme noktası önekleri kümeyi çarpan kadar büyütür; kullanılmayan sınıfların
  ayıklanması bu yüzden yaklaşımın parçasıdır.
- Yapı yinelenmiyorsa yardımcı sınıf, yineleniyorsa bileşen sınıfı; iki katman aynı özel
  özellik ölçeğini paylaşır.

#### Sonraki Adım

Bu konuda beş ayrı yazım yaklaşımı ele alındı ve hepsi aynı soruyu farklı yerden yanıtladı:
biçem nasıl düzenlenir. Bir soru daha var ve tarayıcı tarafından sorulur — biçem dosyasının
kendisi sayfanın açılışını ne kadar geciktirir, seçiciler eşleştirilirken ne kadar iş
yapılır? Sonraki ders biçemin başarım maliyetini ele alır ve kursu kapatır.

### Biçem Başarımı

Source: https://academia.sh/tr/kurslar/duzen-ve-duyarli-tasarim/bicem-basarimi

Seçici eşleştirmenin sağdan sola yapılması, geçersiz kılma kapsamının seçici biçimine bağlılığı, biçem dosyasının çizimi engellemesi ve kritik biçem alt kümesinin hesaplanması.

Önceki beş ders biçemin nasıl düzenleneceğini ele aldı ve her biri aynı soruyu farklı yerden
yanıtladı. Bir soru daha var ve onu tarayıcı sorar.

Biçem dosyası indirilir, ayrıştırılır, her ögeye hangi kuralların uyduğu bulunur ve
hesaplanmış değerler üretilir. Bu işin bir maliyeti vardır ve maliyetin nereden geldiği,
yazım kararlarını da etkiler. Bu ders o maliyeti kural olarak kurar ve kursu kapatır.

#### Eşleştirme Sağdan Sola Yapılır

Bir seçicinin bir ögeye uyup uymadığı sorusu iki yönde yanıtlanabilir. Soldan sağa gitmek —
önce en dıştaki seçiciyi bulup alt ağaçta ilerlemek — okuma yönüne uyar. Tarayıcılar bunu
yapmaz; **sağdan sola** gider.

Nedeni şudur: soru "bu seçici hangi ögelere uyar" değil, "bu ögeye hangi kurallar uyar"dır.
Öge elde olduğunda, seçicinin en sağdaki bileşeni — **anahtar seçici** — ögeye uymuyorsa
kural hemen elenir. Uyuyorsa atalara doğru yürünür.

```js
// bicembasarim.mjs — secici eslestirme yonu ve kritik bicem alt kumesinin hesaplanmasi

// --- kucuk bir belge agaci ---
const yap = (tur, siniflar, cocuk = []) => ({ tur, siniflar, cocuk, ust: null });
const BELGE = yap("body", [], [
  yap("header", ["kunye"], [
    yap("h1", ["kunye__ad"]), yap("p", ["kunye__konum"]),
    yap("nav", ["gezinme"], [yap("ul", [], [yap("li", [], [yap("a", ["etkin"])]), yap("li", [], [yap("a", [])])])]),
  ]),
  yap("main", [], [
    yap("section", ["olcum-bolumu"], [
      yap("h2", ["baslik"]),
      yap("div", ["olcum-kartlari"], [
        yap("article", ["olcum-kart"], [yap("h3", ["olcum-kart__baslik"]), yap("p", ["olcum-kart__deger"])]),
        yap("article", ["olcum-kart"], [yap("h3", ["olcum-kart__baslik"]), yap("p", ["olcum-kart__deger"])]),
        yap("article", ["olcum-kart", "olcum-kart--genis"], [yap("h3", ["olcum-kart__baslik"]), yap("p", ["olcum-kart__deger"])]),
      ]),
    ]),
    yap("section", ["konum-bolumu"], [yap("h2", ["baslik"]), yap("p", []), yap("p", [])]),
  ]),
  yap("footer", ["kaynak-notu"], [yap("p", [])]),
]);

function bagla(dugum, ust = null) {
  dugum.ust = ust;
  for (const c of dugum.cocuk) bagla(c, dugum);
  return dugum;
}
bagla(BELGE);

const tumu = (d, liste = []) => { liste.push(d); for (const c of d.cocuk) tumu(c, liste); return liste; };
const DUGUMLER = tumu(BELGE);

// --- bilesik secici denetimi (tur ve sinif) ---
let denetim = 0;
function uyar(dugum, bilesik) {
  denetim++;
  const tur = bilesik.match(/^[a-z][\w-]*/);
  if (tur && dugum.tur !== tur[0]) return false;
  for (const s of bilesik.match(/\.[\w-]+/g) || []) if (!dugum.siniflar.includes(s.slice(1))) return false;
  return true;
}

// sagdan sola: once anahtar secici, sonra atalar
function sagdanSola(secici) {
  const p = secici.trim().split(/\s+/);
  denetim = 0;
  let eslesen = 0;
  for (const d of DUGUMLER) {
    if (!uyar(d, p[p.length - 1])) continue;
    let i = p.length - 2, ata = d.ust, tamam = i < 0;
    while (i >= 0 && ata) {
      if (uyar(ata, p[i])) i--;
      ata = ata.ust;
      if (i < 0) tamam = true;
    }
    if (tamam) eslesen++;
  }
  return { denetim, eslesen };
}

// soldan saga: once ilk secici, sonra alt agaclarda ilerle
function soldanSaga(secici) {
  const p = secici.trim().split(/\s+/);
  denetim = 0;
  let eslesen = 0;
  const ilerle = (dugum, i) => {
    for (const c of tumu(dugum).slice(1)) {
      if (!uyar(c, p[i])) continue;
      if (i === p.length - 1) eslesen++;
      else ilerle(c, i + 1);
    }
  };
  for (const d of DUGUMLER) {
    if (!uyar(d, p[0])) continue;
    if (p.length === 1) eslesen++;
    else ilerle(d, 1);
  }
  return { denetim, eslesen };
}

console.log(`belgedeki dugum sayisi: ${DUGUMLER.length}`);
console.log("\n--- ayni seciciyi iki yonde eslestirmenin denetim sayisi ---");
console.log("secici".padEnd(62) + "sagdan sola".padStart(13) + "soldan saga".padStart(13) + "  eslesen");
for (const s of [
  ".olcum-kart__baslik",
  ".olcum-bolumu .olcum-kartlari .olcum-kart .olcum-kart__baslik",
  "body main section div article h3",
  ".kunye__ad",
]) {
  const sa = sagdanSola(s), so = soldanSaga(s);
  console.log(s.padEnd(62) + String(sa.denetim).padStart(13) + String(so.denetim).padStart(13) + String(sa.eslesen).padStart(10));
}

// --- kritik bicem: ilk ekranda gorunen ogelere uyan kurallar ---
const KURALLAR = [
  { secici: ".kunye", bayt: 96 }, { secici: ".kunye__ad", bayt: 64 },
  { secici: ".gezinme", bayt: 88 }, { secici: ".gezinme a", bayt: 72 },
  { secici: ".olcum-kartlari", bayt: 120 }, { secici: ".olcum-kart", bayt: 148 },
  { secici: ".olcum-kart__baslik", bayt: 58 }, { secici: ".olcum-kart__deger", bayt: 86 },
  { secici: ".olcum-kart--genis", bayt: 44 }, { secici: ".konum-bolumu", bayt: 92 },
  { secici: ".konum-bolumu p", bayt: 60 }, { secici: ".kaynak-notu", bayt: 54 },
  { secici: ".olcum-suzgeci", bayt: 132 }, { secici: ".yan-bilgi", bayt: 104 },
];

// Ilk ekranda gorunen bolum: kunye ve olcum bolumu (belge sirasinda ilk 20 dugum).
const GORUNUR = new Set(DUGUMLER.slice(0, 20));
const uyanVar = (secici) => {
  const p = secici.trim().split(/\s+/);
  return [...GORUNUR].some((d) => {
    if (!uyar(d, p[p.length - 1])) return false;
    let i = p.length - 2, ata = d.ust;
    while (i >= 0 && ata) { if (uyar(ata, p[i])) i--; ata = ata.ust; }
    return i < 0;
  });
};

const kritik = KURALLAR.filter((k) => uyanVar(k.secici));
const toplam = KURALLAR.reduce((t, k) => t + k.bayt, 0);
const kritikBayt = kritik.reduce((t, k) => t + k.bayt, 0);

console.log("\n--- kritik bicem alt kumesi ---");
console.log(`  ilk ekranda gorunen dugum sayisi: ${GORUNUR.size} / ${DUGUMLER.length}`);
console.log(`  kritik kural: ${kritik.length} / ${KURALLAR.length}`);
console.log(`  kritik bayt : ${kritikBayt} / ${toplam}  (%${((kritikBayt / toplam) * 100).toFixed(1)})`);
console.log("  kritik olmayanlar:", KURALLAR.filter((k) => !kritik.includes(k)).map((k) => k.secici).join(", "));
```

```
belgedeki dugum sayisi: 29

--- ayni seciciyi iki yonde eslestirmenin denetim sayisi ---
secici                                                          sagdan sola  soldan saga  eslesen
.olcum-kart__baslik                                                      29           29         3
.olcum-bolumu .olcum-kartlari .olcum-kart .olcum-kart__baslik            38           55         3
body main section div article h3                                         44          102         3
.kunye__ad                                                               29           29         1

--- kritik bicem alt kumesi ---
  ilk ekranda gorunen dugum sayisi: 20 / 29
  kritik kural: 8 / 14
  kritik bayt : 732 / 1218  (%60.1)
  kritik olmayanlar: .olcum-kart--genis, .konum-bolumu, .konum-bolumu p, .kaynak-notu, .olcum-suzgeci, .yan-bilgi
```

Yirmi dokuz düğümlük bir belgede dört seçici deneniyor. Tek sınıflı seçicilerde iki yön aynı
sayıda denetim yapıyor: her düğüm bir kez sınanıyor.

Ayrım uzun seçicilerde çıkıyor. Dört bileşenli seçici sağdan sola 38, soldan sağa 55 denetim
istiyor; altı bileşenli tür seçicisi zinciri 44'e karşı 102. Sağdan sola yürüyüş, anahtar
seçici uymayan düğümleri **tek denetimde** eliyor; soldan sağa yürüyüş her uyan başlangıç
için alt ağacın tamamını geziyor.

Buradan çıkan yazım kuralı, anahtar seçicinin **ayırıcı** olmasıdır. `... h3` biçiminde biten
bir seçici, belgedeki bütün başlıklarda ata yürüyüşü başlatır; `... .olcum-kart__baslik`
biçiminde biten seçici yalnız üç düğümde başlatır.

Bir ölçü duygusu gerekir: seçici eşleştirme, yerleşim ve boyama aşamalarının yanında küçük bir
maliyettir. Uzun seçicilerin asıl bedeli eşleştirme sayısı değil, bir sonraki bölümdeki
geçersiz kılma kapsamıdır.

#### Geçersiz Kılma Kapsamı

Bir ögenin sınıfı değiştiğinde, tarayıcı hangi ögelerin biçeminin yeniden hesaplanacağını
belirlemek zorundadır. Bu kümeye **geçersiz kılma kapsamı (invalidation scope)** denir ve
seçicilerin biçiminden çıkarılır.

Kural şudur: bir sınıf yalnız kendi ögesine uyan kurallarda geçiyorsa kapsam o ögedir. Aynı
sınıf bir alt öge birleştiricisinin **solunda** geçiyorsa, kapsam ögenin bütün alt ağacıdır.

```css
.olcum-kart--uyari { border-color: var(--uyari); }          /* kapsam: ögenin kendisi */
.olcum-kart--uyari .olcum-kart__deger { color: var(--uyari); }  /* kapsam: alt ağacın tamamı */
```

İkinci yazımda karta sınıf eklemek, kartın içindeki bütün ögelerin biçemini yeniden hesaplatır.
Özel Özellikler dersinde kurulan özel özellik yazımı bu maliyeti de düşürür: değiştirici yalnız
bir değer yazar, alt ögeler zaten o değeri okuyordur.

Ata yönünde çalışan seçiciler kapsamı yukarı doğru genişletir: bir ögenin durumunun ana
ögesini de etkilediği yazımlarda, değişimin sonucu ağacın üst kısmına taşınır. Bu tür
seçiciler güçlüdür ve gerektiğinde kullanılır; ölçüsüz kullanıldığında geçersiz kılma
kapsamını öngörülemez kılar.

#### Biçem Dosyası Çizimi Engeller

Bir belgenin ilk çizimi, biçem dosyası indirilip ayrıştırılana kadar bekler. Bunun nedeni
belirlidir: biçem bilinmeden çizilen sayfa, biçem geldiğinde baştan çizilir ve kullanıcı
biçimsiz bir sayfa görür.

Üç sonucu vardır. Biçem dosyası belgenin baş kısmında bildirilir; sonda bildirilmesi
ayrıştırmanın ilerlemesini geciktirir. `media` özniteliği taşıyan ve koşulu sağlanmayan bir
dosya çizimi engellemez; yazdırma biçemi bu yolla ayrılır. Biçem dosyasının içinden başka bir
dosya çağıran `@import` kuralı ise iki indirmeyi **sıraya** sokar; ikinci istek ancak birinci
dosya ayrıştırıldıktan sonra başlar.

#### Kritik Biçem

İlk çizim için gereken kurallar, dosyadaki kuralların tamamı değildir. Yalnız ilk ekranda
görünen ögelere uyan kurallar gerekir; kalanlar sonra yüklenebilir.

Çıktının ikinci bloğu bu alt kümeyi hesaplıyor: yirmi dokuz düğümün ilk yirmisi görünür
kabul edildiğinde, on dört kuralın sekizi gerekli çıkıyor. Bayt olarak toplamın yüzde 60'ı.

Kritik alt küme belgenin içine doğrudan yazılır; kalan kurallar çizimi engellemeyen bir
biçimde yüklenir. Bu yazımın iki bedeli vardır. Birincisi yinelemedir: kritik kurallar hem
belgenin içinde hem dosyada bulunur. İkincisi bayatlamadır; sayfa yapısı değiştiğinde kritik
küme yeniden hesaplanmalıdır, elle bakımı sürdürülemez.

Bu yüzden kritik biçem, elle yazılan değil **üretilen** bir çıktıdır — hesabı yukarıdaki
kadar mekaniktir: görünür öge kümesi ve kural listesi verildiğinde alt küme belirlidir.

#### Katmanlar ve Bakım Maliyeti

`@layer` kuralının çalışma zamanı maliyeti küçüktür ama bir sıra kuralı vardır: katman
sırası, katman adlarının **ilk göründüğü** yerde belirlenir. Bu yüzden katman adları biçem
dosyasının başında tek satırda bildirilir:

```css
@layer taban, duzen, bilesen, yardimci;
```

Bu satır sıraları sabitler; hangi dosyanın hangi sırada yüklendiği artık sonucu değiştirmez.
Kazandırdığı şey bir başarım ölçüsü değil, ölçülemeyen bir maliyetin — özgüllük tırmanışının
ve `!important` zincirlerinin — önüne geçmesidir.

Yardımcı sınıflar en üst katmanda durur; böylece tek bildirimlik bir sınıf, bileşen kuralını
özgüllük hilesi olmadan ezebilir. Bu, beşinci dersteki karma kullanımın gerçekten çalışması
için gereken düzenlemedir.

#### Özet

- Seçici eşleştirme sağdan sola yapılır; anahtar seçici uymayan düğümler tek denetimde
  elenir, bu yüzden ayırıcı bir anahtar seçici uzun seçicileri de ucuzlatır.
- Uzun seçicilerin asıl bedeli eşleştirme sayısı değil geçersiz kılma kapsamıdır: bir sınıf
  alt öge birleştiricisinin solunda geçiyorsa kapsam bütün alt ağaçtır.
- Biçem dosyası ilk çizimi engeller; koşulu sağlanmayan `media` bildirimi engellemez,
  `@import` ise indirmeleri sıraya sokar.
- Kritik biçem, ilk ekranda görünen ögelere uyan kuralların alt kümesidir; hesabı mekaniktir
  ve elle bakımı sürdürülemediği için üretilmelidir.
- Katman sırası adların ilk göründüğü yerde belirlenir; başta tek satırla bildirmek yükleme
  sırasını sonuçtan bağımsız kılar.

#### Kurs Kapanışı

Bu kurs düzenle başladı. Esnek kutu tek eksende boş alanı bölüştürdü, ızgara iki eksende hat
tanımladı ve ikisi arasındaki seçim bir ölçüte bağlandı. Duyarlı tasarım, kesme noktalarını
aygıt listelerinden değil içeriğin kendisinden türetti; ortam sorguları görüntü alanını,
kapsayıcı sorguları bileşenin kendi bağlamını sordu ve kullanıcı tercihleri tasarımın girdisi
oldu.

Hareket konusu geometriyi ve zamanı ayırdı: dönüşüm matrisi neyin nereye taşındığını,
zamanlama eğrisi bunun ne kadar sürede olduğunu belirledi. Çizim hattı hangi bildirimin
hangi aşamayı çalıştırdığını gösterdi ve hareketin bir de kullanıcıya çıkan maliyeti olduğu
görüldü.

Son konu ise bütün bunların nereye yazılacağını sordu. Adlandırma yöntemi özgüllüğü düz
tuttu, özel özellikler bileşene bir arayüz verdi, derleme araçları kaynağı düzenledi,
kapsamlama adları sözleşmeden mekanizmaya taşıdı ve yardımcı sınıflar kısıtı sistemin
kendisine gömdü. Kuzey Yamaç Ölçüm İstasyonu sayfası bu derslerin her birinde bir adım daha
ilerledi ve sonunda bileşen dosyalarına, bir ölçek tanımına ve katman sırasına bölünmüş bir
biçem katmanına kavuştu.

Sayfanın hâlâ yapamadığı bir şey var: kendisi hiçbir şey yapmıyor. Ölçüm kartları belgede
yazılı olduğu için görünüyor, durum göstergesi bir öznitelik elle değiştirildiğinde
değişiyor, süzgeç hiçbir şeyi süzmüyor. Belge ağacının programla değiştirilmesi, kullanıcı
eylemlerinin olay olarak yakalanması ve tarayıcının sunduğu arayüzlerin kullanılması bir
sonraki kursun konusudur: **Tarayıcı ve Web Platformu**. Orada belge, bir biçim verilmiş
metin olmaktan çıkıp üzerinde çalışılan bir veri yapısına dönüşür.

