/* ── GEÇİŞ KURALI: id DOKUNULMAZ ──────────────────────────────────
   Bir butonu/alanı bu sınıflara taşırken class değişir, id DEĞİŞMEZ.
   Portalde id'ler yük taşıyor: getElementById('nextBtn') (create.html),
   'savePermissionBtn' (admin-mode.js), 'drawerBtn' (field-history-demo)
   — ayrıca otomasyon testleri ve sayfa-içi betikler. id değiştirmek
   sessiz kırılma üretir: buton görünür, tıklanır, hiçbir şey olmaz.
   Doğru hamle: <button id="nextBtn" class="os-btn os-btn--primary">
   ─────────────────────────────────────────────────────────────────── */
/* OtoSOR controls: buttons, inputs, selects, checks, switches, form fields.
   Vanilla-consumable classes; React components wrap these same classes. */

/* ── Button ─────────────────────────────────────────────── */
.os-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  height: var(--control-h-md); padding: 0 var(--space-4);
  /* Taban zemin ZORUNLU: tanımsız bırakılırsa tarayıcının buttonface
     varsayılanı (#f0f0f0) görünüyor ve beyaz metinli varyantlarda
     kontrast 1.14:1'e düşüyor. Varyant sınıfları bunun üzerine yazar;
     --ghost açıkça transparent'a döner. */
  background: var(--surface-card); color: var(--text-primary);
  font: var(--weight-semibold) var(--text-md)/1 var(--font-sans);
  border-radius: var(--radius-md); border: 1px solid transparent;
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
/* BASILI durum — HER varyantta. Önce yalnız --primary'de vardı: kullanıcı
   Kaydet dışındaki hiçbir butona bastığında geri bildirim almıyordu.
   Ölçü tek: zemin bir tık koyulaşır + öğe 1px aşağı iner. */
.os-btn:active:not(:disabled) { transform: translateY(1px); }
.os-btn--success:active:not(:disabled)      { background: var(--green-700); }
.os-btn--secondary:active:not(:disabled)    { background: var(--gray-100); border-color: var(--border-strong); }
.os-btn--ghost:active:not(:disabled)        { background: var(--gray-100); }
.os-btn--danger:active:not(:disabled)       { background: var(--red-100); }
.os-btn--returned:active:not(:disabled)     { background: var(--orange-100); }
.os-btn--danger-solid:active:not(:disabled) { background: var(--red-700); }
.os-iconbtn:active:not(:disabled)           { transform: translateY(1px); background: var(--gray-100); }
@media (prefers-reduced-motion: reduce) {
  .os-btn:active:not(:disabled), .os-iconbtn:active:not(:disabled) { transform: none; }
}
/* aria-disabled: <a> etiketi disabled niteliğini taşıyamaz (os-choicecard/os-menu__item ile aynı kalıp). */
.os-btn:disabled, .os-btn[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
.os-btn--primary { background: var(--interactive-primary); color: var(--text-inverse); }
.os-btn--primary:hover:not(:disabled), .os-btn--primary.is-hover { background: var(--interactive-primary-hover); }
.os-btn--primary:active:not(:disabled) { background: var(--interactive-primary-active); }
/* ── İKON KURALI (bağlayıcı) ──────────────────────────────────────
   Butona ikon YALNIZ şu dört gerekçeden biri varsa girer:
     a) ikon-only buton (metne yer yok) → tooltip + aria-label ZORUNLU
     b) eylem bir KANAL/ARAÇ taşıyor  → Excel · SMS · Yazdır · İndir · Mail
     c) eylem YÖN taşıyor             → İleri · Önceki · İade
     d) sonuç YIKICI/OLUMSUZ          → Sil · Reddet · İptal Et
        (ikon ikinci uyarı kanalı; renk tek başına anlam taşımaz — R-25)
   İkon GİRMEZ: Kaydet · Onayla · Vazgeç · Ekle · Uygula · Kapat · Sorgula ·
   Yeni … · Önizle · Düzenle · Yeniden dene — metin eylemi zaten söylüyor.
   Diyalog ayağı asla ikon almaz.
   GRUP KURALI: aynı sonuç kümesini paylaşan grup (karar grubu, sihirbaz
   navigasyonu, diyalog ayağı) ya tamamen ikonlu ya tamamen ikonsuzdur —
   grup içi tutarlılık tek tek kurala baskındır. Araç çubuğu grup değildir.
   Konum: ikon metnin SOLUNDA; tek istisna ileri yönlü ok (İleri →).
   Yükleniyor: ikonun YERİNİ spinner alır, yanına eklenmez — buton genişliği
   değişmez, tıklama noktası kaymaz.
   Gerekçe: portalde "Kaydet" aynı dosyada ikonsuz, bi-check-lg ve bi-check2
   ile yazılmıştı; attach-vehicle.html:1126'da ikonu bi-cpu (işlemci) idi. */

/* Ara durum eylemi ("İade", "Geri gönder"): olumsuz DEĞİL, geri dönülebilir
   bir adım. Ne yıkıcı (kırmızı) ne nötr olmalı — turuncu outline.
   Dolgu almaz: kullanıcıyı iadeye çekmek istemeyiz, yalnız görünür olsun.

   ÖNEMLİ — bu buton her zaman "Reddet"in yanında durur ve turuncu ile
   kırmızının parlaklık oranı 1,02 (renk körü benzetiminde 1,09): renk
   içinde kullanılır; ayırıcı çizgi geri-dönülebilir eylemleri nihai
   olanlardan ayırır. Butonu ayırıcısız kullanmak kuralın yarısıdır. */
/* ── Karar butonu grubu ──────────────────────────────────────────────
   Geri dönülebilir eylemleri (İade) nihai olanlardan (Reddet) AYIRIR.
   Gerekçe ölçümden: turuncu #9A3412 (hue 15°) ile kırmızı #B20911
   (hue 357°) arasında 18° hue farkı VAR — normal görmede ayrışıyorlar.
   Ama protanopi benzetiminde ikisi neredeyse aynı renge çöküyor:
   parlaklık oranı 1,09. Yani renk TEK BAŞINA yetmiyor. Ayrım konuma
   yüklenir: sınır grubu böler, ikon üçüncü kanalı ekler. */
.os-btngroup--decision { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
/* Alt grup — ayrım kendi sol kenarında taşınır.
   Ayrı bir <span> ayırıcı flex öğesi olduğu için grup sarınca satır
   sonunda boşta kalıyor ve yeni satır ayırıcısız başlıyordu. Sınır
   alt grubun border-left'i olunca onunla birlikte iner: sarma hangi
   noktada olursa olsun ayrım korunur. */
.os-btngroup__set { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.os-btngroup__set + .os-btngroup__set {
  padding-left: var(--space-3);
  border-left: 1px solid var(--border-strong);
}
/* Dikey yığın — üç ve daha fazla alt grup için. Yatay dizilimde sarma
   kaçınılmaz ve sınır öksüz kalıyor; dikeyde sınır border-top olarak
   grubun üstünde durur, hiçbir genişlikte boşa düşmez. Portalin kendi
   .decision-buttons kalıbı da dikey. */
.os-btngroup--stack { flex-direction: column; align-items: stretch; }
.os-btngroup--stack .os-btngroup__set { width: 100%; }
.os-btngroup--stack .os-btngroup__set + .os-btngroup__set {
  padding-left: 0; border-left: 0;
  padding-top: var(--space-3); border-top: 1px solid var(--border-strong);
}
/* Yön KAP genişliğine bağlı, viewport'a değil.
   Sarma kapta olur: 395px'lik bir kartta 909px viewport'ta bile ikinci
   alt grup yeni satıra iner ve sol kenardaki ayırıcı hiçbir şeyi
   bölmeden satır başında yetim kalır. Medya sorgusu bunu göremez —
   container sorgusu görür. Eşik 520px: iki alt grubun yan yana sığması
   için gereken en dar genişlik (ölçüldü). */
.os-btngroup--decision { container-type: inline-size; }
@container (max-width: 520px) {
  .os-btngroup--decision { flex-direction: column; align-items: stretch; }
  .os-btngroup__set { flex-direction: column; align-items: stretch; width: 100%; }
  .os-btngroup__set + .os-btngroup__set {
    padding-left: 0; border-left: 0;
    padding-top: var(--space-3); border-top: 1px solid var(--border-strong);
  }
}
/* Geriye dönük: tek satıra sığacağı bilinen gruplarda hâlâ kullanılabilir */
/* Esneyen boşluk — olumsuz ve olumlu kümeleri şeridin iki ucuna iter.
   Ayırıcıdan farkı: çizgi çizmez, yalnız yer açar. Dar ekranda
   butonlar alt alta indiğinde etkisiz kalır (flex-direction: column). */
.os-btngroup__spacer { flex: 1 1 auto; min-width: var(--space-2); }
.os-btngroup__sep {
  width: 1px; align-self: stretch; min-height: 28px; flex: none;
  background: var(--border-strong); margin: 0 var(--space-1);
}
@media (max-width: 639px) {
  /* Esneyen boşluk — olumsuz ve olumlu kümeleri şeridin iki ucuna iter.
   Ayırıcıdan farkı: çizgi çizmez, yalnız yer açar. Dar ekranda
   butonlar alt alta indiğinde etkisiz kalır (flex-direction: column). */
.os-btngroup__spacer { flex: 1 1 auto; min-width: var(--space-2); }
.os-btngroup__sep { width: auto; min-height: 0; height: 1px; margin: var(--space-1) 0; }
}

/* Menü içinde yıkıcı eylem — ghost'un kırmızı okuması. Kebap/açılır
   menülerde "Sil" tekrar eden bir kalıp; inline renkle çizilmesi
   kopyalandıkça sapıyordu. */
.os-btn--ghost.os-btn--danger-text { color: var(--status-danger-fg); }
.os-btn--ghost.os-btn--danger-text:hover:not(:disabled) { background: var(--status-danger-bg); }

.os-btn--returned {
  background: var(--surface-card); color: var(--status-returned-fg);
  border-color: var(--orange-100);
}
.os-btn--returned:hover:not(:disabled) { background: var(--status-returned-bg); border-color: var(--status-returned-solid); }

/* Karar grubu: olumsuz sonuçlar solda, olumlu sağda; ayırıcı ikisini böler.
   Geri dönülebilir (İade) ile nihai (Reddet) arasına de ayırıcı girer. */
@media (max-width: 639px) {
  /* Mobilde ayırıcı çizgi yatay olur, butonlar tam genişlik ve alt alta;
     sıra korunur ama olumlu eylem EN ALTTA (başparmak bölgesi). */
}

/* KULLANIMDAN KALDIRILDI — --interactive-accent, --status-danger-solid ile
   aynı renk; accent buton yıkıcı butondan ayırt edilemiyordu. Olumlu/
   tamamlayıcı eylemler (Excel dışa aktarma vb.) için .os-btn--success. */
/* R-30 — "Onayla" iki ayrı iş: alternatifi REDDET ise iş kararı → --success;
   alternatifi VAZGEÇ/İptal ise diyalog onayı → --primary (yeşil DEĞİL: yeşili
   "ilerlet" anlamına açar). v1.0'ın koşulsuz "Onayla → yeşil" satırı 28 butonun
   16'sını yanlış boyatıyordu. */
.os-btn--success { background: var(--status-success-solid); color: var(--text-inverse); }
.os-btn--success:hover:not(:disabled) { background: var(--green-600); }
.os-btn--secondary {
  background: var(--surface-card); color: var(--text-brand);
  border-color: var(--border-strong);
}
.os-btn--secondary:hover:not(:disabled) { background: var(--surface-selected); border-color: var(--border-focus); }
.os-btn--ghost { background: transparent; color: var(--text-brand); }
.os-btn--ghost:hover:not(:disabled) { background: var(--surface-selected); }
/* Yıkıcı eylem OUTLINE gelir (R-09): dolgu görsel ağırlıktır, ağırlık
   kullanıcıyı butona çeker — yıkıcı eylemi çekici yapmayız. */
.os-btn--danger {
  background: var(--surface-card); color: var(--status-danger-fg);
  border-color: var(--red-100);
}
.os-btn--danger:hover:not(:disabled) { background: var(--status-danger-bg); border-color: var(--status-danger-solid); }
/* Dolgu kırmızı YALNIZ onay diyaloğunun içinde — kullanıcı niyetini zaten
   beyan etmiştir. Liste veya form içinde kullanılmaz. */
.os-btn--danger-solid { background: var(--status-danger-solid); color: var(--text-inverse); }
.os-btn--danger-solid:hover:not(:disabled) { background: var(--red-600); }
.os-btn--sm { height: var(--control-h-sm); padding: 0 var(--space-3); font-size: var(--text-sm); }
.os-btn--lg { height: var(--control-h-lg); padding: 0 var(--space-6); font-size: var(--text-lg); }
.os-btn--block { width: 100%; }
/* İkon rengi METİNDEN gelir (currentColor), sabit gri değil: gri ikon
   renkli butonda yabancı duruyor ve ikonu metinden ayırıyor. */
.os-btn__icon { font-size: 15px; line-height: 1; width: 1em; height: 1em; flex: none; display: inline-flex; align-items: center; justify-content: center; color: currentColor; }
.os-iconbtn i, .os-iconbtn svg { color: currentColor; line-height: 1; }
.os-iconbtn i[class*="bi-"] { font-size: 16px; }
.os-iconbtn--sm i[class*="bi-"] { font-size: 14px; }

/* Icon-only button */
.os-iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h-md); height: var(--control-h-md);
  border-radius: var(--radius-md); border: 1px solid transparent;
  background: transparent; color: var(--text-secondary); cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard);
}
.os-iconbtn:hover { background: var(--surface-sunken); color: var(--text-primary); }
.os-iconbtn--outline { border-color: var(--border-default); background: var(--surface-card); }
.os-iconbtn--sm { width: var(--control-h-sm); height: var(--control-h-sm); }
/* Varyantlar (2026-09-25, tek kaynak gocu): uc ihtiyac sayfalarda AYRI AYRI yaziliyordu --
   sapid `.btn-ico.tehlike`, hub `.hero-fh-btn.active`, lightbox `.close-btn { color:white }`.
   R-42: sayfaya degil agaca varyant. */
/* Yikici eylem (sil, cikar): kirmizi ikon; zemin yalniz uzerine gelince (R-09: yikici eylem cekici degil) */
.os-iconbtn--danger { color: var(--status-danger-fg); }
.os-iconbtn--danger:hover { background: var(--status-danger-bg); color: var(--status-danger-fg); }
.os-iconbtn--success { color: var(--status-success-fg); }
.os-iconbtn--success:hover { background: var(--status-success-bg); color: var(--status-success-fg); }
/* Ac/kapa dugmesi: basili durum `aria-pressed="true"` (ARIA semantigi, sinif degil) */
.os-iconbtn[aria-pressed="true"] { background: var(--surface-selected); border-color: var(--border-focus); color: var(--text-brand); }
/* `hidden` niteligi inline-flex goruntusune yenilmesin (alan gecmisi "Tum degisiklikler" tusu yuklenene kadar gizli). */
.os-iconbtn[hidden] { display: none; }
/* Koyu zemin (lightbox, onizleme ortusu) */
.os-iconbtn--on-dark { color: var(--text-inverse); }
.os-iconbtn--on-dark:hover { background: var(--on-dark-fill-hover); color: var(--text-inverse); }

/* ── Input / Select / Textarea ──────────────────────────── */
.os-input, .os-select, .os-textarea {
  width: 100%; height: var(--control-h-md); padding: 0 var(--space-3);
  font: var(--weight-regular) var(--text-md)/1.4 var(--font-sans);
  color: var(--text-primary); background: var(--surface-card);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.os-textarea { height: auto; min-height: 80px; padding: var(--space-2) var(--space-3); resize: vertical; }
.os-input::placeholder, .os-textarea::placeholder { color: var(--text-muted); }
.os-input:hover, .os-select:hover, .os-textarea:hover { border-color: var(--gray-400); }
.os-input:focus, .os-select:focus, .os-textarea:focus {
  outline: none; border-color: var(--border-focus); box-shadow: var(--focus-ring);
}
.os-input:disabled, .os-select:disabled, .os-textarea:disabled {
  background: var(--surface-sunken); color: var(--text-muted); cursor: not-allowed;
}
/* Hata durumu iki yoldan gelir: DS varyanti ya da R-06 semantigi `aria-invalid="true"`
   (portalin dogrulama modulleri -- field-error/form-validator/input-sanitizer -- bunu yazar). */
.os-input--invalid, .os-select--invalid, .os-textarea--invalid,
.os-input[aria-invalid="true"], .os-select[aria-invalid="true"], .os-textarea[aria-invalid="true"] { border-color: var(--status-danger-solid); }
.os-input--invalid:focus, .os-input[aria-invalid="true"]:focus, .os-select[aria-invalid="true"]:focus,
.os-textarea[aria-invalid="true"]:focus { box-shadow: var(--focus-ring-danger); border-color: var(--status-danger-solid); }
/* Salt-okunur: duzenlenemez oldugu okunmali (portal kurali, eskiden os-bridge.css'te !important idi) */
.os-input[readonly], .os-textarea[readonly] { background: var(--surface-sunken); border-style: dashed; }
/* İki ayrı rakam sınıfı — hizalama ANLAMDAN gelir, biçimden değil:
   --num  MİKTAR (tutar, oran, adet): sağa yaslı. Basamaklar alt alta
          gelsin ki kullanıcı büyüklükleri göz kararı karşılaştırabilsin.
   --code KİMLİK (TCKN, VKN, plaka, şasi, IBAN, senet no): SOLA yaslı.
          Kimlik bir sayı değil bir DİZGİDİR — karşılaştırılmaz, baştan
          okunur ve soldan yazılır. Sağa yaslamak imleci sağdan sola
          kaydırır, yarım yazılmış TCKN alan içinde zıplar. */
.os-input--num  { font-feature-settings: var(--numeric-features); text-align: right; }
.os-input--code { font-feature-settings: var(--numeric-features); text-align: left; }

/* -- PANOYA KOPYALAMA (baglayici) ---------------------------------
   Bicim GORUNTUDE kalir; panoya ve veritabanina HAM deger gider.
   Kullanici bir numarayi kopyaladiginda onu baska bir yere yapistirmak
   icin kopyaliyor: telefonu cevirmek, plakayi sorgu ekranina yazmak,
   IBAN-i bankaya girmek, TCKN-yi baska bir forma tasimak. Bicim
   karakterleri (parantez, bosluk, nokta) oralarda ise yaramaz, elle
   ayiklanmasi gerekir ve cogu sistem onlari reddeder.
     plaka     gorunen "34 VST 482"      kopyalanan "34VST482"
     telefon   gorunen "(537) 952 40 37" kopyalanan "5379524037"
     IBAN      gorunen "TR33 0006 ..."   kopyalanan "TR330006..."
     TCKN/VKN  gorunen "12345678901"     kopyalanan ayni (bicim yok)
     tutar     gorunen "765.000,00"      kopyalanan "765000.00"
     tarih     gorunen "15.07.2026"      kopyalanan "2026-07-15"
     kilometre gorunen "171.250 km"      kopyalanan "171250"
     birimli   gorunen "45 %" / "48 ay"  kopyalanan "45" / "48"
   BIRIM DE GITMEZ: km, %, ay, gun birer etiket; kopyalanan deger
   sayinin kendisi. Kullanici "171250" yapistirmak istiyor, "171.250 km"
   degil -- birim yapistirildigi yerde sayiyi metne cevirir.
   Tutar ve tarihte ham bicim MAKINE bicimidir (nokta ondalik, ISO
   tarih): kopyalanan deger Excel-e ve API-ye yapistiriliyor.
   Uygulama: alan copy olayini yakalar ve panoya ham degeri yazar
   (OSFormat.raw). Secim kullanicinin yaptigi secim olarak kalir --
   yalniz pano icerigi degisir. Salt-okunur GOSTERIM ogeleri de (tablo
   hucresi, kv degeri) ayni davranisi tasir: kopyalanan sey ekrandaki
   susli metin degil, kayittaki degerdir. */
/* ── UZUNLUK SINIRI (bağlayıcı) ────────────────────────────────────
   Her sınırlı alan maxlength TAŞIR. Maske biçimlendirmeyi çözer ama
   uzunluğu çözmez: kullanıcı 11 haneli TCKN alanına 40 karakter
   yazabiliyor, maske sessizce kırpmıyor ve hata ancak gönderimde
   çıkıyor. Sınır girişte durdurulur.
   DİKKAT: maxlength BİÇİMLENMİŞ dizgiyi sayar, ham veriyi değil —
   telefon 10 hane ama "(537) 952 40 37" 15 karakter.
     TCKN                11      VKN                 10
     IdentityInput auto  11      Telefon (formatlı)  15
     Plaka (formatlı)    10      Tarih gg.aa.yyyy    10
     IBAN (formatlı)     32      OTP                  6
     Yüzde              5 ("100,0")   Vade            3
     Kilometre           9 ("1.250.000")
     Tutar              16 ("999.999.999,99")
     Ad · Soyad         50      Firma/unvan        100
     E-posta           254 (RFC 5321)
   ŞASİ: sınır YOK — 17 hane beklenir ama ithal araçta aşabilir;
   üst sınır DB kolon genişliğinden okunur, arayüze sabit yazılmaz.
   SERBEST METİN (not, açıklama, adres): sınır yok; textarea kullanılır. */

/* ── BİRİM KONUMU (bağlayıcı) ─────────────────────────────────────
   PARA SİMGESİ → ÖN EK:  ₺765.000,00
     Türkçe yazım kuralı bu; ayrıca negatif işaret de rakamdan önce
     geliyor (−₺81.500) ve ikisinin aynı tarafta durması gerekiyor.
     Kullanım: .os-inputgroup > .os-inputgroup__affix + input.os-input--affix-start
   ÖLÇÜ BİRİMİ → SON EK:  45 %   171.250 km   48 ay   90 gün
     Konuşma sırası da bu: sayı, sonra birim. Ayrıca adımlayıcının
     birim yuvası (.os-stepper-input__unit) yapısal olarak yalnız son ek —
     ölçü birimini ön eke almak o bileşende imkânsız olurdu.
     Kullanım: input.os-input--affix-end + .os-inputgroup__affix--end
   TİP ETİKETİ → ÖN EK:  VKN 1234567890   +90 5379524037
     Birim değil: değerin NE OLDUĞUNU söyler ve okumadan önce bilinmesi
     gerekir. Sonda dursa kullanıcı numarayı okuduktan sonra hangi
     numarayı okuduğunu anlar.
   DOĞRULAMA İKONU → SON EK:  1234567890 ✓
     Sonuç, okuma bittikten sonra gelir.
   Bu ayrımlar yazılmadığı sürece aynı alan tipi iki görünüme sahip
   oluyor: "% 15" ile "10 %", "VKN 123…" ile "123… VKN" yan yana. */

/* ── Ad / soyad: BÜYÜK harf ───────────────────────────────────────
   Kişi ve kurum adları kayıtta BÜYÜK harfle saklanır. Sebep biçimsel
   değil işlevsel: aynı müşteri "Ahmet Yılmaz", "AHMET YILMAZ" ve
   "ahmet yılmaz" olarak üç kez girildiğinde mükerrer kayıt oluşuyor
   ve arama eşleşmiyor. Tek biçim, tek kayıt.

   TÜRKÇE TUZAĞI — bu alanın en kritik kuralı:
       "i".toUpperCase()               → "I"   ✗ YANLIŞ
       "i".toLocaleUpperCase("tr-TR")  → "İ"   ✓ DOĞRU
   İngilizce dönüşüm "Işık"ı "ISIK", "İnci"yi "INCI" yapar; isim yanlış
   kaydolur ve kimlik doğrulamada eşleşmez. Dönüşüm DAİMA "tr-TR"
   yerelinde yapılır — hem arayüzde hem sunucuda.

   Dönüşüm blur'da yapılır, yazarken değil: kullanıcı kendi yazdığının
   harf harf değiştiğini görmemeli. Görünen metin CSS ile büyük gösterilir
   (text-transform), böylece yazarken de sonucu görür ama imleç kaymaz.
   NEREDE UYGULANMAZ: serbest metin, not, açıklama, e-posta (küçüğe iner),
   adres satırı. Yalnız AD, SOYAD, UNVAN, FİRMA ADI. */
.os-input--upper { text-transform: uppercase; }

/* ── Sayı adımlayıcı ──────────────────────────────────────────────
   Küçük ve SINIRLI tam sayılarda (sahiplik/pay oranı, vade sayısı, gün)
   ok butonlarıyla 1 birim artırma/azaltma. Portalde bu davranış
   type="number" spinner'ı ile vardı; maskeli metin alanına geçilirken
   düşmemeli — kullanıcı 40'tan 41'e çıkmak için alanı silip yazmaz.
   Native spinner yerine gerçek buton, üç sebeple:
     · hedef 44px'e çıkarılabilir (native spinner ~10px, dokunmatikte kullanılamaz)
     · tarayıcılar arası görünüm farkı yok
     · min/max'ta pasifleşebilir; native spinner sessizce durur
   Klavye: ArrowUp/ArrowDown da çalışır (native davranışın karşılığı).
   NEREDE KULLANILMAZ: tutar ve kilometre. Orada 1 birim anlamsız,
   kullanıcı doğrudan yazar. */
.os-stepper-input { display: flex; align-items: stretch; }
.os-stepper-input > input {
  border-radius: 0; min-width: 0; flex: 1;
  border-left: 0; border-right: 0;
  /* HİZALAMA İSTİSNASI — bilinçli: --num normalde SAĞA yaslıdır (miktarlar
     alt alta karşılaştırılsın diye), ama adımlayıcıda değer iki butonun
     ARASINDA duruyor ve karşılaştırılacak bir sütun yok; sağa yaslamak
     değeri "+" butonuna yapıştırır. Tek değer, ortada okunur. */
  text-align: center;
}
/* Birim yuvası (%, km, gün, ay). .os-inputgroup__affix burada KULLANILAMAZ:
   o mekanizma position:absolute + input'ta padding-right ile çalışıyor,
   adımlayıcıda ise input bir flex item ve sarmalayıcı static. Birim bu
   yüzden gerçek bir flex kardeşi olur — sayfada inline span eklemek aynı
   borcu her kullanıma taşır. */
.os-stepper-input__unit {
  flex: none; display: grid; place-items: center;
  padding: 0 var(--space-2);
  border-top: 1px solid var(--border-strong);
  border-bottom: 1px solid var(--border-strong);
  background: var(--surface-card);
  color: var(--text-muted); font-size: var(--text-sm);
  white-space: nowrap;
}
.os-stepper-input__btn {
  flex: none; width: var(--control-h-md); display: grid; place-items: center;
  border: 1px solid var(--border-strong); background: var(--surface-card);
  color: var(--text-secondary); cursor: pointer; font-size: var(--text-md);
  line-height: 1;
}
.os-stepper-input__btn:first-child { border-radius: var(--radius-md) 0 0 var(--radius-md); }
.os-stepper-input__btn:last-child  { border-radius: 0 var(--radius-md) var(--radius-md) 0; }
.os-stepper-input__btn:hover:not(:disabled) { background: var(--surface-sunken); color: var(--text-primary); }
.os-stepper-input__btn:disabled { color: var(--gray-300); cursor: default; }
.os-stepper-input__btn:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -2px; z-index: 1; }
@media (max-width: 639px) {
  .os-stepper-input__btn { width: var(--control-h-lg); }
}

.os-select--sm, .os-input--sm { height: var(--control-h-sm); font-size: var(--text-sm); }
.os-select--lg, .os-input--lg { height: var(--control-h-lg); font-size: var(--text-lg); }
.os-select {
  appearance: none; padding-right: var(--space-8); cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%234A5769' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
}

/* Input with affix (₺, %, icon) */
.os-inputgroup { position: relative; display: flex; align-items: center; }
.os-inputgroup__affix {
  position: absolute; left: var(--space-3); color: var(--text-muted);
  font-size: var(--text-md); pointer-events: none;
}
.os-inputgroup__affix--end { left: auto; right: var(--space-3); }
.os-inputgroup .os-input--affix-start { padding-left: var(--space-8); }
.os-inputgroup .os-input--affix-end { padding-right: var(--space-8); }
/* Son-ek GENİŞLİĞİNE göre padding — inline style yerine sınıf.
   Belgede 46 input'ta elle padding-right yazılmıştı; kopyalanan her
   örnek hileyi de kopyalıyordu. Son-ek dört boyda gelir:
     (varsayılan)  tek ikon / kısa birim (₺ % ay)        → 38px
     --md          iki karakterli sayaç / uzun birim (km) → 48px
     --lg          "7/10" biçiminde sayaç, iki ikon        → 56px
     --xl          maske ipucu "gg.aa.yyyy"                → 88px */
.os-inputgroup .os-input--affix-end-md { padding-right: 48px; }
.os-inputgroup .os-input--affix-end-lg { padding-right: 56px; }
.os-inputgroup .os-input--affix-end-xl { padding-right: 88px; }

/* ── Form field wrapper ─────────────────────────────────── */
.os-field { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.os-field__label {
  font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-secondary);
}
.os-field__label .os-req { color: var(--status-danger-solid); margin-left: 2px; }
.os-field__hint { font-size: var(--text-xs); color: var(--text-muted); }
.os-field__error { font-size: var(--text-xs); color: var(--status-danger-fg); font-weight: var(--weight-medium); }

/* Form grid: responsive column layout for dense forms */
.os-formgrid { display: grid; gap: var(--space-4); grid-template-columns: repeat(12, 1fr); }
.os-formgrid > * { grid-column: span 12; }
@media (min-width: 640px)  { .os-formgrid > .os-col-6  { grid-column: span 6; }
                             .os-formgrid > .os-col-4  { grid-column: span 6; } }
@media (min-width: 1024px) { .os-formgrid > .os-col-6  { grid-column: span 6; }
                             .os-formgrid > .os-col-4  { grid-column: span 4; }
                             .os-formgrid > .os-col-3  { grid-column: span 3; } }

/* ── Checkbox / Radio ───────────────────────────────────── */
.os-check { display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; font-size: var(--text-md); }
.os-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.os-check__box {
  width: 18px; height: 18px; flex: none; border-radius: var(--radius-sm);
  border: 1.5px solid var(--border-strong); background: var(--surface-card);
  display: inline-flex; align-items: center; justify-content: center;
  transition: all var(--duration-fast) var(--ease-standard);
}
.os-check__box--radio { border-radius: 50%; }
.os-check input:checked + .os-check__box {
  background: var(--interactive-primary); border-color: var(--interactive-primary);
}
/* Onay kutusu tonu durumdan gelebilir — varsayılan marka mavisi. */
.os-check--success input:checked + .os-check__box { background: var(--status-success-solid); border-color: var(--status-success-solid); }
.os-check--warning input:checked + .os-check__box { background: var(--status-warning-fg);    border-color: var(--status-warning-fg); }
.os-check--danger  input:checked + .os-check__box { background: var(--status-danger-solid);  border-color: var(--status-danger-solid); }
.os-check--returned input:checked + .os-check__box { background: var(--status-returned-solid); border-color: var(--status-returned-solid); }
/* Tik MASKE olarak gelir, resim olarak değil: rengi background-color
   belirler, böylece durumdan duruma değiştirilebilir. Sabit stroke='white'
   ile çizilen tik, kutu griye döndüğünde (disabled+checked) kayboluyordu —
   yani tikin varlık sebebi olan durumda görünmüyordu. */
.os-check input:checked + .os-check__box:not(.os-check__box--radio)::after {
  /* 12px maske 18px kutuda kenara dayanıyordu; 11px ile her yanda 3.5px
     nefes payı kalır. Maske KARE olmak zorunda — dikdörtgene ezilirse
     tikin açısı bozulur. */
  content: ""; width: 11px; height: 11px;
  background-color: var(--text-inverse);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m2.5 8.5 3.5 3.5 7.5-8'/%3E%3C/svg%3E") center/contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m2.5 8.5 3.5 3.5 7.5-8'/%3E%3C/svg%3E") center/contain no-repeat;
}
/* Radyo NOKTA çizer, tik değil. Tik kuralı :not(--radio) ile daraltıldığı
   için burada mask sıfırlaması gerekmiyor — dikkat: 'background' kısayolu
   background-image'ı sıfırlar ama MASK'A DOKUNMAZ. Tik kuralı radyoyu da
   kapsasaydı bu 8x8 nokta tik yoluna kırpılırdı. */
.os-check input:checked + .os-check__box--radio::after {
  background: var(--text-inverse); border-radius: 50%; width: 8px; height: 8px;
}
/* Belirsiz durum: bazı satırlar seçili. "Tümünü seç" kutusunun üçüncü
   hâli — dolu tik ile boş kutu arasında ayrı bir işaret gerekir, yoksa
   kullanıcı "hepsi seçili" sanar. Çizgi, tikten biçimce ayrı.
   Tip daraltması zorunlu: :indeterminate, hiçbiri seçili olmayan bir RADIO
   grubunun tüm üyelerini de yakalar — daraltılmazsa her radio listesi ilk
   açılışta "hepsi kısmi seçili" gibi görünür (ekspertiz karar modalları). */
.os-check input[type="checkbox"]:indeterminate + .os-check__box {
  background: var(--interactive-primary); border-color: var(--interactive-primary);
}
.os-check input[type="checkbox"]:indeterminate + .os-check__box::after {
  content: ""; width: 10px; height: 2px; border-radius: 1px;
  background: var(--text-inverse); -webkit-mask-image: none; mask-image: none;
}
.os-check input:focus-visible + .os-check__box { box-shadow: var(--focus-ring); }
.os-check input:disabled + .os-check__box { background: var(--surface-sunken); border-color: var(--border-default); }
/* Pasif + seçili: kutu gri kalır (öğe etkin değil) ama tik KOYULAŞIR —
   beyaz tik gri kutuda 1.16:1, görünmüyordu. --text-muted ile 5.08:1.
   Bu durum kritik: aksi hâlde "pasif+seçili" ile "pasif+boş" ayırt
   edilemiyor, yani salt-okunur formda seçimin ne olduğu okunamıyor. */
.os-check input:disabled:checked + .os-check__box:not(.os-check__box--radio)::after { background-color: var(--text-muted); }
.os-check input[type="checkbox"]:disabled:indeterminate + .os-check__box::after { background: var(--text-muted); }
.os-check input:disabled:checked + .os-check__box--radio::after { background: var(--text-muted); }

/* ── Switch ─────────────────────────────────────────────── */
.os-switch { display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; font-size: var(--text-md); }
.os-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.os-switch__track {
  width: 36px; height: 20px; flex: none; border-radius: var(--radius-pill);
  background: var(--gray-300); position: relative;
  transition: background var(--duration-normal) var(--ease-standard);
}
.os-switch__track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--text-inverse); box-shadow: var(--shadow-xs);
  transition: transform var(--duration-normal) var(--ease-standard);
}
.os-switch input:checked + .os-switch__track { background: var(--status-success-solid); }
.os-switch input:checked + .os-switch__track::after { transform: translateX(16px); }
.os-switch input:focus-visible + .os-switch__track { box-shadow: var(--focus-ring); }
/* Pasif anahtar (yetkisiz / salt okunur): durum okunur kalır, tıklanamaz görünür */
.os-switch:has(input:disabled) { cursor: default; color: var(--text-muted); }
.os-switch input:disabled + .os-switch__track { opacity: .5; }

/* Tıklama alanı — görsel boyuttan BAĞIMSIZ.
   Şeffaf ::after her iki varyantı 4px taşırır:
     .os-iconbtn      36px görsel → 44px hedef  ✅ AA dokunma eşiği
     .os-iconbtn--sm  28px görsel → 36px hedef  (masaüstü-yoğun; fare
                      hassasiyeti için yeterli, dokunma eşiğini KARŞILAMAZ)
   --sm bilinçli bir ödünçtür: 28px'i 44px'e çıkarmak 8px taşma, o da kardeşler
   arasında ≥16px boşluk demek — yoğun tablo satırında mümkün değil. Bunun
   yerine <640px'te aşağıdaki medya sorgusu İKİSİNİN DE görselini 44px yapar.
   Her iki varyantta kardeşler arasında en az 8px boşluk bırakılır. */
.os-iconbtn { position: relative; }
.os-iconbtn::after { content: ""; position: absolute; inset: -4px; border-radius: inherit; }

/* Mobile: bump control heights to 44px touch targets */
@media (max-width: 639px) {
  .os-btn, .os-input, .os-select { height: var(--control-h-lg); }
  .os-btn--sm, .os-select--sm, .os-input--sm { height: var(--control-h-md); }
  /* Dokunmatikte ikon butonun GÖRSELİ de 44px olur — parmak hassasiyeti
     fare kadar değil, şeffaf alan yetmez. */
  /* Dar ekranda etiketi gizlenebilen buton: --collapse taşıyan butonun
     metni düşer, ikon kalır. Etiket aria-label'da yaşamaya devam eder. */
  /* Dar ekranda ikona düşen buton. Metni gizlemek için sarmalayıcı
     GEREKMEZ: font-size:0 metin düğümünü çökertir, ikon kendi boyutunu
     geri alır. Erişilebilir ad aria-label'da yaşamaya devam eder —
     görsel metin düşse de ekran okuyucu butonu adıyla okur. */
  .os-btn--collapse {
    width: var(--control-h-lg); padding: 0; gap: 0;
    font-size: 0; overflow: hidden; white-space: nowrap;
  }
  .os-btn--collapse .os-btn__icon { font-size: 18px; width: 1em; height: 1em; }
  .os-iconbtn, .os-iconbtn--sm { width: var(--control-h-lg); height: var(--control-h-lg); }
  .os-iconbtn::after, .os-iconbtn--sm::after { inset: 0; }
  /* Onay kutusu ve radyo: kutu görseli 18px kalır (satır yüksekliğini
     şişirmemek için) ama hedef 44px'e genişler. Toplu seçim gibi merkezî
     bir eylem 18px'lik hedefte bırakılamaz. */
  .os-check { position: relative; }
  /* Hedef ETİKETİN ::before'unda — .os-check__box::after tikin kendisi,
     onu ezmek mobilde tiki görünmez yapıyordu. */
  .os-check::before {
    content: ""; position: absolute; top: 50%; left: 0;
    width: var(--control-h-lg); height: var(--control-h-lg);
    transform: translateY(-50%);
  }
}


/* ══ İKON RENGİ — sayfada değil, bileşende ═══════════════════════════
   Kural: <i data-lucide> etiketi ASLA style="color:…" taşımaz. Rengi
   ya bulunduğu bileşenden miras alır (currentColor) ya da aşağıdaki
   ton sınıfından. Böylece marka mavisi yeşile çekildiğinde 27 ikonu
   tek tek değil, TEK token'ı değiştirerek çevirirsin.
   Belgede 27 ikon inline color taşıyordu — hepsi bu sınıflara alındı. */
/* İkon kütüphanesi: BOOTSTRAP ICONS (font glifi) — portalin tek kütüphanesi
   (_head.html:19 → vendor/bootstrap-icons/). RemixIcon Y-08 ile kaldırıldı;
   ikinci set eklenmez, CDN kullanılmaz.
   Glif SVG DEĞİL: boyut width/height ile verilmez, font-size'dan gelir.
   width:1em yalnız hizalama için; line-height:1 tabanı sıfırlar, yoksa
   glif metnin yanında 1-2px aşağı kayar. */
.os-icon {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 16px; line-height: 1; width: 1em; height: 1em; flex: none;
  color: currentColor;
}
.os-icon--sm      { font-size: 14px; }
.os-icon--xs      { font-size: 12px; }
.os-icon--lg      { font-size: 20px; }
.os-icon--muted   { color: var(--text-muted); }
.os-icon--secondary { color: var(--text-secondary); }
.os-icon--brand   { color: var(--text-brand); }
.os-icon--success { color: var(--status-success-fg); }
.os-icon--warning { color: var(--status-warning-fg); }
.os-icon--danger  { color: var(--status-danger-fg); }
.os-icon--info    { color: var(--status-info-fg); }
.os-icon--returned { color: var(--status-returned-fg); }
/* Koyu zeminde (topbar, bulk bar, toast) ikon beyaza döner — zemin
   bileşeni bunu KENDİ verir; sayfa color: var(--text-inverse) yazmaz. */
.os-topbar .os-icon, .os-bulkbar .os-icon, .os-toast .os-icon { color: var(--text-inverse); }
.os-topbar .os-icon--muted, .os-bulkbar .os-icon--muted, .os-toast .os-icon--muted { color: var(--on-dark-faint); }

/* Koyu zemin üstü ikincil buton — BulkBar/Toast içinde. Sayfa
   var(--on-dark-fill) yazmaz; zemin bileşeni varyantı verir. */
.os-btn--on-dark {
  background: var(--on-dark-fill); color: var(--text-inverse);
  border-color: var(--on-dark-border);
}
.os-btn--on-dark:hover:not(:disabled) { background: var(--on-dark-fill-active); }
.os-btn--on-dark:active:not(:disabled) { background: var(--on-dark-fill-active); }

/* Durum simülasyonu — YALNIZ dokümantasyon için: hover/focus hâlini fare
   olmadan göstermek. Üretim kodunda kullanılmaz. */
.is-focus { box-shadow: var(--focus-ring); }
/* Utilities shared by affix controls. */
.os-inputgroup > input { min-width:0; }


/* ── Form yardımcıları (Spec 045/048 göçü, 2026-10-07) ──────────────────
   os-formgrid__end: ızgarada alanların TABANINA hizalanan öğe (ör. "Önizle" düğmesi).
   os-checkrow: yan yana onay kutuları (bildirim seçenekleri gibi), sarar.
   os-formactions: form sonu düğme satırı — sağa yaslı, üst çizgi; __note solda (ör. "* zorunlu alan").
   Dar ekranda düğmeler satırı paylaşır. */
.os-formgrid > .os-formgrid__end { align-self: end; }
.os-checkrow { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-5); min-height: 32px; }
.os-formactions {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap;
  padding-top: var(--space-4); border-top: 1px solid var(--border-default);
}
.os-formactions__note { margin-right: auto; min-width: 0; font-size: var(--text-xs); color: var(--text-muted); }
@media (max-width: 600px) {
  .os-formactions > .os-btn { flex: 1 1 auto; }
  .os-formactions__note { flex-basis: 100%; }
}


/* ── Ikon dugmesi: mesgul (yenileme suruyor) — ikon doner ── */
.os-iconbtn--busy > .bi { animation: os-spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) { .os-iconbtn--busy > .bi { animation: none; } }
