```html
İnteraktif Ders Oluşturucu
` etiketini tam olarak yazmaktan kaçınıyoruz. Bunun yerine, `<\/script>` şeklinde bir backslash ile ayırıyoruz. Aksi takdirde tarayıcı, ana script bloğunu erken bitirebilir." },
{ "type": "narrate", "voice": "tr-TR-Wavenet-C", "content": "İşte bu kadar! PHP, bu satırı işlerken `{$message}` kısmının yerine değişkenin içindeki metni koyacak ve tarayıcıya tamamen çalıştırılabilir bir JavaScript kodu göndermiş olacak. Şimdi fonksiyonumuzu kapatalım." },
{ "type": "type", "content": "}", "lang": "php", "append": true },
{ "type": "narrate", "voice": "tr-TR-Wavenet-C", "content": "Metodumuz tamamlandı. Şimdi bu kodu çalıştıralım ve sonucumuzu görelim. Tabi ki, bu kodun çalışması için bir CodeIgniter Controller'ı içinde olması ve tarayıcıdan bu Controller'a erişilmesi gerekiyor. Ama şimdilik, sadece kodun ürettiği çıktıyı görelim." },
{ "type": "execute", "lang": "php" },
{ "type": "wait", "duration": 3000 },
{ "type": "narrate", "voice": "tr-TR-Wavenet-E", "content": "Vay canına, çalıştı! PHP'nin aslında tarayıcıya komutlar veren bir metin üreticisi gibi çalıştığını anlamak harika." },
{ "type": "narrate", "voice": "tr-TR-Wavenet-C", "content": "Harika bir özet! Bu temel mantığı anladığında, sunucu ve istemci arasındaki ilişkiyi de çözmüş olursun. Bugünkü dersimiz bu kadardı. Katıldığın için teşekkürler!" }
]
};;
const lessonPlan = lessonData.script || [];
// =========================================================================================
// BÖLÜM 2: OYNATICI MOTORU VE YARDIMCI FONKSİYONLAR
// =========================================================================================
const playLessonBtn = document.getElementById('playLessonBtn');
const lessonAudioPlayer = document.getElementById('lessonAudioPlayer');
const codeContentEl = document.getElementById('code-content');
const previewFrame = document.getElementById('preview-frame');
let isPlaying = false;
let lessonAbortController = null;
let fullCode = '';
const wait = (ms, signal) => new Promise((resolve, reject) => {
if (signal?.aborted) return reject(new DOMException('Aborted', 'AbortError'));
const timeoutId = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => { clearTimeout(timeoutId); reject(new DOMException('Aborted', 'AbortError')); });
});
const typeCode = async (text, lang, speed = 35, signal) => {
for (let i = 0; i < text.length; i++) {
if (signal.aborted) return;
fullCode += text[i];
if (i % 3 === 0 || i === text.length - 1) {
codeContentEl.innerHTML = applySyntaxHighlighting(fullCode, lang);
}
await wait(speed, signal);
}
};
const clearScreen = () => {
fullCode = '';
codeContentEl.innerHTML = '';
previewFrame.srcdoc = '';
};
const applySyntaxHighlighting = (code, lang) => {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang, ignoreIllegals: true }).value;
}
return code.replace(/&/g, "&").replace(//g, ">");
};
// Kod çalıştırıcıları (Oluşturucu ile birebir aynı olmalı)
const executors = {
'typescript': (code, frame) => { try { const transformed = Babel.transform(code, { presets: ["typescript"], filename: 'script.ts' }).code; frame.srcdoc = `