Öğrenciler - Soru Hazırlama Asistanı

Ders çalışırken, sınava hazırlanırken veya öğrendiğimiz bilgileri tekrar ederken karşılaştığımız en önemli ihtiyaçlardan biri, mevcut soru ve cevapları düzenli bir teste dönüştürebilmektir. Özellikle PDF, Word, Excel veya fotoğraf biçimindeki soruları tek tek yeniden yazmak zaman alıcı olabilir.

Bu ihtiyaca yönelik geliştirilen Soru Hazırla, soru ve cevapların dijital ortama aktarılmasını, eşleştirilmesini, kaydedilmesini ve tekrar tekrar test olarak çözülmesini sağlayan dinamik bir HTML uygulamasıdır.

Bilgisayar, tablet ve telefonların internet tarayıcılarında kullanılabilecek şekilde tasarlanan uygulama, sade arayüzü sayesinde teknik bilgi gerektirmeden test hazırlanmasına yardımcı olur.

Soru Hazırla Nedir?

Soru Hazırla; farklı dosya türlerinde veya fotoğraflarda bulunan çoktan seçmeli soruları okuyarak test düzenine dönüştüren, cevap anahtarını sorularla eşleştiren ve oluşturulan testleri yeniden kullanılmak üzere kaydedebilen bir çalışma aracıdır.

Uygulamanın temel amacı, soru hazırlama süresini azaltmak ve mevcut soru kaynaklarını tekrar kullanılabilir testlere dönüştürmektir.

Öğrenciler, öğretmenler, sınavlara hazırlanan kişiler ve bilgilerini ölçmek isteyen herkes tarafından kullanılabilir.

Soru Hazırla Ne İşe Yarar?

Uygulama, farklı kaynaklarda bulunan soruları tek bir test ortamında bir araya getirerek kullanıcının çalışma sürecini kolaylaştırır.

Başlıca işlevleri şunlardır:

  • PDF, TXT, Word (DOCX) ve Excel (XLSX) dosyalarından soru ve cevap metinlerini okuma.
  • Fotoğraflardan soru ve cevap anahtarı ekleme.
  • Sorularla cevap anahtarındaki doğru şıkları otomatik eşleştirme.
  • Otomatik eşleştirilemeyen cevapları manuel olarak tamamlama.
  • Eksik, boş, tekrarlanan veya hatalı okunan şıkları bulunan soruları ayıklama.
  • Oluşturulan testleri isim vererek kaydetme.
  • Kayıtlı testleri tekrar açarak sınırsız sayıda yeniden çözme.
  • Test sonunda doğru, yanlış ve boş cevapları değerlendirme.
  • Cevapları gerektiğinde gösterme veya gizleme.

Böylece mevcut soru kaynakları yalnızca okunacak belgeler olmaktan çıkarak etkileşimli birer çalışma materyaline dönüşür.

Soru Hazırla Nasıl Kullanılır?

1. Uygulamayı açma

Soru Hazırla, HTML dosyasının Chrome, Edge veya benzeri güncel bir internet tarayıcısında açılmasıyla çalıştırılır.

Açılış ekranında soru ve cevap yükleme alanları bulunur. Daha önce kaydedilmiş testler varsa uygulamanın başlığının hemen altında Kayıtlı Testler bölümünde listelenir.

Yeni bir çalışma başlatmak için Soru Hazırlama Asistanı başlığına tıklanabilir. Bu işlem ekrandaki mevcut çalışma durumunu temizleyerek başlangıç görünümüne döndürür; kayıtlı testleri silmez.

2. Soru dosyalarını veya fotoğraflarını ekleme

Uygulamada soruların bulunduğu dosyalar veya fotoğraflar ilgili alanlardan seçilir.

Örneğin, bir ders kitabındaki çoktan seçmeli soruların fotoğrafı çekilmişse bu görseller Soru Fotoğrafları alanından eklenebilir.

Sorular PDF, Word, Excel veya TXT biçiminde hazırlanmışsa Soru Dosyası seçeneği kullanılabilir.

Aynı şekilde soruların cevap anahtarı da dosya veya fotoğraf olarak ayrıca eklenebilir.

3. Soruları okuma ve cevapları eşleştirme

Soru ve cevap kaynakları eklendikten sonra Metinleri Oku ve Eşleştir düğmesine basılır.

Uygulama metinleri okumaya ve cevap anahtarındaki harfleri ilgili sorularla eşleştirmeye çalışır.

Örneğin, cevap anahtarında 1. sorunun doğru cevabı B olarak belirtilmişse ve soruda B şıkkı doğru biçimde tanınmışsa eşleştirme otomatik yapılır.

Bu işlemde otomatik eşleşen cevaplar korunur. Eşleşmeyen sorular ise ayrıca gösterilir.

4. Eksik cevapları manuel tamamlama

Otomatik eşleşmeyen cevaplar için kullanıcıdan doğru şık harfini girmesi istenir.

Manuel eşleştirme sırasında yalnızca ilgili soruda bulunan A, B, C, D veya E harflerinden biri kullanılabilir.

Bu işlem yeni bir şık oluşturmaz; mevcut seçeneklerden hangisinin doğru olduğunu belirtir.

Dolayısıyla yanlışlıkla ilgisiz metin yazılması veya geçersiz bir şık harfiyle eşleştirme yapılması önlenir.

5. Eşleşme sonuçlarını kontrol etme

Eşleştirme tamamlandığında uygulama otomatik ve manuel eşleşmeleri birlikte gösteren bir sonuç özeti sunar.

Örneğin:

Eşleşme Tamamlandı

  • Otomatik eşleşen: 18 soru
  • Manuel eşleştirilen: 2 soru
  • Toplam eşleşen: 20 soru
  • Pas geçilen: 3 soru

Bu sayılar örnek amaçlıdır. Gerçek sonuçlar yüklenen dosyaların içeriğine göre değişir.

Uygulama, eksik veya hatalı okunan seçenekleri bulunan soruları teste dahil etmez. Pas geçilen soruların numaraları ve gerekçeleri ayrıca belirtilir.

Önemli: Otomatik eşleşmenin tamamlanmış olması, fotoğraf veya belgelerin hatasız okunduğu anlamına gelmez. Kullanıcının eşleşme sonuçlarını kaynak cevap anahtarıyla karşılaştırması önerilir.

6. Testi kaydetme

Geçerli soruların cevap eşleştirmeleri tamamlandıktan sonra test kayıt alanı kullanıma açılır.

Kullanıcı oluşturduğu teste bir isim verir ve Kaydet düğmesine basar.

Örneğin:

İş Sağlığı ve Güvenliği – Deneme Testi 1

Kaydedilen test, aynı tarayıcı ortamındaki uygulama verilerinde saklanır. Daha sonra soru ve cevap kaynaklarının yeniden yüklenmesine gerek kalmadan açılabilir.

7. Testi başlatma

Sorular hazır olduğunda Test Başla düğmesine basılır.

Test süresince sorular ve cevap seçenekleri ekranda görüntülenir. Kullanıcı her soru için yalnızca bir cevap işaretleyebilir.

Test çözme sırasında:

  • İşaretlenen şık değiştirilebilir.
  • Farklı bir şık seçildiğinde önceki seçim kaldırılır.
  • İşaretli şıkka yeniden tıklanarak cevap boş bırakılabilir.
  • Seçimi Kaldır düğmesiyle mevcut işaret temizlenebilir.
  • İlerleme göstergesi cevaplanan soru sayısına göre güncellenir.

Bu özellikler sayesinde kullanıcı bir sınavda olduğu gibi sorular arasında kararını değiştirebilir veya bazı soruları cevapsız bırakabilir.

8. Testi bitirme ve sonuçları inceleme

Test tamamlandığında Test Bitir düğmesine basılır.

Uygulama kullanıcının cevaplarını değerlendirerek doğru, yanlış ve boş cevap sayıları ile başarı oranını hesaplar.

Cevapları Göster düğmesiyle doğru cevaplar ve kullanıcının seçimleri incelenebilir.

İstenirse Cevapları Gizle düğmesiyle cevap gösterimi kapatılabilir.

Bu işlem, kullanıcıya yalnızca kaç soruyu doğru yaptığını değil, hangi sorularda hata yaptığını da görme imkânı sağlar.

9. Kayıtlı testi yeniden çözme

Soru Hazırla'nın en kullanışlı özelliklerinden biri, oluşturulmuş bir testin tekrar tekrar çözülebilmesidir.

Kaydedilmiş testler uygulama başlığının altında görüntülenir. Kullanıcı istediği testi seçtiğinde, sorular ve cevap eşleşmeleri hazır olarak açılır.

Test Başla düğmesiyle yeni bir deneme başlatılabilir.

Her yeni denemede önceki işaretlemeler sıfırlanır. Böylece aynı sorularla yeniden çalışmak ve öğrenme düzeyini farklı zamanlarda ölçmek mümkün olur.

Kayıtlı bir test artık gerekli değilse silinebilir. Son kayıt da silindiğinde uygulama başlangıç ekranına döner.

Soru Hazırla'nın Sağladığı Faydalar

Zaman tasarrufu

Mevcut soru kaynaklarının yeniden yazılmasına duyulan ihtiyacı azaltır. Özellikle çok sayıda sorudan oluşan belgelerle çalışan kişiler için hazırlık süresini kısaltabilir.

Düzenli çalışma ortamı

Farklı belgelerde veya fotoğraflarda bulunan soru ve cevapların bir test düzeni içerisinde kullanılmasını sağlar.

Tekrar ederek öğrenme

Aynı testin tekrar tekrar çözülebilmesi, öğrenilen bilgilerin pekiştirilmesine ve eksik konuların fark edilmesine yardımcı olur.

Hataları fark etme

Test sonuçlarında doğru, yanlış ve boş cevapların ayrı ayrı gösterilmesi, kullanıcının hangi sorular üzerinde yeniden çalışması gerektiğini belirlemesini kolaylaştırır.

Esnek kullanım

HTML tabanlı olması sayesinde uygulama kurulum gerektirmeden güncel bir internet tarayıcısında açılabilir. Ekran düzeni farklı cihaz boyutlarına uyum sağlayacak biçimde hazırlanmıştır.

Kullanımda Dikkat Edilmesi Gerekenler

Soru Hazırla'nın fotoğraflardan metin okuma özelliği, görüntüdeki yazıların açıklığına ve fotoğraf kalitesine bağlıdır.

Düşük çözünürlüklü, eğik, bulanık veya karmaşık sayfa düzenine sahip görsellerde bazı soru ve seçenekler doğru okunamayabilir.

Daha iyi sonuç almak için soru numaralarının ve A, B, C, D, E şıklarının açık biçimde göründüğü, düzgün ışıklandırılmış ve mümkünse düz açıdan çekilmiş fotoğraflar tercih edilmelidir.

Ayrıca şu hususlar dikkate alınmalıdır:

  • Eksik veya yanlış tanınan soruların pas geçilmesi, hatalı sorularla test oluşturulmasını azaltmaya yönelik bir önlemdir.
  • Kaydedilen testler tarayıcının yerel verilerinde saklanır. Tarayıcı verilerinin temizlenmesi veya farklı bir tarayıcı kullanılması kayıtların erişilebilirliğini etkileyebilir.
  • PDF, Word, Excel ve fotoğraftan metin okuma için kullanılan bazı yazılım bileşenleri internet üzerinden yüklendiğinden, bu özelliklerin çalışması için internet bağlantısı gerekebilir.
  • Soru okuma ve otomatik cevap eşleştirme işlemlerinin sonuçları kullanıcı tarafından kontrol edilmelidir.

Kimler Kullanabilir?

Soru Hazırla; öğrencilerin ders tekrarı yapması, öğretmenlerin mevcut soru kaynaklarından çalışma testleri oluşturması, kurum içi eğitimlerde bilgi ölçme çalışmaları yürütülmesi ve mesleki sınavlara hazırlık amacıyla kullanılabilir.

Özellikle aynı soru havuzunu farklı zamanlarda yeniden çözmek isteyen kullanıcılar için pratik bir yardımcı araçtır.

Sonuç

Soru Hazırla, mevcut soru ve cevap kaynaklarını tekrar kullanılabilir dijital testlere dönüştürmeyi amaçlayan pratik bir öğrenme ve değerlendirme aracıdır.

Dosyalardan ve fotoğraflardan soru ekleme, otomatik ve manuel cevap eşleştirme, hatalı soruları ayıklama, testleri kaydetme ve tekrar çözme özellikleriyle çalışma sürecini daha düzenli hâle getirir.

Uygulamanın temel yaklaşımı, soruları yalnızca dijital ortama taşımak değil; onları yeniden kullanılabilen, cevapları değerlendirilebilen ve öğrenme sürecine katkı sağlayan testlere dönüştürmektir.

Soru Hazırla ile mevcut sorularınızı değerlendirin, kendi testlerinizi oluşturun ve bilgilerinizi tekrar ederek pekiştirin.

Web Uygulaması Görünüm:


Aşağıdaki HTML kodu kopyalayıp not defterine yapıştırıp "Soru Hazırlama Asistanı.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. "Soru Hazırla.apk" indir.


HTML KOD
<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#FEF9C3"><meta name="color-scheme" content="light"><title>Soru Hazırlama Asistanı</title>
<style>
:root{--yellow:#fef9c3;--ink:#1f2937;--muted:#64748b;--line:#e2e8f0;--green:#dcfce7;--greenb:#86efac;--red:#ffe4e6;--redb:#fda4af}*{box-sizing:border-box}body{margin:0;background:#f8fafc;color:var(--ink);font:15px "Segoe UI",Arial,sans-serif}.head{background:var(--yellow);padding:16px;display:flex;align-items:center;gap:12px;box-shadow:0 2px 8px #0001}.appicon{width:62px;height:62px;background:var(--yellow);border:0;border-radius:14px;display:grid;place-items:center;font-size:19px;font-weight:900;letter-spacing:-.8px;white-space:nowrap;color:#111827;flex-shrink:0}.head h1{font-size:20px;margin:0}.head[role=button]{cursor:pointer}.head[role=button]:focus-visible{outline:3px solid #0284c7;outline-offset:-3px}.quick-card{border-color:#fde68a}.quick-saved-row{display:flex;gap:7px;align-items:center;border-top:1px solid #e5e7eb;padding:8px 0}.quick-saved-row:first-child{border-top:0}.quick-saved-row .open-saved{flex:1;text-align:left;justify-content:flex-start;display:flex;flex-direction:column;align-items:flex-start;min-width:0;overflow-wrap:anywhere}.quick-saved-row .quick-delete{flex:0 0 auto}.quick-saved-row .open-saved.active{background:#dcfce7;border-color:#86efac}.quick-card h2{margin-bottom:7px}.head p{margin:4px 0 0;font-size:12px}.wrap{width:100%;max-width:none;margin:0;padding:14px}.card{background:white;border:1px solid var(--line);border-radius:12px;padding:15px;margin-bottom:13px;box-shadow:0 3px 12px #0f172a08}.card h2{font-size:17px;margin:0 0 12px}.row{display:flex;gap:9px;flex-wrap:wrap;align-items:center}.field{display:flex;flex-direction:column;gap:5px;flex:1;min-width:180px}.field label{font-size:12px;font-weight:700;color:#475569}input[type=text],select{width:100%;border:1px solid #cbd5e1;border-radius:8px;padding:11px;font:inherit;background:white}.btn,.pick{border:1px solid #cbd5e1;background:white;color:#1f2937;border-radius:8px;padding:10px 13px;font:600 14px inherit;display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;min-height:42px}.btn.primary{background:#1f2937;color:white;border-color:#1f2937}.btn.yellow{background:#fef08a;border-color:#eab308}.btn.start-test{background:#dcfce7;border-color:#86efac;color:#14532d}.btn.finish-test{background:#fef9c3;border-color:#fde047;color:#713f12}.btn.answers-test{background:#dbeafe;border-color:#93c5fd;color:#1e3a8a}.letter-input{width:68px!important;min-width:68px;text-align:center;font-size:19px!important;font-weight:800!important;text-transform:uppercase}.matching-row{padding:11px 0;border-bottom:1px solid #e2e8f0}.matching-row:last-child{border-bottom:0}.matching-fields{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:8px}.matching-fields .btn{min-width:94px}.status.success{background:#f0fdf4;border:1px solid #86efac}.missing-choice{padding:9px;margin-top:8px;border:1px dashed #f59e0b;border-radius:8px;background:#fffbeb}.btn:disabled{opacity:.45;cursor:not-allowed}.hint{font-size:12px;color:var(--muted);line-height:1.5}.hidden{display:none!important}.pick-label{position:relative;overflow:hidden}.native-picker{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;z-index:2}.status{padding:10px;border-radius:8px;background:#f1f5f9;font-size:13px;margin-top:10px;white-space:pre-wrap}.saved{display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--line);padding:10px 0}.saved:last-child{border-bottom:0}.saved .name{flex:1;min-width:0;overflow-wrap:anywhere}.q{border:1px solid var(--line);border-radius:10px;padding:13px;margin:12px 0;background:white}.qtitle{display:flex;gap:9px;align-items:flex-start;font-weight:700;line-height:1.5}.num{background:#fef08a;border-radius:50%;min-width:29px;height:29px;display:grid;place-items:center}.opts{display:grid;gap:7px;margin:12px 0 0 38px}.opt{display:flex;gap:8px;align-items:flex-start;padding:10px;border:1px solid #dbe3ea;border-radius:8px;line-height:1.4}.opt input{margin-top:3px}.opt.chosen{background:#eff6ff;border-color:#60a5fa}.clear-answer-btn{background:#fff7ed;border-color:#fdba74;color:#9a3412;margin:8px 0 0 38px;padding:7px 11px;min-height:36px;font-size:12px}.opt.correct{background:var(--green);border-color:var(--greenb)}.opt.wrong{background:var(--red);border-color:var(--redb)}.answer{margin:10px 0 0 38px;background:var(--green);border:1px solid var(--greenb);padding:10px;border-radius:8px;line-height:1.5}.answer.wrong{background:var(--red);border-color:var(--redb)}.answer.unmatched{background:#fff7ed;border-color:#fdba74}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.metric{background:#f8fafc;border:1px solid var(--line);border-radius:8px;text-align:center;padding:10px}.metric b{display:block;font-size:20px}.metric span{font-size:11px;color:var(--muted)}progress{width:100%;height:12px;accent-color:#eab308}canvas{max-width:100%;height:150px}.filelist{font-size:12px;color:#475569;margin-top:7px;overflow-wrap:anywhere}@media(max-width:540px){.wrap{padding:9px}.head{padding:12px}.head h1{font-size:18px}.metrics{grid-template-columns:repeat(2,1fr)}.field{min-width:100%}.btn,.pick{flex:1}.q{padding:10px}.opts{margin-left:0}.answer{margin-left:0}}

/* EKRAN GENİŞLİĞİNE UYARLANAN TAM GENİŞLİK DÜZENİ
   Önceki maksimum 980px sınırı kaldırıldı. Masaüstü, tablet ve telefonlar. */
html { width: 100%; min-width: 0; }
body { width: 100%; min-width: 0; margin: 0; }
.head { width: 100%; min-width: 0; padding: clamp(10px, 1.3vw, 20px); }
.head > div:last-child { flex: 1 1 auto; min-width: 0; }
.head h1, .head p { overflow-wrap: anywhere; }
.wrap { width: 100%; max-width: none; min-width: 0; margin: 0; padding: clamp(8px, 1.2vw, 20px); }
.wrap > .card { width: 100%; max-width: none; min-width: 0; }
.card { min-width: 0; padding: clamp(10px, 1.15vw, 20px); }
.row, .matching-fields { max-width: 100%; min-width: 0; }
.row > *, .matching-fields > * { max-width: 100%; }
.field { min-width: min(180px, 100%); }
.field input, .field select, .matching-fields input { min-width: 0; }
.btn, .pick { white-space: normal; text-align: center; overflow-wrap: anywhere; }
.pick-label { flex: 1 1 210px; }
.test-actions > .btn { flex: 1 1 180px; min-width: 0; }
.q, .qtitle, .opt, .opt span, .status, .hint, .matching-row,
.mapping-roster, .mapping-line, .quick-card { min-width: 0; overflow-wrap: anywhere; }
.opts { grid-template-columns: minmax(0, 1fr); }
.quick-saved-row { min-width: 0; }
.quick-saved-row .open-saved { min-width: 0; }
.quick-saved-row .open-saved small { overflow-wrap: anywhere; }
img, video, canvas, svg, table { max-width: 100%; }
canvas { display: block; }
@media (max-width: 768px) {
 .wrap { padding: 9px; }
 .head { padding: 11px; }
 .card { padding: 11px; }
 .field { flex: 1 1 100%; min-width: 0; }
 .row .pick-label { flex: 1 1 calc(50% - 9px); min-width: 0; }
 .test-actions > .btn { flex: 1 1 160px; }
 .matching-fields > input[type="text"]:not(.letter-input) { flex: 1 1 150px; }
 .clear-answer-btn, .answer, .opts { margin-left: 0; }
}
@media (max-width: 440px) {
 .head h1 { font-size: 17px; }
 .appicon { width: 55px; height: 55px; font-size: 17px; }
 .quick-saved-row { flex-wrap: wrap; }
 .quick-saved-row .open-saved { flex: 1 1 calc(100% - 66px); }
 .quick-saved-row .quick-delete { flex: 0 0 auto; }
 .row .pick-label { flex: 1 1 100%; }
 .test-actions > .btn { flex: 1 1 100%; }
 .row > button:not(.quick-delete) { max-width: 100%; }
 .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
 .q { padding: 10px; }
 .opt { padding: 10px 8px; }
}

</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.8.0/mammoth.browser.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js"></script>
</head><body>
<header class="head" id="homeHeader" role="button" tabindex="0" aria-label="Yeni test için başlangıç ekranına dön"><div class="appicon">Soru</div><div><h1>Soru Hazırlama Asistanı</h1><p>Dosya ve fotoğraflardan test oluşturma</p></div></header>
<main class="wrap">
<section class="card" id="recordCard"><h2>1. Test kaydı</h2><div class="row hidden" id="recordEditRow"><div class="field"><label>Test adı</label><input id="testName" type="text" placeholder="Örn. İş Güvenliği Deneme 1"></div><button class="btn yellow" id="saveBtn">Kaydet</button></div><div class="row hidden" id="legacySavedControls" style="margin-top:10px"><div class="field"><label>Kayıtlı testler</label><select id="savedSelect"><option value="">Kayıt seçin</option></select></div><button class="btn" id="loadBtn">Aç</button><button class="btn" id="deleteBtn">Sil</button></div><p class="hint hidden" id="legacySavedHint">Kayıtlar bu cihazın uygulama verilerinde saklanır; uygulama verilerini temizlemek kayıtları da silebilir.</p><div id="savedList" class="hidden"></div></section>
<section class="card" id="uploadCard"><h2>2. Soruları ve cevapları yükle</h2><div class="row"><label class="btn yellow pick-label">📄 Soru dosyası<input class="native-picker" id="filesInput" type="file" multiple accept=".pdf,.txt,.docx,.xlsx"></label><label class="btn yellow pick-label">📄 Cevap dosyası<input class="native-picker" id="answerFilesInput" type="file" multiple accept=".pdf,.txt,.docx,.xlsx"></label><label class="btn yellow pick-label">🖼️ Soru fotoğrafları<input class="native-picker" id="questionPhotos" type="file" multiple accept="image/*"></label><label class="btn yellow pick-label">🖼️ Cevap fotoğrafları<input class="native-picker" id="answerPhotos" type="file" multiple accept="image/*"></label></div><div class="filelist" id="fileList">Henüz dosya veya fotoğraf eklenmedi.</div><div class="row" style="margin-top:12px"><button class="btn primary" id="extractBtn">Metinleri oku ve eşleştir</button><button class="btn" id="clearUploadBtn">Yüklemeleri temizle</button></div><div id="manualAnswers" class="status hidden"></div><div id="matchingSummary" class="status success hidden"><b id="matchingCount"></b> Soruların doğru şıkları tamamlandı. Testi kaydedebilir veya başlatabilirsiniz. <button type="button" class="btn" id="editMatchingBtn">Şık Harflerini Düzenle</button></div><div class="status" id="importStatus">Dosyaları yükleyin. Fotoğraflar OCR ile okunur; cevap anahtarı ayrı dosya veya fotoğraf olarak eklenebilir.</div><p class="hint">Desteklenen dosyalar: PDF, TXT, DOCX, XLSX. Birden fazla fotoğraf seçilebilir. OCR sonucu ve cevap eşleşmeleri doğruluk açısından kontrol edilmelidir.</p></section>
<section class="card"><h2>3. Test</h2><div class="row test-actions"><button class="btn start-test" id="startBtn" disabled>Test Başla</button><button class="btn finish-test" id="finishBtn" disabled>Test Bitir</button><button class="btn answers-test" id="toggleAnswersBtn" disabled>Cevapları Göster</button></div><p class="hint" id="choiceHint">Test sırasında bir şık seçin. İşaretlenen şıkka tekrar dokunursanız seçim kaldırılır; farklı bir şık seçerseniz cevap değiştirilir. İsterseniz “Seçimi Kaldır” düğmesini de kullanabilirsiniz.</p><div style="margin:12px 0"><div class="row" style="justify-content:space-between"><b id="progressLabel">İlerleme: 0 / 0</b><b id="timer">Süre: 00:00</b></div><progress id="progress" value="0" max="100"></progress></div><div id="metrics" class="metrics hidden"><div class="metric"><b id="correctCount">0</b><span>Doğru</span></div><div class="metric"><b id="wrongCount">0</b><span>Yanlış</span></div><div class="metric"><b id="rate">0%</b><span>Başarı oranı</span></div><div class="metric"><b id="unanswered">0</b><span>Cevapsız</span></div></div><canvas id="chart" class="hidden" width="640" height="180"></canvas><div id="questions"><p class="hint">Önce dosya veya fotoğraf ekleyip metinleri okuyun.</p></div></section>
</main>
<script>
'use strict';
const $=id=>document.getElementById(id);
const data={questionFiles:[],answerFiles:[],questions:[],answersVisible:false,running:false,finished:false,startAt:0,timerId:null,sourceNames:[],currentSavedName:'',editingAnswers:false,manualOptionFor:null,pendingLetters:{}};
const DB='soruAsistaniRecordsV2';
function notify(m){$('importStatus').textContent=m}
// Keep the app's light corporate palette even when Android uses a dark system theme.
try{document.documentElement.style.colorScheme='light'}catch(e){}
function readRecords(){try{return JSON.parse(localStorage.getItem(DB)||'[]')}catch{return []}}
function writeRecords(a){localStorage.setItem(DB,JSON.stringify(a));renderSaved()}
function renderSaved(){const records=readRecords();$('savedSelect').innerHTML='<option value="">Kayıt seçin</option>'+records.map((r,i)=>'<option value="'+i+'">'+esc(r.name)+'</option>').join('');$('savedList').innerHTML=records.map((r,i)=>'<div class="saved"><span class="name">'+esc(r.name)+'</span><span class="hint">'+(r.questions?.length||0)+' soru</span><button class="btn" data-open="'+i+'">Aç</button><button class="btn" data-del="'+i+'">Sil</button></div>').join('');$('savedList').querySelectorAll('[data-open]').forEach(b=>b.onclick=()=>loadRecord(+b.dataset.open));$('savedList').querySelectorAll('[data-del]').forEach(b=>b.onclick=()=>deleteRecord(+b.dataset.del))}
function esc(s=''){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
function filesText(){const all=[...data.questionFiles,...data.answerFiles];$('fileList').textContent=all.length?all.map(f=>(data.answerFiles.includes(f)?'Cevap: ':'Soru: ')+f.name).join('\n'):'Henüz dosya veya fotoğraf eklenmedi.'}

$('filesInput').onchange=e=>{data.questionFiles.push(...e.target.files);data.sourceNames.push(...[...e.target.files].map(f=>f.name));filesText();e.target.value=''};
$('answerFilesInput').onchange=e=>{data.answerFiles.push(...e.target.files);data.sourceNames.push(...[...e.target.files].map(f=>f.name));filesText();e.target.value=''};
$('questionPhotos').onchange=e=>{data.questionFiles.push(...e.target.files);data.sourceNames.push(...[...e.target.files].map(f=>f.name));filesText();e.target.value=''};
$('answerPhotos').onchange=e=>{data.answerFiles.push(...e.target.files);data.sourceNames.push(...[...e.target.files].map(f=>f.name));filesText();e.target.value=''};
$('clearUploadBtn').onclick=()=>{data.questionFiles=[];data.answerFiles=[];data.sourceNames=[];filesText();notify('Yükleme listesi temizlendi.')};
async function extractFile(file){const ext=file.name.split('.').pop().toLowerCase();if(ext==='txt')return await file.text();if(ext==='pdf'){if(!window.pdfjsLib)throw Error('PDF okuyucu yüklenemedi.');pdfjsLib.GlobalWorkerOptions.workerSrc='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';const pdf=await pdfjsLib.getDocument({data:await file.arrayBuffer()}).promise;let out='';for(let i=1;i<=pdf.numPages;i++){const p=await pdf.getPage(i);const c=await p.getTextContent();let line='',prevY=null;for(const item of c.items){const y=Math.round((item.transform&&item.transform[5])||0);if(prevY!==null&&Math.abs(y-prevY)>3){out+=line.trim()+'\n';line=''}line+=item.str+' ';prevY=y}out+=line.trim()+'\n'}return out}if(ext==='docx'){if(!window.mammoth)throw Error('Word okuyucu yüklenemedi.');return (await mammoth.extractRawText({arrayBuffer:await file.arrayBuffer()})).value}if(ext==='xlsx'){if(!window.XLSX)throw Error('Excel okuyucu yüklenemedi.');const w=XLSX.read(await file.arrayBuffer(),{type:'array'});return w.SheetNames.map(n=>XLSX.utils.sheet_to_csv(w.Sheets[n])).join('\n')}throw Error('Desteklenmeyen dosya: '+file.name)}
async function ocrFile(file){if(!window.Tesseract)throw Error('OCR bileşeni yüklenemedi. İnternet bağlantısını kontrol edin.');const result=await Tesseract.recognize(file,'tur+eng',{logger:m=>{if(m.status==='recognizing text')notify('Fotoğraf okunuyor: '+file.name+' · %'+Math.round((m.progress||0)*100))}});return result.data.text}
$('extractBtn').onclick=async()=>{
 if(!data.questionFiles.length){notify('Önce soru dosyası veya fotoğrafı yükleyin.');return}
 const qtexts=[],atexts=[];
 try{
  let n=0,total=data.questionFiles.length+data.answerFiles.length;
  for(const f of data.questionFiles){
   n++;notify('Soru kaynağı okunuyor ('+n+'/'+total+'): '+f.name);
   qtexts.push(await (/^image\//.test(f.type)?ocrFile(f):extractFile(f)))
  }
  for(const f of data.answerFiles){
   n++;notify('Cevap kaynağı okunuyor ('+n+'/'+total+'): '+f.name);
   atexts.push(await (/^image\//.test(f.type)?ocrFile(f):extractFile(f)))
  }
  data.questions=parseQuestions(qtexts.join('\n'),atexts.join('\n'));
  if(!data.questions.length){notify('Soru yapısı tanınamadı. Soruların 1. ..., şıkların A) ... biçiminde olduğunu kontrol edin.');return}
  clearInterval(data.timerId);data.running=false;data.finished=false;data.answersVisible=false;
  data.editingAnswers=false;data.manualOptionFor=null;data.pendingLetters={};data.currentSavedName='';
  $('testName').value='';$('timer').textContent='Süre: 00:00';
  $('recordCard').classList.remove('hidden');$('uploadCard').classList.remove('hidden');
  $('metrics').classList.add('hidden');$('chart').classList.add('hidden');
  $('toggleAnswersBtn').textContent='Cevapları Göster';
  renderQuestions();renderManualAnswers();
  notify(allAnswersMatched()?'Tüm cevaplar eşleştirildi. Test adını girip kaydedebilir veya testi başlatabilirsiniz.':data.questions.length+' soru okundu. Eksik doğru cevapların harflerini A–E olarak yazıp Eşleştir düğmesine basın.');
 }catch(e){notify('İçe aktarma hatası: '+e.message)}
};

function normalizeQuestionChoices(q){
 const options=Array.isArray(q.options)?q.options:[];
 const letters=Array.isArray(q.letters)?q.letters:[];
 const oldCorrect=Number.isInteger(q.correct)?q.correct:null;
 const wantedLetter=String(q.correctLetter||((oldCorrect!==null?letters[oldCorrect]:null)||'')).trim().toUpperCase();
 const seenLetters=new Set(),seenTexts=new Set(),unique=[];
 for(let i=0;i<options.length;i++){
  const letter=String(letters[i]||String.fromCharCode(65+i)).trim().toUpperCase();
  const value=String(options[i]||'').replace(/\s+/g,' ').trim();
  const textKey=value.toLocaleLowerCase('tr-TR');
  if(!/^[A-E]$/.test(letter)||!value||seenLetters.has(letter)||seenTexts.has(textKey))continue;
  seenLetters.add(letter);seenTexts.add(textKey);unique.push({letter,text:value})
 }
 const correct=unique.findIndex(x=>x.letter===wantedLetter);
 return {...q,letters:unique.map(x=>x.letter),options:unique.map(x=>x.text),correct:correct>=0?correct:null,correctLetter:correct>=0?wantedLetter:null,selected:null}
}

function parseQuestions(text,answerText){
 const answerMap={},sequential=[];
 const answerLines=(String(answerText||'')+'\n'+String(text||'')).replace(/\r/g,'').split('\n');
 for(const line of answerLines){
  let m;
  const pairRe=/(?:^|[\s,;|])(?:Soru\s*)?(\d{1,4})\s*[.)\-::]\s*([A-E])(?=\s|$|[,;|])/gi;
  while((m=pairRe.exec(line)))answerMap[+m[1]]=m[2].toUpperCase();
  const spacedRe=/(?:^|[\s,;|])(\d{1,4})\s+([A-E])(?=\s|$|[,;|])/gi;
  while((m=spacedRe.exec(line)))answerMap[+m[1]]=m[2].toUpperCase();
  const standalone=line.match(/^\s*([A-E])\s*[.)]?\s*$/i);
  
 }
 sequential.push(...String(answerText||'').replace(/\r/g,'').split('\n').map(x=>x.trim()).filter(x=>/^[A-E][.)]?$/i.test(x)).map(x=>x[0].toUpperCase()));
 const lines=String(text||'').replace(/\r/g,'').split('\n').map(s=>s.trim()).filter(Boolean);
 const blocks=[];let cur=null;
 for(const line of lines){
  if(/^(cevap\s*anahtar[ıi]|answer\s*key)\s*:?/i.test(line)) {if(cur)blocks.push(cur);cur=null;break}
  if(/^\d{1,4}\s*[-:]\s*[A-E]\s*$/i.test(line))continue;
  const m=line.match(/^\s*(?:(?:Soru)\s*)?(\d{1,4})\s*[.)\-:]\s*(.*)$/i);
  if(m){if(cur)blocks.push(cur);cur={n:+m[1],lines:[m[2]]};continue}
  if(cur)cur.lines.push(line)
 }
 if(cur)blocks.push(cur);
 const parsed=[];
 for(const b of blocks){
  const opts=[],stem=[];
  for(const line of b.lines){
   const marks=[...line.matchAll(/(?:^|\s)([A-E])\s*[).:-]\s*/gi)];
   if(/^\s*A\s*[).:-]/i.test(line)&&marks.length>1){
    for(let k=0;k<marks.length;k++){const mark=marks[k],start=mark.index+mark[0].length,end=k+1<marks.length?marks[k+1].index:line.length;opts.push({letter:mark[1].toUpperCase(),text:line.slice(start,end).trim()})}
   }else{
    const m=line.match(/^\s*([A-E])\s*[).:-]\s*(.*)$/i);
    if(m)opts.push({letter:m[1].toUpperCase(),text:m[2]});else stem.push(line)
   }
  }
  const q=stem.join(' ').trim();if(q.length<2)continue;
  parsed.push({n:b.n,text:q,options:opts.map(o=>o.text),letters:opts.map(o=>o.letter),correctLetter:answerMap[b.n]||null,selected:null})
 }
 if(sequential.length===parsed.length)parsed.forEach((q,i)=>{if(!q.correctLetter)q.correctLetter=sequential[i]});
 return parsed.map(q=>normalizeQuestionChoices(q))
}
function isMatched(q){
 return !!q && Array.isArray(q.options) && Array.isArray(q.letters) && q.options.length>=2 &&
 Number.isInteger(q.correct) && q.correct>=0 && q.correct<q.options.length && !!String(q.options[q.correct]||'').trim() &&
 String(q.letters[q.correct]||'').toUpperCase()===String(q.correctLetter||'').toUpperCase();
}
function allAnswersMatched(){return data.questions.length>0&&data.questions.every(isMatched)}
function updateTestControls(){
 const ready=allAnswersMatched();
 $('startBtn').disabled=!ready||data.running;
 $('finishBtn').disabled=!data.running;
 $('toggleAnswersBtn').disabled=!ready||data.running;
 $('recordEditRow').classList.toggle('hidden',!ready||data.running||data.editingAnswers);
}
function renderManualAnswers(){
 const box=$('manualAnswers'),summary=$('matchingSummary');
 const total=data.questions.length,missing=data.questions.map((q,i)=>({q,i})).filter(x=>!isMatched(x.q));
 if(!total||data.running){box.classList.add('hidden');summary.classList.add('hidden');updateTestControls();return}
 if(!missing.length&&!data.editingAnswers){
  box.classList.add('hidden');summary.classList.remove('hidden');
  $('matchingCount').textContent=total+' / '+total+' eşleşme tamam.';
  updateTestControls();return
 }
 summary.classList.add('hidden');box.classList.remove('hidden');
 const rows=(data.editingAnswers?data.questions.map((q,i)=>({q,i})):missing);
 box.innerHTML='<b>Doğru şık harflerini elle girin</b><p>Menüden cevap seçmeniz gerekmez. Eksik her sorunun kutusuna A, B, C, D veya E yazın ve <b>Eşleştir</b> düğmesine basın.</p>'+
 rows.map(({q,i})=>{
  const letters=q.letters.map(x=>String(x).toUpperCase()).join(', ')||'Şık okunamadı';
  const needsChoice=q.options.length<2||data.manualOptionFor===i;
  const prefill=esc(data.pendingLetters[i]||q.correctLetter||'');
  return '<div class="matching-row"><b>'+esc(String(q.n||i+1))+'. '+esc(q.text.slice(0,150))+'</b><div class="hint">Okunan şıklar: '+esc(letters)+'</div>'+
   '<div class="matching-fields"><label for="letter-'+i+'">Doğru harf:</label><input id="letter-'+i+'" class="letter-input" type="text" maxlength="1" autocapitalize="characters" autocomplete="off" inputmode="text" data-letter="'+i+'" value="'+prefill+'" placeholder="A-E"><button type="button" class="btn yellow" data-apply-letter="'+i+'">Eşleştir</button><span class="hint">'+(isMatched(q)?'Eşleşti':'Eşleşmedi')+'</span></div>'+
   (needsChoice?'<div class="missing-choice"><b>Eksik soru şıkkını elle tamamla</b><p class="hint">Kaynakta seçenek okunmadıysa harfini ve metnini aşağıya girin.</p><div class="matching-fields"><input class="letter-input" type="text" maxlength="1" autocomplete="off" data-extra-letter="'+i+'" value="'+esc(data.pendingLetters[i]||'')+'" placeholder="A-E" aria-label="Eksik şık harfi"><input type="text" data-extra-text="'+i+'" placeholder="Şık metnini girin" style="flex:1;min-width:150px"><button type="button" class="btn" data-add-extra="'+i+'">Şıkkı Ekle</button></div></div>':'')+
   '</div>';
 }).join('')+
 '<p><b>'+(total-missing.length)+' / '+total+'</b> cevap eşleştirildi.'+(missing.length?' Kalan '+missing.length+' soru için şık harfi girin.':'')+'</p>'+
 (data.editingAnswers?'<button type="button" class="btn start-test" id="confirmMatchingBtn">Eşleştirmeyi Tamamla</button>':'');
 box.querySelectorAll('[data-apply-letter]').forEach(btn=>btn.onclick=()=>{
  const i=+btn.dataset.applyLetter,q=data.questions[i];
  const letter=String(box.querySelector('[data-letter="'+i+'"]').value||'').trim().toUpperCase();
  if(!/^[A-E]$/.test(letter)){notify('Sadece A, B, C, D veya E harflerinden birini girin.');return}
  const index=q.letters.findIndex(x=>String(x).toUpperCase()===letter);
  if(index<0){
   data.manualOptionFor=i;data.pendingLetters[i]=letter;
   notify('Girilen '+letter+' şıkkı soru seçeneklerinde bulunamadı. Aşağıdaki eksik şık alanına şık metnini ekleyin.');
   renderManualAnswers();return
  }
  q.correct=index;q.correctLetter=letter;delete data.pendingLetters[i];data.manualOptionFor=null;
  if(allAnswersMatched()&&!data.editingAnswers)notify('Tüm cevaplar eşleştirildi. Kaydetme alanı açıldı.');
  renderManualAnswers();renderQuestions()
 });
 box.querySelectorAll('[data-letter]').forEach(inp=>inp.onkeydown=e=>{
  if(e.key==='Enter'){e.preventDefault();box.querySelector('[data-apply-letter="'+inp.dataset.letter+'"]').click()}
 });
 box.querySelectorAll('[data-add-extra]').forEach(btn=>btn.onclick=()=>{
  const i=+btn.dataset.addExtra,q=data.questions[i];
  const letter=String(box.querySelector('[data-extra-letter="'+i+'"]').value||'').trim().toUpperCase();
  const optionText=String(box.querySelector('[data-extra-text="'+i+'"]').value||'').trim();
  if(!/^[A-E]$/.test(letter)||!optionText){notify('Eksik şıkkın A–E harfini ve metnini girin.');return}
  if(q.options.length>=5){notify('Bir soruda en fazla 5 şık bulunabilir.');return}
  let idx=q.letters.findIndex(x=>String(x).toUpperCase()===letter);
  if(idx>=0){q.options[idx]=optionText}else{q.letters.push(letter);q.options.push(optionText);idx=q.options.length-1}
  q.correct=idx;q.correctLetter=letter;data.manualOptionFor=null;delete data.pendingLetters[i];
  if(allAnswersMatched()&&!data.editingAnswers)notify('Tüm cevaplar eşleştirildi. Kaydetme alanı açıldı.');
  renderManualAnswers();renderQuestions()
 });
 const done=$('confirmMatchingBtn');
 if(done)done.onclick=()=>{
  if(!allAnswersMatched()){notify('Önce eksik soruların cevaplarını tamamlayın.');return}
  data.editingAnswers=false;renderManualAnswers();notify('Eşleştirmeler tamamlandı. Kaydetme alanı açıldı.')
 };
 updateTestControls();
}
$('editMatchingBtn').onclick=()=>{if(data.running)return;data.editingAnswers=true;renderManualAnswers()};
function renderQuestions(){
 const list=$('questions');
 if(!data.questions.length){list.innerHTML='<p class="hint">Henüz soru yok.</p>';return}
 list.innerHTML=data.questions.map((q,i)=>{
  const opts=q.options.length?q.options.map((o,j)=>{
   let cls='opt';
   if(data.running&&q.selected===j)cls+=' chosen';
   if(data.answersVisible&&q.correct===j)cls+=' correct';
   if(data.answersVisible&&q.selected===j&&q.correct!==j)cls+=' wrong';
   return '<label class="'+cls+'"><input type="radio" name="answer_question_'+i+'" data-question="'+i+'" value="'+j+'" '+(q.selected===j?'checked':'')+' '+(!data.running?'disabled':'')+'><span><b>'+esc(q.letters[j]||String.fromCharCode(65+j))+')</b> '+esc(o)+'</span></label>'
  }).join(''):'<p class="hint">Bu soruda seçenekler tanınmadı. Kaynak metni kontrol edin.</p>';
  let answerRow='';
  if(data.answersVisible){
   if(q.correct!==null){
    const userLetter=q.selected===null?'Cevaplanmadı':(q.letters[q.selected]||String.fromCharCode(65+q.selected));
    const isCorrect=q.selected===q.correct;
    answerRow='<div class="answer '+(q.selected===null?'':isCorrect?'correct':'wrong')+'"><b>Doğru cevap:</b> '+esc(q.correctLetter||q.letters[q.correct]||String.fromCharCode(65+q.correct))+' <span> · </span><b>Senin cevabın:</b> '+esc(userLetter)+(q.selected===null?'':' — '+(isCorrect?'Doğru':'Yanlış'))+'</div>';
   }else answerRow='<div class="answer unmatched">Cevap anahtarı bu soruyla eşleşmedi. Kaynak/cevap anahtarını kontrol edin.</div>';
  }
  const clearControl=data.running?'<button type="button" class="btn clear-answer-btn '+(q.selected===null?'hidden':'')+'" data-clear-answer="'+i+'" aria-label="'+(i+1)+'. sorunun seçimini kaldır">Seçimi Kaldır</button>':'';
  return '<article class="q"><div class="qtitle"><span class="num">'+(i+1)+'</span><span>'+esc(q.text)+'</span></div><div class="opts">'+opts+'</div>'+clearControl+answerRow+'</article>'
 }).join('');
 function changeAnswer(i,choice){
  if(!data.running)return;
  const q=data.questions[i];
  if(!q||!(choice===null||(Number.isInteger(choice)&&choice>=0&&choice<q.options.length)))return;
  q.selected=choice;
  list.querySelectorAll('input[name="answer_question_'+i+'"]').forEach(input=>{
   const chosen=choice!==null&&Number(input.value)===choice;
   input.checked=chosen;
   input.closest('.opt')?.classList.toggle('chosen',chosen);
  });
  const clearButton=list.querySelector('[data-clear-answer="'+i+'"]');
  if(clearButton)clearButton.classList.toggle('hidden',choice===null);
  updateProgress();
 }
 list.querySelectorAll('input[type=radio]').forEach(inp=>inp.onclick=()=>{
  if(!data.running)return;
  const i=Number(inp.dataset.question),j=Number(inp.value);
  const q=data.questions[i];
  if(!q||!Number.isInteger(j)||j<0||j>=q.options.length)return;
  // Clicking the selected option again cancels it; another option replaces it.
  changeAnswer(i,q.selected===j?null:j)
 });
 list.querySelectorAll('[data-clear-answer]').forEach(btn=>btn.onclick=()=>changeAnswer(Number(btn.dataset.clearAnswer),null));
 updateProgress()
}
function updateProgress(){const total=data.questions.length,answered=data.questions.filter(q=>q.selected!==null).length;$('progressLabel').textContent='İlerleme: '+answered+' / '+total;$('progress').value=total?answered/total*100:0}
$('startBtn').onclick=()=>{
 if(data.running||!allAnswersMatched()){notify('Önce tüm soruların doğru şıklarını eşleştirin.');return}
 clearInterval(data.timerId);data.questions.forEach(q=>q.selected=null);
 data.running=true;data.finished=false;data.answersVisible=false;data.startAt=Date.now();
 $('recordCard').classList.add('hidden');$('uploadCard').classList.add('hidden');
 $('metrics').classList.add('hidden');$('chart').classList.add('hidden');
 $('toggleAnswersBtn').textContent='Cevapları Göster';$('timer').textContent='Süre: 00:00';
 data.timerId=setInterval(()=>{const t=Math.floor((Date.now()-data.startAt)/1000);$('timer').textContent='Süre: '+String(Math.floor(t/60)).padStart(2,'0')+':'+String(t%60).padStart(2,'0')},1000);
 renderQuestions();updateTestControls();window.scrollTo({top:$('questions').offsetTop,behavior:'smooth'})
};
$('finishBtn').onclick=()=>{
 if(!data.running)return;
 data.running=false;data.finished=true;clearInterval(data.timerId);data.answersVisible=false;
 $('recordCard').classList.remove('hidden');$('uploadCard').classList.remove('hidden');
 $('toggleAnswersBtn').textContent='Cevapları Göster';
 const correct=data.questions.filter(q=>q.selected===q.correct).length;
 const wrong=data.questions.filter(q=>q.selected!==null&&q.selected!==q.correct).length;
 const unanswered=data.questions.filter(q=>q.selected===null).length;
 const rate=data.questions.length?Math.round(correct/data.questions.length*100):0;
 $('correctCount').textContent=correct;$('wrongCount').textContent=wrong;$('rate').textContent=rate+'%';$('unanswered').textContent=unanswered;
 $('metrics').classList.remove('hidden');drawChart(correct,wrong,unanswered);$('chart').classList.remove('hidden');
 renderManualAnswers();renderQuestions();updateTestControls();
 notify('Test bitti. Sonuçlar hazır. Doğru ve yanlış şıkları görmek için Cevapları Göster düğmesine basın.')
};
$('toggleAnswersBtn').onclick=()=>{
 if(data.running||!allAnswersMatched()){notify('Cevapları görmek için eşleştirmeyi tamamlayın ve varsa testi bitirin.');return}
 data.answersVisible=!data.answersVisible;
 $('toggleAnswersBtn').textContent=data.answersVisible?'Cevapları Gizle':'Cevapları Göster';
 renderQuestions()
};
function drawChart(c,w,u){const cv=$('chart'),ctx=cv.getContext('2d');ctx.clearRect(0,0,cv.width,cv.height);const values=[c,w,u],labels=['Doğru','Yanlış','Cevapsız'],colors=['#86efac','#fda4af','#cbd5e1'],max=Math.max(1,...values);values.forEach((v,i)=>{const x=60+i*190,h=v/max*110;ctx.fillStyle=colors[i];ctx.fillRect(x,130-h,90,h);ctx.fillStyle='#1f2937';ctx.font='bold 18px Arial';ctx.fillText(String(v),x+36,118-h);ctx.font='14px Arial';ctx.fillText(labels[i],x+8,155)});}
function currentRecord(){return {name:$('testName').value.trim(),questions:data.questions.map(normalizeQuestionChoices),sourceNames:[...data.sourceNames],updatedAt:new Date().toISOString()}}
function saveCurrent(show=true){if(data.running||data.editingAnswers){notify('Test veya eşleştirme düzenlemesi sırasında kayıt yapılamaz.');return}if(!allAnswersMatched()){notify('Kaydetmeden önce bütün soruları cevaplarıyla eşleştirin.');return}const name=$('testName').value.trim();if(!name){if(show)notify('Kaydetmek için test adı girin.');return}try{const records=readRecords(),r=currentRecord(),i=records.findIndex(x=>x.name.toLocaleLowerCase('tr-TR')===name.toLocaleLowerCase('tr-TR'));if(i>=0)records[i]=r;else records.unshift(r);data.currentSavedName=name;writeRecords(records);$('savedSelect').value=String(Math.max(0,records.findIndex(x=>x.name===name)));if(show)notify('“'+name+'” kaydedildi.')}catch(e){notify('Kayıt başarısız. Tarayıcı depolama sınırı dolmuş olabilir: '+e.message)}}
$('saveBtn').onclick=()=>saveCurrent(true);
function loadRecord(i){
 if(data.running){notify('Testi bitirmeden başka kayıt açılamaz.');return}
 const r=readRecords()[i];if(!r)return;
 data.currentSavedName=r.name;$('testName').value=r.name;
 data.questions=(r.questions||[]).map(normalizeQuestionChoices);
 data.sourceNames=r.sourceNames||[];data.answersVisible=false;data.running=false;data.finished=false;
 data.editingAnswers=false;data.manualOptionFor=null;data.pendingLetters={};
 clearInterval(data.timerId);$('timer').textContent='Süre: 00:00';
 $('recordCard').classList.remove('hidden');$('uploadCard').classList.remove('hidden');
 $('metrics').classList.add('hidden');$('chart').classList.add('hidden');
 $('toggleAnswersBtn').textContent='Cevapları Göster';
 renderQuestions();renderManualAnswers();notify('“'+r.name+'” açıldı. Test Başla ile istediğiniz kadar yeniden çözebilirsiniz.')
}
$('loadBtn').onclick=()=>{if($('savedSelect').value===''){notify('Açmak için kayıt seçin.');return}loadRecord(+$('savedSelect').value)};
function deleteRecord(i){const records=readRecords();if(!records[i])return;const name=records[i].name;if(!confirm('“'+name+'” kaydı kalıcı olarak silinsin mi?'))return;records.splice(i,1);writeRecords(records);notify('Kayıt silindi.')}
$('deleteBtn').onclick=()=>{if($('savedSelect').value===''){notify('Silmek için kayıt seçin.');return}deleteRecord(+$('savedSelect').value)};
renderSaved();updateProgress();renderManualAnswers();
</script><script>
/* Android question/answer reliability layer - loaded AFTER original app script.
   OCR matching coverage is not proof of semantic correctness: confirm against the source. */
(() => {
'use strict';
const el=id=>document.getElementById(id);
const safe=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const collapse=s=>String(s??'').normalize('NFKC').replace(/\u00a0/g,' ').replace(/[ \t]+/g,' ').trim();
const expectedLetters=n=>n===5?['A','B','C','D','E']:['A','B','C','D'];
function strictQuestion(raw) {
  const q={...raw, options:[],letters:[],selected:null,correct:null,correctLetter:null,matchSource:null};
  const rawOptions=Array.isArray(raw.options)?raw.options:[];
  const rawLetters=Array.isArray(raw.letters)?raw.letters:[];
  const count=rawOptions.length;
  if(![4,5].includes(count))return {ok:false,reason:'Şık sayısı eksik veya fazla ('+count+'). Dört ya da beş şık gerekli.'};
  const letters=rawLetters.map(x=>collapse(x).toUpperCase());
  const values=rawOptions.map(collapse);
  const expected=expectedLetters(count);
  if(letters.join(',')!==expected.join(','))return {ok:false,reason:'Şık harfleri eksik, yinelenmiş veya sırasız.'};
  if(values.some(x=>!x))return {ok:false,reason:'Boş şık bulundu.'};
  if(new Set(values.map(x=>x.toLocaleLowerCase('tr-TR'))).size!==count)return {ok:false,reason:'Aynı şık metni birden fazla okunmuş.'};
  if(values.some(x=>/(?:^|\s)[A-E]\s*[\)\].:]\s+\S/.test(x)))return {ok:false,reason:'İç içe geçmiş şık bulundu.'};
  if(collapse(raw.text).length<4)return {ok:false,reason:'Soru metni okunamadı.'};
  q.options=values; q.letters=letters; q.text=collapse(raw.text);
  const preferred=collapse(raw.correctLetter||(
    Number.isInteger(raw.correct)?letters[raw.correct]:''
  )).toUpperCase();
  const idx=letters.indexOf(preferred);
  if(idx>=0){
    q.correct=idx;q.correctLetter=preferred;
    q.matchSource=raw.matchSource==='manual'?'manual':'auto';
  }
  return {ok:true,question:q};
}
function keyData(source) {
  const raw=String(source||'').normalize('NFKC').replace(/\r/g,'\n');
  const map=new Map(),conflicts=new Set();
  const pair=/(?:^|[\s,;|])(?:Soru\s*)?(\d{1,4})\s*(?:[.)\-:]|\s)\s*([A-E])(?=$|[\s,;|.)])/gi;
  let m;
  while((m=pair.exec(raw))!==null){
    const n=Number(m[1]),letter=m[2].toUpperCase();
    if(map.has(n)&&map.get(n)!==letter)conflicts.add(n);
    else map.set(n,letter);
  }
  for(const n of conflicts)map.delete(n);
  const sequential=raw.split(/\n/).map(collapse).filter(x=>/^[A-E][.)]?$/.test(x)).map(x=>x[0]);
  return {map,conflicts,sequential};
}
function parseReport(questionText,answerText) {
  const raw=String(questionText||'').normalize('NFKC').replace(/\r/g,'\n').replace(/\u00a0/g,' ');
  const keyMarker=/(?:^|\n)\s*(?:cevap\s*anahtar[ıi]|answer\s*key)\s*[:\-\n]/i;
  const found=keyMarker.exec(raw);
  const questionPart=found?raw.slice(0,found.index):raw;
  const inlineKey=found?raw.slice(found.index):'';
  const answerKeys=keyData(String(answerText||'')+'\n'+inlineKey);
  const lines=questionPart.split(/\n/).map(collapse).filter(Boolean);
  const blocks=[],skipped=[];
  let current=null,unreadableFragments=0;
  const push=()=>{if(current){blocks.push(current);current=null}};
  for(const line of lines){
    if(/^\d{1,4}\s*[.:\-]\s*[A-E]\s*$/i.test(line))continue;
    const qm=/^(?:(?:Soru|S)\s*[:#]?\s*)?(\d{1,4})\s*[.)\-:]\s*(.*)$/i.exec(line);
    if(qm){
      push();
      current={n:Number(qm[1]),lines:[qm[2]]};
    }else if(current){current.lines.push(line)}
    else if(/^(?:Soru|Question)\s*[:#]?\s*[^\d\s]/i.test(line))unreadableFragments++;
  }
  push();
  if(!blocks.length && questionPart.trim())unreadableFragments=Math.max(1,unreadableFragments);
  const questions=[],used=new Set(),usedNums=new Set();
  for(const block of blocks){
    const opts=[],stem=[];
    let sawOptions=false;
    for(const line of block.lines){
      // A) ... B) ... on one OCR row, or one choice per row.
      const markers=[...line.matchAll(/(?:^|[\s;|])([A-E])\s*[\)\].:]\s*/gi)];
      if(!markers.length){
        if(sawOptions && opts.length)opts[opts.length-1].text+=' '+line;
        else stem.push(line);
        continue;
      }
      const leading=line.slice(0,markers[0].index).trim();
      if(leading){
        if(sawOptions && opts.length)opts[opts.length-1].text+=' '+leading;
        else stem.push(leading);
      }
      for(let i=0;i<markers.length;i++){
        const marker=markers[i],end=i+1<markers.length?markers[i+1].index:line.length;
        opts.push({letter:marker[1].toUpperCase(),text:line.slice(marker.index+marker[0].length,end).trim()});
      }
      sawOptions=true;
    }
    const source={n:block.n,text:stem.join(' ').trim(),letters:opts.map(x=>x.letter),options:opts.map(x=>x.text)};
    if(usedNums.has(block.n)){
      skipped.push({n:block.n,reason:'Aynı soru numarası birden çok kez okundu.'});
      const previous=questions.findIndex(q=>q.n===block.n);
      if(previous>=0)questions.splice(previous,1);
      continue;
    }
    usedNums.add(block.n);
    const auto=answerKeys.map.get(block.n) || (
      answerKeys.sequential.length>=block.n ? answerKeys.sequential[block.n-1] : ''
    );
    source.correctLetter=answerKeys.conflicts.has(block.n)?null:(auto||null);
    const validated=strictQuestion(source);
    if(!validated.ok){skipped.push({n:block.n,reason:validated.reason});continue}
    const q=validated.question;
    if(used.has(block.n)){skipped.push({n:block.n,reason:'Çift soru numarası.'});continue}
    questions.push(q);used.add(block.n);
  }
  // In case a duplicate number appears after a previously accepted question.
  const blocked=new Set(skipped.filter(x=>x.reason.startsWith('Aynı soru numarası')).map(x=>x.n));
  const valid=questions.filter(q=>!blocked.has(q.n));
  if(unreadableFragments)skipped.push({n:null,reason:'Numarası okunamayan '+unreadableFragments+' soru/bölüm'});
  return {questions:valid,skipped,detected:blocks.length+unreadableFragments,auto:valid.filter(q=>q.matchSource==='auto').length};
}
normalizeQuestionChoices=function(q){
 const v=strictQuestion(q);
 return v.ok?v.question:{...q,options:[],letters:[],correct:null,correctLetter:null,selected:null,invalidReason:v.reason}
};
isMatched=function(q){return strictQuestion(q).ok && Number.isInteger(q.correct) && q.letters[q.correct]===q.correctLetter};
allAnswersMatched=function(){return data.questions.length>0 && data.questions.every(isMatched)};
parseQuestions=function(qText,aText){
 const result=parseReport(qText,aText);
 data.skippedQuestions=result.skipped;
 data.totalDetected=result.detected;
 return result.questions;
};
if(!Array.isArray(data.skippedQuestions))data.skippedQuestions=[];
data.totalDetected=data.totalDetected||0;
data.editingAnswers=false;
// Guide the user through source upload, matching report, then the save panel.
const uploadCard=el('uploadCard');
const recordCard=el('recordCard');
uploadCard.insertAdjacentElement('afterend',recordCard);
uploadCard.querySelector('h2').textContent='1. Soruları ve cevapları yükle';
recordCard.querySelector('h2').textContent='2. Test kaydı';
const summary=el('matchingSummary');
const report=document.createElement('div');report.id='matchingReport';report.className='status hidden';
summary.insertAdjacentElement('afterend',report);
const skippedBox=document.createElement('div');skippedBox.id='skippedReport';skippedBox.className='status hidden';
report.insertAdjacentElement('afterend',skippedBox);
summary.innerHTML='<b id="matchingCount"></b> <span>Şık eşleştirmeleri tamamlandı. Test kayıt ekranı açıldı.</span>';
function renderReports(){
 const total=data.questions.length;
 const auto=data.questions.filter(q=>isMatched(q)&&q.matchSource!=='manual').length;
 const manual=data.questions.filter(q=>isMatched(q)&&q.matchSource==='manual').length;
 const missing=data.questions.filter(q=>!isMatched(q)).length;
 const skipped=data.skippedQuestions||[];
 const ready=total>0 && !missing;
 summary.classList.toggle('hidden',!ready||data.running);
 if(ready){
   el('matchingCount').textContent='Eşleşme tamamlandı: '+auto+' otomatik + '+manual+' manuel = '+total+' / '+total+'.';
 }
 report.classList.toggle('hidden',!total||data.running);
 report.innerHTML='<b>Eşleşme özeti:</b> Otomatik '+auto+' | Manuel '+manual+
   ' | Tamamlanan '+(auto+manual)+' / '+total+' | Bekleyen '+missing+
   ' | Pas geçilen '+skipped.length+
   (ready?'<p><b>Tüm kabul edilen soruların cevap harfleri belirlendi.</b> Cevapların anlam bakımından doğruluğunu kaynak anahtarla karşılaştırın.</p>':'<p>Eksik cevaplar aşağıdan harf girilerek tamamlanmalıdır.</p>')+
   '<details '+(ready?'open':'')+'><summary>Geçerli sorular ve cevapların tamamı ('+total+')</summary>'+
   '<div class="mapping-roster">'+data.questions.map(q=>
     '<div class="mapping-line">Soru '+safe(q.n)+': <b>'+safe(q.correctLetter||'Bekliyor')+
     '</b> — '+safe(q.matchSource==='manual'?'Manuel':isMatched(q)?'Otomatik':'Eşleşmedi')+'</div>'
   ).join('')+'</div></details>';
 skippedBox.classList.toggle('hidden',!skipped.length||data.running);
 skippedBox.innerHTML=skipped.length?'<b>'+skipped.length+' soru/bölüm pas geçildi. Teste dahil edilmedi.</b>'+
   '<div class="mapping-roster">'+skipped.map(x=>'<div class="mapping-line">'+
   (x.n==null?'Numarası bilinmeyen bölüm':'Soru '+safe(x.n))+': '+safe(x.reason)+'</div>').join('')+'</div>':'';
 el('recordCard').classList.toggle('hidden',!ready||data.running||!!data.fromSaved);
}
updateTestControls=function(){
 const ready=allAnswersMatched();
 el('startBtn').disabled=!ready||data.running;
 el('finishBtn').disabled=!data.running;
 el('toggleAnswersBtn').disabled=!data.finished||data.running;
 el('recordEditRow').classList.toggle('hidden',!ready||data.running);
};
renderManualAnswers=function(){
 const box=el('manualAnswers');
 if(!data.questions.length||data.running){box.classList.add('hidden');renderReports();updateTestControls();return}
 const missing=data.questions.map((q,i)=>({q,i})).filter(x=>!isMatched(x.q));
 if(!missing.length){
   box.classList.add('hidden');
   renderReports();updateTestControls();
   return;
 }
 box.classList.remove('hidden');
 box.innerHTML='<b>Sadece eşleşmeyen cevaplar</b><p>Doğru şıkkın mevcut harfini yazın. Yalnızca o soruda bulunan A–D veya A–E harfleri kabul edilir; başka şık ya da metin eklenemez.</p>'+
 missing.map(({q,i})=>{
   const allowed=q.letters.join('');
   return '<div class="matching-row"><b>Soru '+safe(q.n)+': '+safe(q.text.slice(0,140))+'</b>'+
    '<p class="hint">Geçerli şık harfleri: '+safe(q.letters.join(', '))+'</p>'+
    '<div class="matching-fields"><input type="text" class="letter-input" inputmode="text" autocapitalize="characters" autocomplete="off" maxlength="1" pattern="['+safe(allowed)+']" data-letter="'+i+'" aria-label="Soru '+safe(q.n)+' doğru şık harfi" placeholder="'+safe(allowed)+'">'+
    '<button class="btn yellow" type="button" data-apply-letter="'+i+'">Eşleştir</button></div></div>'
 }).join('')+'<p>Eksik kalan: <b>'+missing.length+'</b></p>';
 box.querySelectorAll('[data-letter]').forEach(input=>{
   const q=data.questions[Number(input.dataset.letter)];
   input.addEventListener('input',()=>{
     const valid=new Set(q.letters);
     input.value=Array.from(input.value.toLocaleUpperCase('tr-TR')).find(c=>valid.has(c))||'';
   });
   input.addEventListener('keydown',e=>{
     if(e.key==='Enter'){e.preventDefault();box.querySelector('[data-apply-letter="'+input.dataset.letter+'"]').click()}
   });
 });
 box.querySelectorAll('[data-apply-letter]').forEach(button=>{
   button.onclick=()=>{
     const i=Number(button.dataset.applyLetter),q=data.questions[i];
     const letter=collapse(box.querySelector('[data-letter="'+i+'"]').value).toUpperCase();
     if(!/^[A-E]$/.test(letter)||!q.letters.includes(letter)){
       notify('Geçersiz harf. Bu soru için yalnızca '+q.letters.join(', ')+' kullanılabilir.');
       return;
     }
     q.correct=q.letters.indexOf(letter);
     q.correctLetter=letter;q.matchSource='manual';
     renderManualAnswers();renderQuestions();
     if(allAnswersMatched()){
       notify('Eşleşme tamamlandı: Otomatik ve manuel eşleşmeler birlikte listelendi. Test kayıt ekranı açıldı.');
       el('recordCard').scrollIntoView?.({behavior:'smooth',block:'start'});
     }
   }
 });
 renderReports();updateTestControls();
};
el('extractBtn').onclick=async()=>{
 if(!data.questionFiles.length){notify('Önce soru dosyası veya soru fotoğrafı ekleyin.');return}
 const qt=[],at=[];
 const btn=el('extractBtn');
 btn.disabled=true;
 try{
   const files=[...data.questionFiles.map(f=>[f,qt,'Soru']),...data.answerFiles.map(f=>[f,at,'Cevap'])];
   for(let i=0;i<files.length;i++){
     const [file,output,label]=files[i];
     notify((i+1)+'/'+files.length+' '+label+' kaynağı okunuyor: '+file.name);
     output.push(await (/^image\//.test(file.type)?ocrFile(file):extractFile(file)));
   }
   const result=parseReport(qt.join('\n'),at.join('\n'));
   clearInterval(data.timerId);
   data.questions=result.questions;
   data.skippedQuestions=result.skipped;data.totalDetected=result.detected;
   data.answersVisible=false;data.running=false;data.finished=false;
   data.currentSavedName='';data.editingAnswers=false;
   el('testName').value='';el('timer').textContent='Süre: 00:00';
   el('recordCard').classList.add('hidden');
   el('metrics').classList.add('hidden');el('chart').classList.add('hidden');
   el('toggleAnswersBtn').textContent='Cevapları Göster';
   renderQuestions();renderManualAnswers();
   const auto=result.auto;
   notify(result.questions.length?
    'Okuma tamamlandı. '+auto+' cevap otomatik eşleşti. '+(result.questions.length-auto)+' cevap manuel bekliyor. '+result.skipped.length+' soru/bölüm pas geçildi.':
    'Geçerli soru bulunamadı. Eksik/okunamayan soru ve şıklar aşağıda raporlandı.');
 }catch(error){notify('Okuma işlemi tamamlanamadı: '+error.message)}
 finally{btn.disabled=false}
};
const previousLoad=loadRecord;
loadRecord=function(i){
 if(data.running)return;
 const record=readRecords()[i];if(!record)return;
 const questions=[],skipped=Array.isArray(record.skippedQuestions)?record.skippedQuestions.map(item=>({n:item.n??null,reason:String(item.reason||'Okunamayan soru')})):[];
 for(const item of (record.questions||[])){
   const v=strictQuestion(item);
   if(v.ok)questions.push(v.question);
   else skipped.push({n:item.n||null,reason:'Kayıttaki eksik soru: '+v.reason});
 }
 data.questions=questions;data.skippedQuestions=skipped;
 data.totalDetected=questions.length+skipped.length;
 data.currentSavedName=record.name||'';
 el('testName').value=data.currentSavedName;
 data.sourceNames=record.sourceNames||[];
 data.answersVisible=false;data.running=false;data.finished=false;data.editingAnswers=false;
 clearInterval(data.timerId);
 el('timer').textContent='Süre: 00:00';
 el('recordCard').classList.remove('hidden');el('uploadCard').classList.remove('hidden');
 el('metrics').classList.add('hidden');el('chart').classList.add('hidden');
 el('toggleAnswersBtn').textContent='Cevapları Göster';
 renderQuestions();renderManualAnswers();
 notify(questions.length?'Kayıt açıldı. Test Başla ile istediğiniz kadar yeniden çözebilirsiniz.':'Bu kayıtta geçerli soru bulunamadı. Lütfen kaynağı yeniden okuyun.');
};
const previousCurrent=currentRecord;
currentRecord=function(){
 const value=previousCurrent();
 return {...value,questions:data.questions.map(q=>({...q,selected:null})),skippedQuestions:[...data.skippedQuestions],totalDetected:data.totalDetected};
};
el('startBtn').onclick=()=>{
 if(data.running||!allAnswersMatched())return;
 clearInterval(data.timerId);data.questions.forEach(q=>q.selected=null);
 data.answersVisible=false;data.running=true;data.finished=false;data.startAt=Date.now();
 el('recordCard').classList.add('hidden');el('uploadCard').classList.add('hidden');
 el('metrics').classList.add('hidden');el('chart').classList.add('hidden');
 el('timer').textContent='Süre: 00:00';el('toggleAnswersBtn').textContent='Cevapları Göster';
 data.timerId=setInterval(()=>{
   const seconds=Math.floor((Date.now()-data.startAt)/1000);
   el('timer').textContent='Süre: '+String(Math.floor(seconds/60)).padStart(2,'0')+':'+String(seconds%60).padStart(2,'0');
 },1000);
 renderQuestions();updateTestControls();
 window.scrollTo?.({top:el('questions').offsetTop,behavior:'smooth'});
};
el('finishBtn').onclick=()=>{
 if(!data.running)return;
 data.running=false;data.finished=true;clearInterval(data.timerId);data.answersVisible=false;
 el('recordCard').classList.remove('hidden');el('uploadCard').classList.remove('hidden');
 el('toggleAnswersBtn').textContent='Cevapları Göster';
 const correct=data.questions.filter(q=>q.selected===q.correct).length;
 const wrong=data.questions.filter(q=>q.selected!==null&&q.selected!==q.correct).length;
 const unanswered=data.questions.filter(q=>q.selected===null).length;
 el('correctCount').textContent=correct;el('wrongCount').textContent=wrong;el('unanswered').textContent=unanswered;
 el('rate').textContent=Math.round(correct/data.questions.length*100)+'%';
 el('metrics').classList.remove('hidden');drawChart(correct,wrong,unanswered);el('chart').classList.remove('hidden');
 renderQuestions();renderManualAnswers();updateTestControls();
 notify('Test tamamlandı. Cevapları Göster ile doğruları ve yanlışları inceleyebilir, Test Başla ile yeniden çözebilirsiniz.');
};
el('toggleAnswersBtn').onclick=()=>{
 if(data.running||!data.finished)return;
 data.answersVisible=!data.answersVisible;
 el('toggleAnswersBtn').textContent=data.answersVisible?'Cevapları Gizle':'Cevapları Göster';
 renderQuestions();
};
// Fresh-start and saved-test navigation. Keep the persistent storage when the title is tapped.
const quickCard=document.createElement('section');
quickCard.id='quickSavedCard';quickCard.className='card quick-card hidden';
quickCard.innerHTML='<h2>Kayıtlı Testler</h2><p class="hint">Bir testi seçtiğinizde sorular ve doğru cevap eşleşmeleri hazır açılır. Ardından Test Başla düğmesine dokunun.</p><div id="quickSavedList"></div>';
el('uploadCard').insertAdjacentElement('beforebegin',quickCard);
const oldRenderSaved=renderSaved;
function renderQuickSaved(){
 const records=readRecords();
 const visible=records.length>0&&!data.running;
 quickCard.classList.toggle('hidden',!visible);
 const list=el('quickSavedList');
 list.innerHTML=records.map((record,i)=>{
  const selected=data.fromSaved&&data.currentSavedName===record.name;
  const count=Array.isArray(record.questions)?record.questions.length:0;
  return '<div class="quick-saved-row"><button type="button" class="btn open-saved'+(selected?' active':'')+'" data-quick-open="'+i+'" aria-label="'+safe(record.name)+' testini aç"><b>'+safe(record.name)+'</b><small>'+count+' soru · '+(selected?'Test hazır':'Aç ve çöz')+'</small></button>'+
    '<button type="button" class="btn quick-delete" data-quick-delete="'+i+'" aria-label="'+safe(record.name)+' kaydını sil">Sil</button></div>';
 }).join('');
 list.querySelectorAll('[data-quick-open]').forEach(button=>button.onclick=()=>{
  loadRecord(Number(button.dataset.quickOpen));
  if(allAnswersMatched())el('startBtn').scrollIntoView?.({block:'center',behavior:'smooth'});
 });
 list.querySelectorAll('[data-quick-delete]').forEach(button=>button.onclick=deleteRecord.bind(null,Number(button.dataset.quickDelete)));
}
renderSaved=function(){
 oldRenderSaved();
 renderQuickSaved();
};
function resetToHome(){
 clearInterval(data.timerId);data.timerId=null;
 data.questionFiles=[];data.answerFiles=[];data.sourceNames=[];
 data.questions=[];data.skippedQuestions=[];data.totalDetected=0;
 data.answersVisible=false;data.running=false;data.finished=false;
 data.editingAnswers=false;data.manualOptionFor=null;data.pendingLetters={};
 data.startAt=0;data.fromSaved=false;data.currentSavedName='';
 ['filesInput','answerFilesInput','questionPhotos','answerPhotos'].forEach(id=>{el(id).value=''});
 el('testName').value='';el('savedSelect').value='';
 filesText();
 el('timer').textContent='Süre: 00:00';
 el('progress').value=0;el('progressLabel').textContent='İlerleme: 0 / 0';
 ['correctCount','wrongCount','unanswered'].forEach(id=>{el(id).textContent='0'});
 el('rate').textContent='0%';
 el('metrics').classList.add('hidden');el('chart').classList.add('hidden');
 el('uploadCard').classList.remove('hidden');
 el('recordCard').classList.add('hidden');
 el('toggleAnswersBtn').textContent='Cevapları Göster';
 renderQuestions();
 el('questions').innerHTML='<p class="hint">Önce soru ve cevap dosyası veya fotoğrafı ekleyin; ya da yukarıdaki kayıtlı testlerden birini seçin.</p>';
 el('importStatus').textContent='Dosyaları yükleyin. Fotoğraflar OCR ile okunur; cevap anahtarı ayrı dosya veya fotoğraf olarak eklenebilir.';
 renderManualAnswers();
 renderQuickSaved();
 window.scrollTo?.({top:0,behavior:'instant'});
}
const header=el('homeHeader');
header.onclick=()=>resetToHome();
header.onkeydown=event=>{
 if(event.key==='Enter'||event.key===' '){event.preventDefault();resetToHome()}
};
const savedLoadRecord=loadRecord;
loadRecord=function(index){
 if(data.running)return;
 if(!readRecords()[index])return;
 data.fromSaved=true;
 data.questionFiles=[];data.answerFiles=[];
 ['filesInput','answerFilesInput','questionPhotos','answerPhotos'].forEach(id=>{el(id).value=''});
 savedLoadRecord(index);
 el('uploadCard').classList.add('hidden');
 el('recordCard').classList.add('hidden');
 renderQuickSaved();
 if(!allAnswersMatched()){
   // Older or corrupted records do not bypass validation.
   el('uploadCard').classList.remove('hidden');
   notify('Bu kayıt eşleştirme açısından eksik görünüyor. Teste başlamadan önce kaynağı yeniden yükleyin.');
 }
};
deleteRecord=function(index){
 const records=readRecords(),record=records[index];
 if(!record)return;
 if(!confirm('“'+record.name+'” kaydını kalıcı olarak silmek istediğinize emin misiniz?'))return;
 const wasSelected=Boolean(data.currentSavedName)&&data.currentSavedName===record.name;
 records.splice(index,1);
 writeRecords(records);
 if(wasSelected||records.length===0)resetToHome();
 else renderQuickSaved();
 if(records.length===0)notify('Son kayıt silindi. Yeni test oluşturmak için dosya veya fotoğraf yükleyin.');
 else notify('“'+record.name+'” silindi. Diğer kayıtlı testler başlığın altında duruyor.');
};
const extractOriginal=el('extractBtn').onclick;
el('extractBtn').onclick=async()=>{
 data.fromSaved=false;
 el('uploadCard').classList.remove('hidden');
 await extractOriginal();
 renderQuickSaved();
};
const originalStart=el('startBtn').onclick;
el('startBtn').onclick=()=>{
 originalStart();
 renderQuickSaved();
};
const originalFinish=el('finishBtn').onclick;
el('finishBtn').onclick=()=>{
 originalFinish();
 if(data.fromSaved){
   el('recordCard').classList.add('hidden');
   el('uploadCard').classList.add('hidden');
 }
 renderQuickSaved();
};
window.__quizDiagnostics={parseReport,strictQuestion,keyData,resetToHome};
resetToHome();
})();
</script></body></html>