Öğrenciler - Test Soru Takip

Düzenli ders çalışmak kadar, yapılan çalışmaların sonuçlarını takip etmek de önemlidir. Bir derste kaç soru çözüldüğü, bu soruların kaçının doğru veya yanlış cevaplandığı ve zaman içerisinde nasıl bir gelişim gösterildiği, öğrenme sürecinin değerlendirilmesine yardımcı olur.

Test Soru Takip, öğrencilerin, öğretmenlerin ve velilerin test çözme süreçlerini daha düzenli izleyebilmesi amacıyla hazırlanmış, kullanımı kolay ve mobil cihazlarla uyumlu bir HTML uygulamasıdır.

Uygulama sayesinde farklı dersler için test sonuçları kaydedilebilir, doğru ve yanlış sayıları tarihlere göre grafiklerle incelenebilir ve elde edilen sonuçlar WhatsApp üzerinden paylaşılabilir.

Test Soru Takip Uygulaması Nedir?

Test Soru Takip; ders bazında soru çözme faaliyetlerini kayıt altına alan, geçmiş çalışmaları saklayan ve bu kayıtlardan istatistikler oluşturan dijital bir takip aracıdır.

Uygulamanın temel amacı yalnızca çözülen soru sayılarını göstermek değil, yapılan çalışmaların zaman içerisindeki değişimini görünür hâle getirmektir.

Örneğin bir öğrenci, matematik dersinde farklı günlerde çözdüğü testleri kaydederek doğru ve yanlış sayılarının nasıl değiştiğini takip edebilir. Böylece çalışma sürecinin sonuçları yalnızca hatırlanan bilgilere değil, somut kayıtlara dayalı olarak değerlendirilebilir.

Uygulama Nasıl Kullanılır?

Uygulama üç ana bölümden oluşmaktadır: Dersler, İstatistik ve Ayarlar.

1. Ders Ekleme

İlk olarak Dersler bölümünden takip edilmek istenen dersler eklenir.

Ders adı alanına Matematik, Türkçe, Fen Bilimleri, Tarih veya çalışılan başka bir dersin adı yazılarak Ders Ekle butonuna basılır.

Eklenen dersler liste hâlinde görüntülenir. Her ders için ayrı test kayıtları tutulabilir. Böylece farklı derslerdeki çalışmalar birbirine karışmadan takip edilebilir.

2. Test Sonuçlarını Kaydetme

Ders listesinden ilgili derse tıklandığında test kayıt ekranı açılır. Bu ekranda aşağıdaki bilgiler girilir:

  • Tarih ve Saat: Testin çözüldüğü tarih ve zaman.
  • Soru Adedi: Çözülen testte bulunan toplam soru sayısı.
  • Doğru: Doğru cevaplanan soru sayısı.
  • Yanlış: Yanlış cevaplanan soru sayısı.

Bilgiler girildikten sonra Kaydı Ekle butonuna basılarak test sonucu kaydedilir.

Uygulama, doğru ve yanlış sayılarının toplamının soru adedini aşmasına izin vermez. Boş bırakılan sorular bulunabileceği için doğru ve yanlış toplamının soru adedinden az olması mümkündür.

Örnek kayıt:

BilgiDeğer
DersMatematik
Tarih14.10.2026
Soru Adedi40
Doğru32
Yanlış6
Boş2

Bu kayıt, öğrencinin ilgili tarihteki çalışmasını gösterir. Aynı derse farklı günlerde yeni kayıtlar eklendikçe çalışma geçmişi oluşur.

3. Kayıtları Görüntüleme ve Silme

Her dersin altında, daha önce kaydedilen test sonuçları tarih ve saat bilgileriyle birlikte görüntülenir.

Yanlış girilen veya artık saklanması istenmeyen kayıtlar silinebilir.

Veri kaybını önlemeye yardımcı olmak amacıyla silme işlemlerinde iki aşamalı onay uygulanır. İlk Sil onayından sonra Emin misiniz? sorusu görüntülenir. Kullanıcı ikinci onayı verdiğinde kayıt kalıcı olarak silinir.

Bir ders silindiğinde o derse ait test kayıtları da silinir. Bu nedenle ders silme işlemlerinin dikkatli yapılması gerekir.

İstatistiklerle Çalışma Performansını Değerlendirme

Uygulamanın önemli özelliklerinden biri, kaydedilen test sonuçlarının grafiklerle gösterilmesidir.

İstatistik bölümünde bütün derslerin sonuçları birlikte incelenebileceği gibi yalnızca belirli bir ders de seçilebilir.

Bu bölümde toplam soru, doğru ve yanlış sayıları ile doğru cevaplama oranı görüntülenir.

İstatistik ekranında iki ayrı grafik bulunmaktadır.

Doğru Grafiği

Doğru grafiği, çözülen testlerdeki doğru cevap sayılarının tarihlere göre dağılımını gösterir.

Grafiğin yatay ekseninde tarihler, dikey ekseninde doğru cevap sayıları yer alır.

Aynı tarihte birden fazla test çözülmüşse bunların doğru sayıları günlük toplam olarak gösterilir.

Bu grafik, öğrencinin çalışma sonuçlarındaki değişimi görmesine yardımcı olur.

Yanlış Grafiği

Yanlış grafiği, testlerde verilen yanlış cevapların tarihlere göre dağılımını gösterir.

Bu grafik sayesinde yanlış cevap sayılarının hangi günlerde arttığı veya azaldığı incelenebilir.

Doğru ve yanlış grafikleri birlikte değerlendirildiğinde çalışma sürecine ilişkin daha anlamlı sonuçlara ulaşılabilir. Bununla birlikte soru sayıları ve testlerin güçlük düzeyleri de farklılaşabileceğinden, yalnızca grafiklerdeki artış veya azalışa bakılarak kesin başarı değerlendirmesi yapılmamalıdır.

WhatsApp ile Test Sonuçlarını Paylaşma

Test Soru Takip, kaydedilen sonuçların öğretmenler, veliler veya öğrencilerle paylaşılmasını kolaylaştıran WhatsApp paylaşım özelliğine sahiptir.

Bunun için önce Ayarlar bölümündeki Telefon Gir alanından paylaşım yapılacak telefon numaraları eklenir.

Telefon numaraları, Türkiye cep telefonu biçimine uygun olarak başında sıfır bulunacak şekilde 11 rakamdan oluşmalıdır. Örneğin 05XXXXXXXXX.

Birden fazla telefon numarası kaydedilebilir. İstenmeyen telefon kayıtları çift onayla silinebilir.

Test Kaydını Paylaşma

Bir test kaydının altındaki WhatsApp Paylaş butonuna basıldığında ders adı, tarih ve saat, soru adedi, doğru ve yanlış sayıları paylaşılmak üzere hazırlanır.

Uygulama, kayıtlı bütün telefon numaralarını sırayla kullanır. İlk numara için WhatsApp açılır; mesaj gönderildikten sonra uygulamaya dönülerek sonraki numaraya geçilir.

Böylece paylaşılacak kişilerin her seferinde yeniden seçilmesine gerek kalmaz.

Önemli: Uygulama mesajları kullanıcı onayı olmadan otomatik olarak göndermez. Her mesajın WhatsApp üzerinden ayrıca gönderilmesi gerekir.

İstatistikleri Paylaşma

İstatistikleri WhatsApp ile Paylaş butonu kullanılarak toplam soru, doğru, yanlış ve doğru oranı bilgileri paylaşılabilir.

Ayrıca tarihlere göre doğru ve yanlış dağılımları, metin tabanlı grafik gösterimiyle mesaja eklenir.

Bu özellik özellikle velilerin öğrencinin çalışmalarını izlemesi veya öğrencinin gelişimini öğretmeniyle paylaşması açısından yararlı olabilir.

Test Soru Takip Uygulamasının Faydaları

Düzenli kayıt oluşturur. Çözülen testlerin sonuçları tek bir yerde tutulur. Geçmiş çalışmaların ne zaman yapıldığı ve nasıl sonuçlandığı kolaylıkla görülebilir.

Gelişimin izlenmesini kolaylaştırır. Günlük kayıtlar ve grafikler sayesinde çalışma sonuçlarındaki değişimler takip edilebilir.

Ders bazında değerlendirme sağlar. Her ders ayrı kaydedildiğinden hangi derse daha fazla zaman ve çalışma ayrılması gerektiğine ilişkin değerlendirmeler yapılabilir.

Öğrenci, veli ve öğretmen arasındaki iletişimi destekler. Test sonuçlarının WhatsApp üzerinden paylaşılması, çalışma sürecine ilişkin bilgi alışverişini kolaylaştırır.

Mobil cihazlarla uyumludur. HTML tabanlı arayüz, telefon, tablet ve bilgisayar ekranlarının genişliğine uyum sağlayacak şekilde tasarlanmıştır. Küçük ekranlarda da kullanılabilir.

Kullanımı kolaydır. Ders ekleme, test kaydetme, istatistik görüntüleme ve paylaşma işlemleri anlaşılır menüler üzerinden gerçekleştirilebilir.

Veriler Nerede Saklanır?

Test Soru Takip'in HTML sürümünde dersler, test sonuçları ve telefon numaraları kullanılan tarayıcının yerel depolama alanında saklanır.

Uygulamanın temel kayıt ve istatistik işlemleri için ayrıca bir kullanıcı hesabı oluşturulması gerekmez. WhatsApp üzerinden paylaşım yapılabilmesi için ise internet bağlantısı gerekir.

Yedekleme: Ayarlar bölümüne Yedek İndir ve Yedekten Yükle seçenekleri eklendi.

Kimler Kullanabilir?

Test Soru Takip yalnızca okul öğrencileri için değil; LGS, YKS, KPSS, ALES, DGS ve benzeri sınavlara hazırlanan kişiler tarafından da kullanılabilir.

Öğretmenler öğrencilere düzenli kayıt tutma alışkanlığı kazandırmak, veliler çocuklarının çalışmalarını takip etmek, bireysel kullanıcılar ise kendi soru çözme performanslarını değerlendirmek amacıyla uygulamadan yararlanabilir.

Sonuç

Başarılı bir çalışma süreci yalnızca çok sayıda soru çözmekle değil, çalışmaların sonuçlarını değerlendirmek ve eksiklikleri fark etmekle de ilişkilidir.

Test Soru Takip, bu değerlendirmeyi kolaylaştırmak amacıyla geliştirilmiş pratik bir dijital araçtır. Ders ve test kayıtlarının düzenli tutulması, doğru ve yanlış sayılarının grafiklerle incelenmesi ve sonuçların kolayca paylaşılabilmesi, çalışma sürecinin daha ölçülebilir hâle gelmesine yardımcı olur.

Uygulama, tek başına başarıyı garanti eden bir sistem değil; bilinçli, düzenli ve veriye dayalı çalışma alışkanlığını destekleyen yardımcı bir araçtır.

Test Soru Takip ile çalışmalarınızı kaydedin, gelişiminizi izleyin ve öğrenme sürecinizi daha bilinçli yönetin.


Web Uygulaması Görünüm:

Aşağıdaki HTML kodu kopyalayıp not defterine yapıştırıp "Test Soru Takip.html" uzantılı olarak kaydedin. Çift tıklayarak Chrome gibi bir bowser ile açmanız yeterlidir. 

+ Projenin Android telefonlar için mobil uygulamaya dönüştürülmüş hali aşağıdaki paylaşımdadır. "Test Soru Takip.apk" indir.

HTML KOD
<!DOCTYPE html>
<html lang="tr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><meta name="theme-color" content="#dbeafe"><meta name="color-scheme" content="light"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-title" content="Test Soru Takip"><title>Test Soru Takip</title>
<style>
:root{--bg:#f0f7ff;--surface:#fff;--primary:#147dc6;--ink:#173552;--muted:#688198;--line:#d8e6f3;--danger:#d64545;--blue:#147dc6;--green:#138a65;--shadow:0 3px 18px rgba(32,91,143,.08)}
*{box-sizing:border-box}html{height:100%;-webkit-text-size-adjust:100%}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 system-ui,-apple-system,"Segoe UI",Arial,sans-serif;min-height:100%;padding-bottom:calc(88px + env(safe-area-inset-bottom));-webkit-tap-highlight-color:transparent}
button,input,select{font:inherit}button{cursor:pointer}button:disabled{opacity:.5;cursor:default}
header{background:#dcefff;border-bottom:1px solid #c5def2;padding:calc(17px + env(safe-area-inset-top)) 17px 16px}
.brand{max-width:720px;margin:auto;display:flex;align-items:center;gap:13px}.mark{width:49px;height:49px;flex:0 0 auto;border-radius:12px;background:#a9d9ff;border:1px solid #81bce9;display:flex;align-items:center;justify-content:center;color:#075389;font-weight:900;font-size:15px;letter-spacing:.4px}.brand h1{font-size:20px;line-height:1.2;margin:0;font-weight:800}.brand p{margin:4px 0 0;color:#526e87;font-size:12px}
main{max-width:720px;margin:auto;padding:16px 14px}section.view{display:none}section.view.active{display:block}.card{background:var(--surface);border:1px solid var(--line);border-radius:13px;box-shadow:var(--shadow);padding:16px;margin-bottom:14px}.section-title{font-size:17px;font-weight:800;margin:0 0 12px;display:flex;align-items:center;justify-content:space-between;gap:12px}.sub{color:var(--muted);font-size:12px;margin:0 0 14px}.row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.split{display:flex;justify-content:space-between;align-items:center;gap:9px}.split>.grow{min-width:0;flex:1}.grow{flex:1}input,select{background:#fff;color:var(--ink);width:100%;padding:11px 12px;min-height:44px;border:1px solid #c9daea;border-radius:8px;outline:none}input:focus,select:focus{border-color:#268dc8;box-shadow:0 0 0 3px #dceffa}label{display:block;color:#385a75;font-weight:700;font-size:12px;margin:11px 0 5px}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px}.grid .wide{grid-column:1/-1}
.btn{border:1px solid transparent;border-radius:9px;font-weight:750;padding:10px 13px;min-height:43px;display:inline-flex;justify-content:center;align-items:center;gap:6px;background:#e6f3ff;color:#145c93;white-space:nowrap}.btn.primary{background:var(--primary);color:white}.btn.danger{background:#fff2f1;border-color:#f6d0ca;color:#b62d2d}.btn.ghost{background:white;border-color:var(--line);color:var(--ink)}.btn.small{padding:6px 10px;min-height:35px;font-size:12px}.btn:active{transform:scale(.985)}.full{width:100%}
.item{border:1px solid #dfebf5;border-radius:10px;background:#fff;display:flex;align-items:center;gap:9px;padding:10px 11px;margin-bottom:9px}.item:last-child{margin-bottom:0}.item-info{min-width:0;flex:1}.item-name{font-weight:800;word-break:break-word}.item-desc{font-size:12px;color:var(--muted);margin-top:2px}.open{border:0;background:transparent;text-align:left;min-width:0;flex:1;padding:0;color:inherit}.chevron{color:var(--muted);font-size:24px;line-height:1}.muted{color:var(--muted)}.empty{text-align:center;border:1px dashed #c4d9ec;border-radius:12px;padding:22px 14px;color:#6b8398;background:#fbfdff}.empty strong{display:block;color:#315f82;font-size:14px;margin-bottom:5px}
.stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:13px}.stat{background:#edf6ff;border:1px solid #d6eafb;border-radius:10px;padding:11px 7px;text-align:center}.stat strong{display:block;font-size:20px;color:#1b608c}.stat span{font-size:11px;color:#648198}.record{border:1px solid #e0ebf4;border-radius:10px;padding:12px;margin:10px 0}.recordtop{display:flex;align-items:start;justify-content:space-between;gap:8px}.record time{font-size:12px;color:#647e96}.pills{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}.pill{padding:4px 8px;border-radius:20px;background:#edf6ff;color:#205e89;font-size:12px}.pill.good{background:#e1f6ec;color:#117750}.pill.bad{background:#fff0ef;color:#b83933}
.chart{border:1px solid #dbeaf6;border-radius:11px;padding:13px 9px 7px;margin:14px 0;background:#fff}.chart h3{margin:0 5px 8px;font-size:15px}.chart svg{width:100%;height:auto;display:block}.chart .hint{font-size:11px;color:var(--muted);padding:6px}.tablewrap{overflow:auto;border:1px solid #e0eaf3;border-radius:9px}table{border-collapse:collapse;width:100%;font-size:12px}th,td{padding:9px;text-align:left;border-bottom:1px solid #e6eff7;white-space:nowrap}th{background:#f2f8fe;color:#406783}tr:last-child td{border:0}
nav{position:fixed;z-index:10;bottom:0;left:0;width:100%;border-top:1px solid #c7ddee;background:rgba(255,255,255,.98);display:flex;justify-content:center;padding:8px 9px calc(8px + env(safe-area-inset-bottom));box-shadow:0 -2px 18px #33669911}nav .navin{width:min(100%,720px);display:flex;gap:6px}nav button{flex:1;border:0;border-radius:9px;background:transparent;color:#6e8799;padding:9px 2px;display:flex;align-items:center;justify-content:center;gap:5px;flex-direction:column;font-size:11px;font-weight:750}nav button span{font-size:20px;line-height:1.1}nav button.active{color:#0b72b4;background:#e8f5ff}
.toast{position:fixed;z-index:99;bottom:92px;left:50%;transform:translateX(-50%);padding:11px 16px;color:white;background:#254d6b;border-radius:9px;box-shadow:var(--shadow);max-width:85vw;width:max-content;display:none;text-align:center}.toast.show{display:block}
dialog{border:1px solid #cfdeeb;border-radius:15px;padding:0;width:min(92vw,390px);box-shadow:0 18px 65px #0b284852}dialog::backdrop{background:#0e29456b}.modal{padding:22px}.modal h2{font-size:18px;margin:0 0 10px}.modal p{color:#476680;margin:0 0 20px}.modal .row{justify-content:flex-end}
@media (max-width:380px){main{padding:13px 9px}.card{padding:12px}.grid{gap:6px}.btn{padding:9px}.pills{gap:4px}.recordtop{flex-direction:column}}

/* Google Chrome, iOS tarayıcıları ve sınırlı dosya önizlemeleri. */
#appRuntime{width:100%;background:#fff4e3;color:#744710;padding:11px max(12px,env(safe-area-inset-left)) 11px max(12px,env(safe-area-inset-right));font-size:13px;line-height:1.5;border-bottom:1px solid #f2d7ab}
#appRuntime[hidden]{display:none!important}
#appRuntime strong{color:#68400d}
.fallbackDialog{position:fixed;inset:50% auto auto 50%;transform:translate(-50%,-50%);z-index:100;width:min(92vw,390px);max-height:85dvh;overflow-y:auto;box-shadow:0 0 0 100vmax rgba(14,41,69,.48),0 18px 65px #0b284852}
.share-direct{display:flex;text-align:center;margin-top:10px;text-decoration:none}.share-direct[hidden],#shareNext[hidden],#shareModeChoices[hidden]{display:none!important}
/* Responsive full-width layout — phones, foldables, tablets and desktop. */
:root{--page-gutter:clamp(8px,1.4vw,24px)}
html,body{width:100%;max-width:100%;min-width:0;overflow-x:hidden}
header{width:100%;padding-right:max(var(--page-gutter),env(safe-area-inset-right));padding-left:max(var(--page-gutter),env(safe-area-inset-left))}
.brand{width:100%;max-width:none;min-width:0;margin:0}
.brand>div:last-child{min-width:0}
main{width:100%;max-width:none;min-width:0;margin:0;padding:clamp(9px,1.4vw,22px) max(var(--page-gutter),env(safe-area-inset-right)) clamp(9px,1.4vw,22px) max(var(--page-gutter),env(safe-area-inset-left))}
section.view, #courseIndex, #courseDetail,.card,.chart,.tablewrap{width:100%;max-width:100%;min-width:0}
.card{overflow-wrap:break-word}
.section-title{flex-wrap:wrap;min-width:0}
.row,.item,.recordtop{min-width:0}
.row>.grow{min-width:0}
.entry-create{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px}
.entry-create>.grow{width:100%;min-width:0}
.entry-create>.btn{min-width:0;max-width:100%}
.grid>div{min-width:0}
.grid input{min-width:0;max-width:100%}
nav{width:100%;right:0;padding-left:max(var(--page-gutter),env(safe-area-inset-left));padding-right:max(var(--page-gutter),env(safe-area-inset-right))}
nav .navin{width:100%;max-width:none;min-width:0;margin:0}
nav button{min-width:0}
@media (max-width:360px){
  .entry-create{grid-template-columns:minmax(0,1fr)}
  .entry-create>.btn{width:100%}
  .card{padding:12px}
  .stats{gap:5px}
  .stat strong{font-size:18px}
}
/* Prevent iOS focusing inputs from enlarging the whole viewport. */
@media (hover:none) and (pointer:coarse){input,select{font-size:16px}}

/* İstatistik: mobilde iki küçük grafik aynı sayfaya sığar. */
#statsView .card{padding:clamp(10px,1.8vw,16px)}
#statsView .section-title{margin-bottom:5px}
#statsView .stats{margin-top:9px!important;margin-bottom:6px;gap:6px}
#statsView .stat{padding:6px 5px;border-radius:8px}
#statsView .stat strong{font-size:18px;line-height:1.2}
#statsView .stat span{font-size:10px}
#statsView #accuracy{margin:3px 0 5px}
.charts-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin:8px 0 12px}
.charts-pair>.chart{margin:0;min-width:0;max-width:100%;padding:8px 7px 5px;border-radius:9px;overflow:hidden}
.charts-pair .chart h3{margin:0 0 2px;font-size:13px;line-height:1.3}
.chart-graphic{min-width:0;max-width:100%;overflow:hidden}
.chart-graphic svg{display:block;width:100%;height:125px;max-width:100%;overflow:visible}
.chart-graphic .hint{padding:0 4px 2px;font-size:10px;line-height:1.4}
.chart-graphic .empty{padding:12px 5px;font-size:12px}
@media (max-width:700px){
 .charts-pair{grid-template-columns:minmax(0,1fr);gap:7px;margin-top:7px}
 .charts-pair>.chart{padding:6px 7px 3px}
 .chart-graphic svg{height:111px}
 .chart-graphic .hint{font-size:10px}
 #statsView .sub{margin-bottom:7px}
 #statsView label{margin-top:7px;margin-bottom:4px}
}
.share-mode-choices{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:12px 0}
.share-mode-choices .btn{white-space:normal;text-align:center;line-height:1.25;min-height:50px}
#shareDialog .modal{max-height:85dvh;overflow-y:auto}
#shareDialog #shareProgress{margin:0 0 7px}
#shareDialog .share-help{font-size:12px;margin:8px 0 6px;color:#526e87}
#shareDialog .share-direct{white-space:normal}
@media (max-width:330px){.share-mode-choices{grid-template-columns:1fr}}

/* MASAÜSTÜ DÜZENİ: üst menü, yan yana bölümler ve okunur grafikler. */
#appRuntime[hidden]{display:none!important}
@media (min-width: 900px){
  :root{--page-gutter:clamp(18px,2.1vw,42px)}
  body{padding-bottom:32px;font-size:15px}
  header{padding:20px var(--page-gutter);box-shadow:none}
  header .brand{max-width:1450px;margin:0 auto}
  .mark{width:58px;height:58px;font-size:17px}
  .brand h1{font-size:24px}
  .brand p{font-size:13px}
  nav{position:sticky;top:0;bottom:auto;left:0;right:0;width:100%;z-index:30;
      padding:8px var(--page-gutter);border-top:0;border-bottom:1px solid #ccdfef;
      box-shadow:0 4px 12px rgba(20,63,100,.07);background:rgba(255,255,255,.97)}
  nav .navin{max-width:1450px;margin:0 auto;justify-content:flex-start;gap:10px}
  nav button{flex:0 0 auto;min-width:160px;min-height:44px;flex-direction:row;
      gap:10px;padding:10px 16px;font-size:14px;border-radius:9px}
  nav button span{font-size:19px}
  main{width:100%;max-width:1450px;margin:0 auto;padding:26px var(--page-gutter) 35px}
  .card{padding:23px;border-radius:12px;margin-bottom:0}
  .section-title{font-size:19px}
  .sub{font-size:13px}
  input,select{min-height:46px}
  .btn{min-height:43px}
  #courseIndex{display:grid;grid-template-columns:minmax(0,.83fr) minmax(0,1.17fr);gap:20px;align-items:start}
  #courseDetail:not([hidden]){display:grid;grid-template-columns:minmax(0,.83fr) minmax(0,1.17fr);gap:20px;align-items:start}
  #courseDetail #backCourses{grid-column:1/-1;justify-self:start;margin-bottom:-8px!important}
  #courseIndex .card,#courseDetail .card{min-width:0}
  #settingsView.active{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;align-items:start}
  #settingsView .card{min-width:0}
  #statsView .card{padding:24px}
  .charts-pair{grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;margin:15px 0 20px}
  .charts-pair>.chart{padding:15px 18px 12px;border-radius:11px}
  .charts-pair .chart h3{font-size:16px;margin-bottom:10px}
  .chart-graphic svg{height:235px}
  .chart-graphic .hint{font-size:12px}
  .stats{max-width:640px}
  #statsView .stat{padding:12px 8px}
  #statsView .stat strong{font-size:24px}
  #statsView .stat span{font-size:12px}
  #statsView .tablewrap{max-height:420px;overflow:auto}
  .record{padding:14px}
  dialog{width:min(92vw,450px)}
  .backup-actions{flex-wrap:wrap}
}
@media (min-width:900px) and (max-width:1130px){
  #courseIndex,#courseDetail:not([hidden]){grid-template-columns:repeat(2,minmax(0,1fr));gap:13px}
  .charts-pair{gap:12px}
}
@media (max-width:899px){
  .backup-card{margin-top:14px}
  .backup-actions>*{flex:1}
}
</style></head><body>
<header><div class="brand"><div class="mark">TEST</div><div><h1>Test Soru Takip</h1><p>Ders, soru ve başarı takibi</p></div></div></header>
<div id="appRuntime" role="alert" hidden></div>
<nav aria-label="Ana menü"><div class="navin"><button id="navCourses" class="active" data-view="courses"><span>▤</span>Dersler</button><button id="navStats" data-view="stats"><span>▥</span>İstatistik</button><button id="navSettings" data-view="settings"><span>⚙</span>Ayarlar</button></div></nav>
<main>
<section id="coursesView" class="view active">
 <div id="courseIndex">
  <div class="card"><h2 class="section-title">Derslerim</h2><p class="sub">Ders ekleyin, yaptığınız testlerin doğru ve yanlışlarını kaydedin.</p>
    <label for="newCourse">Ders adı</label><div class="row entry-create"><input id="newCourse" class="grow" type="text" maxlength="80" placeholder="Örn. Matematik" autocomplete="off"><button class="btn primary" id="addCourse">+ Ders Ekle</button></div>
  </div><div class="card"><h2 class="section-title">Eklenen dersler <span id="courseCount" class="muted" style="font-size:12px"></span></h2><div id="courseList"></div></div>
 </div>
 <div id="courseDetail" hidden>
  <button class="btn ghost small" id="backCourses" style="margin-bottom:12px">‹ Derslere dön</button>
  <div class="card"><h2 class="section-title" id="detailCourseName"></h2><p class="sub">Test tarihini, saatini ve soru sonuçlarını girin.</p>
   <div class="grid"><div class="wide"><label for="entryDate">Tarih ve saat</label><input id="entryDate" type="datetime-local"></div><div><label for="entryQuestions">Soru adedi</label><input id="entryQuestions" type="number" inputmode="numeric" min="1" step="1" placeholder="0"></div><div><label for="entryCorrect">Doğru</label><input id="entryCorrect" type="number" inputmode="numeric" min="0" step="1" placeholder="0"></div><div><label for="entryWrong">Yanlış</label><input id="entryWrong" type="number" inputmode="numeric" min="0" step="1" placeholder="0"></div></div>
   <p class="sub" style="margin:10px 0 12px">Doğru + yanlış, soru adedini aşamaz. Boş sorular olabilir.</p><button class="btn primary full" id="saveEntry">+ Kaydı Ekle</button>
  </div><div class="card"><h2 class="section-title">Test kayıtları <span id="entryCount" class="muted" style="font-size:12px"></span></h2><div id="entryList"></div></div>
 </div>
</section>
<section id="statsView" class="view"><div class="card"><h2 class="section-title">İstatistik</h2><p class="sub">Grafikler test tarihindeki toplam doğru ve yanlış sayılarını gösterir.</p><label for="statsFilter">Ders</label><select id="statsFilter"></select><div class="stats" style="margin-top:14px"><div class="stat"><strong id="totalQuestions">0</strong><span>Soru</span></div><div class="stat"><strong id="totalCorrect">0</strong><span>Doğru</span></div><div class="stat"><strong id="totalWrong">0</strong><span>Yanlış</span></div></div><div id="accuracy" class="sub"></div>
<div class="charts-pair" aria-label="Doğru ve yanlış grafikleri">
<div class="chart"><h3 style="color:#12865d">Doğru grafiği</h3><div id="correctGraph" class="chart-graphic"></div></div>
<div class="chart"><h3 style="color:#d5524c">Yanlış grafiği</h3><div id="wrongGraph" class="chart-graphic"></div></div>
</div>
<div class="tablewrap" id="statsTable"></div>
<p class="sub" style="margin:13px 0 7px">Paylaşım, Ayarlar’a eklenen <strong>tüm numaralar</strong> için hazırlanır. <strong>WhatsApp uygulaması</strong> veya <strong>WhatsApp Web</strong> seçebilirsiniz; mesajlar ilgili yerde ayrı ayrı gönderilir.</p><button class="btn primary full" id="shareStats" style="margin-top:10px">↗ İstatistikleri WhatsApp ile Paylaş</button></div></section>
<section id="settingsView" class="view"><div class="card"><h2 class="section-title">Ayarlar</h2><p class="sub">WhatsApp paylaşımı için telefon numaralarını cihazınıza kaydedin. Numaralar 0 ile başlamalıdır.</p><label for="newPhone">Telefon gir</label><div class="row entry-create"><input type="tel" id="newPhone" class="grow" inputmode="tel" maxlength="11" placeholder="05XXXXXXXXX"><button id="addPhone" class="btn primary">+ Ekle</button></div><p class="sub" style="margin-top:10px">Türkiye cep telefonu biçimi: 05XXXXXXXXX (11 rakam).</p></div><div class="card"><h2 class="section-title">Kayıtlı telefonlar</h2><div id="phoneList"></div></div><div class="card"><h2 class="section-title">Veri güvenliği</h2><p class="sub" style="margin-bottom:0">Ders ve telefon kayıtları bu cihazda saklanır. Uygulamayı kaldırmanız veya uygulama verilerini silmeniz kayıtları silebilir. Paylaşırken kayıtlı tüm numaralar sırayla kullanılır; WhatsApp uygulamasını veya WhatsApp Web’i seçebilirsiniz. Her mesajın gönderimini WhatsApp’ta ayrı ayrı onaylamanız gerekir.</p></div><div class="card backup-card"><h2 class="section-title">Kayıt yedeği</h2>
<p class="sub">Ders, test ve telefon bilgilerinizi bilgisayarınıza yedekleyebilir; gerektiğinde aynı dosyadan geri yükleyebilirsiniz.</p>
<div class="row backup-actions"><button id="exportBackup" type="button" class="btn primary">↓ Yedek İndir</button><button id="importBackup" type="button" class="btn ghost">↑ Yedekten Yükle</button></div>
<input type="file" id="backupFile" accept="application/json,.json" hidden aria-label="Yedek dosyası seç"/>
<p class="sub" style="margin:10px 0 0">Kayıtlar bilgisayarınızın tarayıcısında saklanır. Yedek dosyası cihazınızdan dışarıya gönderilmez.</p>
</div></section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<dialog id="shareDialog"><div class="modal">
<h2>WhatsApp paylaşımı</h2>
<p id="shareProgress" class="sub" aria-live="polite"></p>
<div class="share-mode-choices" id="shareModeChoices">
<button class="btn primary" id="shareAppMode" type="button">WhatsApp Uygulaması</button>
<button class="btn ghost" id="shareWebMode" type="button">WhatsApp Web</button>
</div>
<p id="shareMethodHelp" class="share-help">Kayıtlı numaraların hepsine sırayla paylaşmak için yöntem seçin. WhatsApp Web için tarayıcıda oturum açılmış olmalıdır. Bilgisayarda WhatsApp Web hesabınız açık olmalıdır.</p>
<div class="row" style="justify-content:flex-end;margin-top:12px"><button class="btn ghost" id="shareCancel" type="button">İptal / Kapat</button><button class="btn primary" id="shareNext" type="button" hidden>Sonraki numarayı aç</button></div>
<a id="shareDirectLink" class="btn ghost share-direct" href="https://wa.me/" target="_blank" rel="noopener noreferrer" hidden>↗ Bağlantıdan aç (pencere engellendiyse)</a>
</div></dialog>
<dialog id="confirmDialog"><div class="modal"><h2 id="confirmTitle">Onay</h2><p id="confirmText"></p><div class="row"><button class="btn ghost" type="button" id="confirmCancel">İptal</button><button class="btn danger" type="button" id="confirmYes">Sil</button></div></div></dialog>
<script>
(function(){'use strict';
const KEY='tst_data_v1', $=id=>document.getElementById(id);
let storageArea=null, storageMode='memory', selectedCourse=null, active='courses', toastTimer, shareQueue=null, shareIndex=-1;
let data=load();
function blankData(){return{courses:[],entries:[],phones:[]}}
function validData(x){return x&&Array.isArray(x.courses)&&Array.isArray(x.entries)&&Array.isArray(x.phones)}
function hasNativeStorage(){return !!(window.webkit&&window.webkit.messageHandlers&&window.webkit.messageHandlers.appStorage)}
function findStorage(){
  for(const type of ['localStorage','sessionStorage']){
    try{
      const store=window[type], probe=KEY+'_probe';
      store.setItem(probe,'1');
      if(store.getItem(probe)!=='1')continue;
      store.removeItem(probe);
      storageMode=type==='localStorage'?'local':'session';
      return store;
    }catch(e){}
  }
  storageMode='memory';
  return null;
}
function load(){
  // Xcode ile oluşturulan iOS uygulamasındaki kalıcı veri köprüsünü de destekler.
  if(hasNativeStorage()){
    storageMode='native';
    try{const saved=window.__testSoruTakipSavedData;if(validData(saved))return saved;}catch(e){}
    return blankData();
  }
  storageArea=findStorage();
  try{const value=storageArea&&storageArea.getItem(KEY);const parsed=value?JSON.parse(value):null;if(validData(parsed))return parsed;}catch(e){}
  return blankData();
}
function refreshRuntimeNotice(){
  // Masaüstü HTML sürümünde başlangıç / yerel dosya uyarısı gösterilmez.
  // Herhangi bir hata varsa kullanıcıya yalnızca gerçek işlem hatası iletilir.
}
function save(){
  try{
    const value=JSON.stringify(data);
    if(storageMode==='native'){
      window.webkit.messageHandlers.appStorage.postMessage(value);
      return true;
    }
    if(storageArea){storageArea.setItem(KEY,value);return true;}
  }catch(e){
    // Kalıcı depolama sonradan erişilemez olursa geçici moda geç.
    storageArea=null;
    storageMode='memory';
    refreshRuntimeNotice();
  }
  // Önizleme ortamında kayıt bellekte tutulur; kullanıcıya kalıcı olmadığı bildirilir.
  refreshRuntimeNotice();
  return true;
}
function showDialog(dialog){
  if(typeof dialog.showModal==='function')dialog.showModal();
  else{dialog.setAttribute('open','');dialog.classList.add('fallbackDialog');}
}
function hideDialog(dialog){
  if(typeof dialog.close==='function'&&dialog.open)dialog.close();
  else{dialog.removeAttribute('open');dialog.dispatchEvent(new Event('close'));}
  dialog.classList.remove('fallbackDialog');
}
function id(){return String(Date.now())+'-'+Math.random().toString(36).slice(2,9)}
function escapeText(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
function toast(msg){const t=$('toast');t.textContent=msg;t.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>t.classList.remove('show'),3400)}
function nowInput(){const d=new Date();return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate())+'T'+pad(d.getHours())+':'+pad(d.getMinutes())}
function pad(n){return String(n).padStart(2,'0')}
function dateText(dt){const d=new Date(dt);return isNaN(d)?dt:d.toLocaleString('tr-TR',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}
function dateKey(dt){return String(dt).slice(0,10)}
function shortDate(k){const v=k.split('-');return v[2]+'.'+v[1]+'.'+v[0].slice(-2)}
function courseName(courseId){const c=data.courses.find(c=>c.id===courseId);return c?c.name:'Silinmiş ders'}
// Tek bir onay penceresinde iki aşama: ilk "Sil", ardından "Emin misiniz?".
// İki dialogu peş peşe kapatıp açmak, gecikmiş `close` olayı nedeniyle
// ikinci onayı istemsiz olarak iptal ediyordu.
let pendingDelete=null;
function doubleDelete(thing,action){
  const dialog=$('confirmDialog');
  if(pendingDelete||dialog.open)return;
  pendingDelete={thing,action,stage:1};
  $('confirmTitle').textContent='Silme onayı';
  $('confirmText').textContent=thing+' silinsin mi?';
  $('confirmYes').textContent='Sil';
  try{showDialog(dialog)}catch(e){pendingDelete=null;toast('Silme onayı açılamadı.')}
}
function cancelDelete(){
  pendingDelete=null;
  if($('confirmDialog').open)hideDialog($('confirmDialog'));
}
$('confirmCancel').addEventListener('click',cancelDelete);
$('confirmDialog').addEventListener('cancel',()=>{pendingDelete=null});
$('confirmDialog').addEventListener('close',()=>{pendingDelete=null});
$('confirmYes').addEventListener('click',()=>{
  if(!pendingDelete)return;
  if(pendingDelete.stage===1){
    pendingDelete.stage=2;
    $('confirmTitle').textContent='Emin misiniz?';
    $('confirmText').textContent=pendingDelete.thing+' kalıcı olarak silinecek. Bu işlem geri alınamaz.';
    $('confirmYes').textContent='Evet, sil';
    return;
  }
  const action=pendingDelete.action;
  pendingDelete=null;
  hideDialog($('confirmDialog'));
  const snapshot=JSON.stringify(data);
  try{
    action();
    if(!save())data=JSON.parse(snapshot);
    else toast('Kayıt silindi.');
    renderAll();
  }catch(err){
    data=JSON.parse(snapshot);
    renderAll();
    toast('Silme işlemi tamamlanamadı.');
  }
});
function addCourse(){const v=$('newCourse').value.trim().replace(/\s+/g,' ');if(!v){toast('Ders adı girin.');return}if(data.courses.some(x=>x.name.toLocaleLowerCase('tr-TR')===v.toLocaleLowerCase('tr-TR'))){toast('Bu ders zaten eklenmiş.');return}data.courses.push({id:id(),name:v});if(save()){$('newCourse').value='';renderCourses();toast('Ders eklendi.')}}
function renderCourses(){$('courseCount').textContent=data.courses.length+' ders';const dst=$('courseList');dst.innerHTML='';if(!data.courses.length){dst.innerHTML='<div class="empty"><strong>Henüz ders eklenmedi.</strong>Yukarıdan ilk dersinizi ekleyin.</div>';return}data.courses.forEach(c=>{const count=data.entries.filter(e=>e.courseId===c.id).length;const elt=document.createElement('div');elt.className='item';elt.innerHTML='<button class="open" type="button"><div class="item-name">'+escapeText(c.name)+'</div><div class="item-desc">'+count+' test kaydı • Aç ›</div></button><button class="btn danger small" type="button">Ders Sil</button>';elt.querySelector('.open').onclick=()=>openCourse(c.id);elt.querySelector('.danger').onclick=()=>doubleDelete('“'+c.name+'” dersi ve dersin '+count+' test kaydı',()=>{data.entries=data.entries.filter(e=>e.courseId!==c.id);data.courses=data.courses.filter(x=>x.id!==c.id);if(selectedCourse===c.id)backCourses()});dst.appendChild(elt)})}
function openCourse(courseId){selectedCourse=courseId;$('courseIndex').hidden=true;$('courseDetail').hidden=false;$('entryDate').value=nowInput();['entryQuestions','entryCorrect','entryWrong'].forEach(i=>$(i).value='');renderEntries();window.scrollTo(0,0)}
function backCourses(){selectedCourse=null;$('courseDetail').hidden=true;$('courseIndex').hidden=false;renderCourses();window.scrollTo(0,0)}
function saveEntry(){if(!selectedCourse)return;const d=$('entryDate').value;const fields=['entryQuestions','entryCorrect','entryWrong'];const nums=fields.map(x=>Number($(x).value));if(!d||isNaN(new Date(d).getTime())){toast('Geçerli tarih ve saat seçin.');return}if(fields.some((x,i)=>$(x).value.trim()===''||!Number.isSafeInteger(nums[i]))){toast('Soru, doğru ve yanlış için tam sayı girin.');return}const [questions,correct,wrong]=nums;if(questions<=0||correct<0||wrong<0||correct+wrong>questions){toast('Soru adedi pozitif olmalı; doğru + yanlış, soru adedini aşamaz.');return}const entry={id:id(),courseId:selectedCourse,date:d,questions,correct,wrong};data.entries.push(entry);if(save()){fields.forEach(x=>$(x).value='');renderAll();toast('Test kaydı eklendi.')}}
function renderEntries(){const c=data.courses.find(c=>c.id===selectedCourse);if(!c){backCourses();return}$('detailCourseName').textContent=c.name;const entries=data.entries.filter(e=>e.courseId===selectedCourse).sort((a,b)=>b.date.localeCompare(a.date));$('entryCount').textContent=entries.length+' kayıt';const dst=$('entryList');dst.innerHTML='';if(!entries.length){dst.innerHTML='<div class="empty"><strong>Henüz test kaydı yok.</strong>Yukarıdan ilk sonucunuzu girin.</div>';return}entries.forEach(e=>{const div=document.createElement('div');div.className='record';div.innerHTML='<div class="recordtop"><div><div class="item-name">'+escapeText(c.name)+'</div><time>'+escapeText(dateText(e.date))+'</time></div><button class="btn danger small remove">Sil</button></div><div class="pills"><span class="pill">Soru: '+e.questions+'</span><span class="pill good">Doğru: '+e.correct+'</span><span class="pill bad">Yanlış: '+e.wrong+'</span></div><div class="row"><button class="btn small share">↗ WhatsApp Paylaş</button></div>';div.querySelector('.remove').onclick=()=>doubleDelete(dateText(e.date)+' tarihli test kaydı',()=>{data.entries=data.entries.filter(x=>x.id!==e.id)});div.querySelector('.share').onclick=()=>shareEntry(e);dst.appendChild(div)})}
function renderPhones(){const dst=$('phoneList');dst.innerHTML='';if(!data.phones.length){dst.innerHTML='<div class="empty"><strong>Telefon eklenmemiş.</strong>Paylaşım yapmak için bir telefon numarası girin.</div>'}data.phones.forEach(phone=>{const div=document.createElement('div');div.className='item';div.innerHTML='<div class="item-info"><div class="item-name">'+escapeText(phone)+'</div></div><button type="button" class="btn danger small">Sil</button>';div.querySelector('button').onclick=()=>doubleDelete(phone+' numaralı telefon',()=>{data.phones=data.phones.filter(x=>x!==phone)});dst.appendChild(div)});
}
function addPhone(){const v=$('newPhone').value.replace(/[\s()-]/g,'');if(!/^05\d{9}$/.test(v)){toast('Telefon 05 ile başlamalı ve 11 rakam olmalı.');return}if(data.phones.includes(v)){toast('Telefon zaten kayıtlı.');return}data.phones.push(v);if(save()){$('newPhone').value='';renderPhones();toast('Telefon eklendi.')}}
function startShareAll(text){
  if(!data.phones.length){toast('Önce Ayarlar kısmından telefon ekleyin.');switchView('settings');return}
  if(shareQueue){toast('Önce mevcut paylaşım sırasını tamamlayın veya kapatın.');return}
  shareQueue={phones:data.phones.slice(),text:text,mode:null};
  shareIndex=-1;
  $('shareProgress').textContent=shareQueue.phones.length+' kayıtlı numara için paylaşım yöntemi seçin.';
  $('shareModeChoices').hidden=false;
  $('shareMethodHelp').textContent='WhatsApp Uygulaması veya WhatsApp Web’i seçin. Web sürümünü kullanmak için web.whatsapp.com oturumu gerekir.';
  $('shareNext').hidden=true;
  $('shareDirectLink').hidden=true;
  showDialog($('shareDialog'));
}
function chooseShareMode(mode){
  if(!shareQueue||shareQueue.mode)return;
  shareQueue.mode=mode;
  $('shareModeChoices').hidden=true;
  $('shareNext').hidden=false;
  $('shareMethodHelp').textContent=mode==='web'
    ? 'WhatsApp Web her numarayı tarayıcıda açar. Bilgisayarda WhatsApp Web oturumunuzun açık olduğundan emin olun. Her mesajı kendiniz gönderin ve buraya geri dönün.'
    : 'Her numara WhatsApp uygulamasında sırayla açılır. Mesajı kendiniz gönderip buraya geri dönün.';
  openNextRecipient();
}
function openNextRecipient(){
  if(!shareQueue||!shareQueue.mode)return;
  const next=shareIndex+1;
  if(next>=shareQueue.phones.length){
    closeShare();
    toast('Tüm numaralar için paylaşım bağlantıları açıldı. Gönderimleri WhatsApp’tan kontrol edin.');
    return;
  }
  shareIndex=next;
  const phone=shareQueue.phones[shareIndex];
  $('shareDirectLink').hidden=true;
  $('shareProgress').textContent='Numara '+(shareIndex+1)+' / '+shareQueue.phones.length+': '+phone;
  $('shareNext').textContent=shareIndex===shareQueue.phones.length-1?'Tamamla':'Sonraki numarayı aç';
  openWhatsApp(phone,shareQueue.text,shareQueue.mode);
}
function closeShare(){if($('shareDialog').open)hideDialog($('shareDialog'));shareQueue=null;shareIndex=-1}
function shareEntry(e){
  const text=['Test Soru Takip','Ders: '+courseName(e.courseId),'Tarih/Saat: '+dateText(e.date),'Soru adedi: '+e.questions,'Doğru: '+e.correct,'Yanlış: '+e.wrong].join('\n');
  startShareAll(text);
}
function openWhatsApp(phone,text,mode){
  const international='90'+phone.substring(1);
  const encoded=encodeURIComponent(text);
  const url=mode==='web'
    ? 'https://web.whatsapp.com/send?phone='+international+'&text='+encoded
    : 'https://wa.me/'+international+'?text='+encoded;
  const direct=$('shareDirectLink');
  direct.href=url;
  direct.textContent=mode==='web'?'↗ WhatsApp Web’de aç (engellendiyse dokunun)':'↗ WhatsApp’ta aç (engellendiyse dokunun)';
  direct.hidden=false;
  // Web seçildiğinde yerel uygulama köprülerini çağırma.
  if(mode!=='web'){
    try{if(window.webkit&&window.webkit.messageHandlers&&window.webkit.messageHandlers.whatsapp){window.webkit.messageHandlers.whatsapp.postMessage({phone:international,text:text});return}}catch(e){}
    try{if(window.AndroidBridge&&typeof AndroidBridge.shareWhatsApp==='function'){AndroidBridge.shareWhatsApp(international,text);return}}catch(e){}
  }
  // Tarayıcı yeni pencereyi engellerse aynı bağlantı kullanıcı tıklamasıyla açılır.
  try{window.open(url,'_blank','noopener,noreferrer');}catch(e){}
}
function filteredEntries(){const f=$('statsFilter').value;return data.entries.filter(e=>!f||e.courseId===f)}
function daily(entries){const m=new Map();entries.forEach(e=>{const k=dateKey(e.date);if(!m.has(k))m.set(k,{date:k,correct:0,wrong:0,questions:0});const d=m.get(k);d.correct+=e.correct;d.wrong+=e.wrong;d.questions+=e.questions});return Array.from(m.values()).sort((a,b)=>a.date.localeCompare(b.date))}
function renderChart(target,days,key,color){
  const node=$(target);
  if(!days.length){node.innerHTML='<div class="empty">Grafik için test kaydı ekleyin.</div>';return}
  // Genişlik ekrandan hesaplanır; tarih sayısı arttığında SVG genişletilmez.
  // Çok sayıda günde yalnızca bazı tarih etiketleri yazılır, bütün sütunlar korunur.
  const width=node.clientWidth||node.parentElement.clientWidth||300;
  const w=Math.max(225,Math.round(width-4)),h=126,left=27,right=5,top=14,bottom=27;
  const plotW=w-left-right,plotH=h-top-bottom;
  const max=Math.max(1,...days.map(d=>d[key]));
  const spacing=plotW/days.length;
  const labelInterval=Math.max(1,Math.ceil(days.length/5));
  const valVisible=days.length<=7&&spacing>=28;
  let svg='<svg role="img" aria-label="'+(key==='correct'?'Doğru':'Yanlış')+' sayıları tarih grafiği" viewBox="0 0 '+w+' '+h+'" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">';
  for(let i=0;i<=2;i++){
    const v=Math.round(max*i/2),y=top+plotH-i*plotH/2;
    svg+='<line x1="'+left+'" y1="'+y+'" x2="'+(w-right)+'" y2="'+y+'" stroke="#e4edf5"/>';
    svg+='<text x="'+(left-3)+'" y="'+(y+3)+'" text-anchor="end" font-size="9" fill="#617b91">'+v+'</text>';
  }
  days.forEach((d,i)=>{
    const x=left+(i+.5)*spacing;
    const height=d[key]/max*(plotH-8);
    const bw=Math.max(1,Math.min(22,spacing*.62));
    const y=top+plotH-height;
    svg+='<rect x="'+(x-bw/2).toFixed(2)+'" y="'+y.toFixed(2)+'" width="'+bw.toFixed(2)+'" height="'+height.toFixed(2)+'" rx="'+Math.min(3,bw/4)+'" fill="'+color+'"><title>'+d.date+': '+d[key]+' adet</title></rect>';
    if(valVisible){svg+='<text x="'+x+'" y="'+Math.max(9,y-3)+'" text-anchor="middle" font-size="9" font-weight="bold" fill="'+color+'">'+d[key]+'</text>'}
    if(i%labelInterval===0||i===days.length-1){
      // Birbirine çok yakın tarih metinlerini çizme.
      if(i===days.length-1&&i>0&&i%labelInterval!==0&&spacing*(i-Math.floor(i/labelInterval)*labelInterval)<29)return;
      svg+='<text x="'+x+'" y="'+(h-7)+'" text-anchor="middle" font-size="9" fill="#617b91">'+shortDate(d.date).slice(0,5)+'</text>';
    }
  });
  svg+='</svg>';
  node.innerHTML=svg+'<div class="hint">Tarihe göre adet · '+days.length+' tarih</div>';
}
function renderStats(){const filter=$('statsFilter'),prior=filter.value;filter.innerHTML='<option value="">Tüm dersler</option>';data.courses.forEach(c=>{const o=document.createElement('option');o.value=c.id;o.textContent=c.name;filter.appendChild(o)});if(data.courses.some(c=>c.id===prior))filter.value=prior;const rows=filteredEntries(),days=daily(rows);$('totalQuestions').textContent=rows.reduce((x,e)=>x+e.questions,0);$('totalCorrect').textContent=rows.reduce((x,e)=>x+e.correct,0);$('totalWrong').textContent=rows.reduce((x,e)=>x+e.wrong,0);const q=+($('totalQuestions').textContent),r=+($('totalCorrect').textContent);$('accuracy').textContent=q?'Doğru oranı: %'+(100*r/q).toFixed(1):'Henüz kayıt yok.';renderChart('correctGraph',days,'correct','#1a9a70');renderChart('wrongGraph',days,'wrong','#e06a62');$('statsTable').innerHTML=days.length?'<table><thead><tr><th>Tarih</th><th>Soru</th><th>Doğru</th><th>Yanlış</th></tr></thead><tbody>'+days.map(d=>'<tr><td>'+shortDate(d.date)+'</td><td>'+d.questions+'</td><td>'+d.correct+'</td><td>'+d.wrong+'</td></tr>').join('')+'</tbody></table>':'<div class="empty">Gösterilecek sonuç bulunmuyor.</div>'}
function shareStats(){const rows=filteredEntries(),days=daily(rows);if(!days.length){toast('Paylaşmak için önce test kaydı ekleyin.');return}const name=$('statsFilter').value?courseName($('statsFilter').value):'Tüm dersler';const total=key=>rows.reduce((s,e)=>s+e[key],0),q=total('questions'),c=total('correct'),w=total('wrong');let msg=['TEST SORU TAKİP — İSTATİSTİK','Ders: '+name,'Toplam soru: '+q,'Toplam doğru: '+c,'Toplam yanlış: '+w,'Doğru oranı: %'+(100*c/q).toFixed(1),'','DOĞRU GRAFİĞİ (Tarih | Adet)'];const cm=Math.max(1,...days.map(x=>x.correct));days.forEach(d=>msg.push(shortDate(d.date)+' | '+d.correct+' '+ '▇'.repeat(Math.round(d.correct/cm*12))));msg.push('','YANLIŞ GRAFİĞİ (Tarih | Adet)');const wm=Math.max(1,...days.map(x=>x.wrong));days.forEach(d=>msg.push(shortDate(d.date)+' | '+d.wrong+' '+'▇'.repeat(Math.round(d.wrong/wm*12))));msg.push('','Test Soru Takip uygulamasından paylaşıldı.');startShareAll(msg.join('\n'))}
function downloadBackup(){
  try{
    const payload={format:'TestSoruTakip',version:1,exportedAt:new Date().toISOString(),
      courses:data.courses,entries:data.entries,phones:data.phones};
    const blob=new Blob([JSON.stringify(payload,null,2)],{type:'application/json;charset=utf-8'});
    const url=URL.createObjectURL(blob),link=document.createElement('a');
    link.href=url;link.download='Test_Soru_Takip_Yedek_'+new Date().toISOString().slice(0,10)+'.json';
    document.body.appendChild(link);link.click();link.remove();
    setTimeout(()=>URL.revokeObjectURL(url),1500);
    toast('Yedek dosyası indirildi.');
  }catch(e){toast('Yedek indirilemedi.');}
}
function validateBackup(value){
  const x=value&&value.format==='TestSoruTakip'?value:value;
  if(!validData(x))return false;
  if(x.courses.length>2000||x.entries.length>50000||x.phones.length>1000)return false;
  if(!x.courses.every(c=>c&&typeof c.id==='string'&&typeof c.name==='string'))return false;
  if(!x.entries.every(e=>e&&typeof e.id==='string'&&typeof e.courseId==='string'
      &&typeof e.date==='string'&&Number.isSafeInteger(e.questions)&&Number.isSafeInteger(e.correct)
      &&Number.isSafeInteger(e.wrong)&&e.questions>0&&e.correct>=0&&e.wrong>=0
      &&e.correct+e.wrong<=e.questions))return false;
  if(!x.phones.every(p=>typeof p==='string'&&/^05\d{9}$/.test(p)))return false;
  return true;
}
function loadBackupFile(){
  const input=$('backupFile'),file=input.files&&input.files[0];
  input.value='';
  if(!file)return;
  if(file.size>4*1024*1024){toast('Yedek dosyası çok büyük.');return;}
  const reader=new FileReader();
  reader.onerror=()=>toast('Yedek dosyası okunamadı.');
  reader.onload=()=>{
    try{
      const incoming=JSON.parse(reader.result);
      if(!validateBackup(incoming)){toast('Yedek biçimi geçersiz.');return;}
      if(!window.confirm('Mevcut ders, test ve telefon kayıtları yedekteki kayıtlarla değiştirilsin mi?'))return;
      const previous=JSON.stringify(data);
      const next={courses:incoming.courses,entries:incoming.entries,phones:incoming.phones};
      data=next;
      if(!save()){data=JSON.parse(previous);renderAll();toast('Yedek kaydedilemedi.');return;}
      selectedCourse=null;$('courseDetail').hidden=true;$('courseIndex').hidden=false;
      renderAll();toast('Yedek başarıyla yüklendi.');
    }catch(e){toast('Yedek dosyası açılamadı.');}
  };
  reader.readAsText(file,'UTF-8');
}
function switchView(view){active=view;['courses','stats','settings'].forEach(v=>{$(v+'View').classList.toggle('active',view===v);$('nav'+v[0].toUpperCase()+v.slice(1)).classList.toggle('active',view===v)});if(view==='stats')renderStats();if(view==='settings')renderPhones();window.scrollTo(0,0)}
function renderAll(){renderCourses();if(selectedCourse)renderEntries();renderPhones();renderStats()}
try{
  $('addCourse').onclick=addCourse;
  $('newCourse').addEventListener('keydown',e=>{if(e.key==='Enter')addCourse()});
  $('backCourses').onclick=backCourses;
  $('saveEntry').onclick=saveEntry;
  $('exportBackup').onclick=downloadBackup;
  $('importBackup').onclick=()=>$('backupFile').click();
  $('backupFile').addEventListener('change',loadBackupFile);
  $('addPhone').onclick=addPhone;
  $('newPhone').addEventListener('keydown',e=>{if(e.key==='Enter')addPhone()});
  $('statsFilter').addEventListener('change',renderStats);
  $('shareStats').onclick=shareStats;
  $('shareCancel').onclick=closeShare;
  $('shareNext').onclick=openNextRecipient;
  $('shareAppMode').onclick=()=>chooseShareMode('app');
  $('shareWebMode').onclick=()=>chooseShareMode('web');
  $('shareDialog').addEventListener('close',()=>{shareQueue=null;shareIndex=-1});
  window.addEventListener('tst-storage-error',()=>toast('Kayıt tarayıcıya yazılamadı. Depolamayı kontrol edin.'));
  window.addEventListener('resize',()=>{if(active==='stats')renderStats();});
  document.querySelectorAll('nav button').forEach(btn=>btn.onclick=()=>switchView(btn.dataset.view));
  $('entryDate').value=nowInput();
  // Chrome for iOS (CriOS) da dahil olmak üzere tüm web tarayıcıları için
  // doğrudan DOM olayları kullanılır; WKWebView köprüsü zorunlu değildir.
  renderAll();
  refreshRuntimeNotice();
}catch(e){
  $('appRuntime').hidden=false;
  $('appRuntime').textContent='Uygulama başlatılamadı: '+String(e&&e.message||e)+'. Tarayıcıyı yenileyerek tekrar deneyin.';
}
})();
</script></body></html>