1// Immediate theme initialization to prevent flash & mobile viewport lock 2(function() { 3 try { 4 if (typeof document !== 'undefined' && document.documentElement) { 5 document.documentElement.style.overflowX = 'hidden'; 6 document.documentElement.style.maxWidth = '100%'; 7 } 8 const savedTheme = localStorage.getItem('nls_theme'); 9 const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; 10 if (savedTheme === 'dark' || (!savedTheme && systemPrefersDark)) { 11 document.documentElement.classList.add('dark'); 12 } else { 13 document.documentElement.classList.remove('dark'); 14 } 15 } catch (e) { 16 console.error('Error initializing theme', e); 17 } 18})(); 19 20function updateThemeIcons() { 21 const isDark = document.documentElement.classList.contains('dark'); 22 document.querySelectorAll('.theme-toggle-dark-icon').forEach(function(icon) { 23 if (isDark) { 24 icon.classList.add('hidden'); 25 } else { 26 icon.classList.remove('hidden'); 27 } 28 }); 29 document.querySelectorAll('.theme-toggle-light-icon').forEach(function(icon) { 30 if (isDark) { 31 icon.classList.remove('hidden'); 32 } else { 33 icon.classList.add('hidden'); 34 } 35 }); 36 37 // Update tooltips / titles 38 document.querySelectorAll('.theme-toggle-btn').forEach(function(btn) { 39 btn.setAttribute('title', isDark ? 'Beralih ke Mode Terang' : 'Beralih ke Mode Gelap'); 40 btn.setAttribute('aria-label', isDark ? 'Beralih ke Mode Terang' : 'Beralih ke Mode Gelap'); 41 }); 42} 43 44function toggleTheme() { 45 const isDark = document.documentElement.classList.toggle('dark'); 46 try { 47 localStorage.setItem('nls_theme', isDark ? 'dark' : 'light'); 48 } catch (e) {} 49 updateThemeIcons(); 50} 51 52// Attach event listeners when DOM is loaded 53if (document.readyState === 'loading') { 54 document.addEventListener('DOMContentLoaded', initThemeButtons); 55} else { 56 initThemeButtons(); 57} 58 59function initThemeButtons() { 60 updateThemeIcons(); 61 document.querySelectorAll('.theme-toggle-btn').forEach(function(btn) { 62 btn.removeEventListener('click', toggleTheme); 63 btn.addEventListener('click', toggleTheme); 64 }); 65} 66 67// Global Alpine Store for Program Registration & Teacher Recruitment Modal 68function initAlpineStores() { 69 if (typeof window === 'undefined' || !window.Alpine) return; 70 71 // 1. Store: daftarPrivat 72 if (!window.Alpine.store('daftarPrivat')) { 73 const defaultForm = { 74 kebutuhan: 'Privat', 75 nama: '', 76 jenjang: 'SD / Sederajat', 77 subjek: '', 78 wa: '', 79 catatan: '' 80 }; 81 82 let savedForm = defaultForm; 83 try { 84 const raw = localStorage.getItem('nls_program_registration_form') || localStorage.getItem('nls_priv_registration_form'); 85 if (raw) { 86 savedForm = Object.assign({}, defaultForm, JSON.parse(raw)); 87 } 88 } catch (e) {} 89 90 window.Alpine.store('daftarPrivat', { 91 showRegistrationModal: false, 92 modalOpen: false, 93 showToast: false, 94 toastMessage: 'Formulir berhasil diproses! Mengarahkan ke WhatsApp...', 95 form: savedForm, 96 submitForm: function() { 97 try { 98 localStorage.setItem('nls_program_registration_form', JSON.stringify(this.form)); 99 } catch (e) {} 100 101 const k = this.form.kebutuhan || 'Privat'; 102 const n = this.form.nama || '-'; 103 const j = this.form.jenjang || '-'; 104 const s = this.form.subjek || '-'; 105 const w = this.form.wa || '-'; 106 const c = this.form.catatan || '-'; 107 108 const lines = [ 109 'Halo Admin Next Level Study!', 110 '', 111 'Saya ingin mendaftar / konsultasi program:', 112 `*Kebutuhan:* ${k}`, 113 `*Nama / Instansi:* ${n}`, 114 `*Jenjang Pendidikan:* ${j}`, 115 `*Subjek yang Diminati:* ${s}`, 116 `*Nomor WhatsApp:* ${w}`, 117 `*Catatan Tambahan:* ${c}`, 118 '',
119 'Mohon informasi dan tindak lanjutnya. Terima kasih!' 120 ]; 121 122 const waUrl = 'https://wa.me/6285163070002?text=' + encodeURIComponent(lines.join('\n')); 123 window.open(waUrl, '_blank'); 124 125 this.showRegistrationModal = false; 126 this.modalOpen = false; 127 this.showToast = true; 128 const self = this; 129 setTimeout(function() { 130 self.showToast = false; 131 }, 4000); 132 }, 133 submit: function() { 134 this.submitForm(); 135 } 136 }); 137 } 138 139 // 1.5. Store: paketPrivat (Adaptive Package Registration Modal) 140 if (!window.Alpine.store('paketPrivat')) { 141 window.Alpine.store('paketPrivat', { 142 showModal: false, 143 activePackageKey: 'reguler', 144 packageDetails: { 145 reguler: { 146 key: 'reguler', 147 title: 'Paket Reguler', 148 tagline: 'Pendampingan Kurikulum Nasional & Persiapan TKA (SD, SMP, SMA)', 149 badge: 'Kurikulum Nasional & TKA', 150 price: 'Rp 120.000', 151 priceUnit: '/ Jam', 152 sessions: 'Ideal 8 sesi per bulan', 153 themeColor: '#0284c7' 154 }, 155 intensif: { 156 key: 'intensif', 157 title: 'Paket Exclusive', 158 tagline: 'OSN Kota/Provinsi, Siswa SD/SMP Internasional & SNBT/Mandiri', 159 badge: 'OSN Kota/Provinsi & SNBT', 160 price: 'Rp 160.000', 161 priceUnit: '/ Jam', 162 sessions: 'Ideal 10 sesi per bulan (Paling Diminati)', 163 themeColor: '#f59e0b' 164 }, 165 internasional: { 166 key: 'internasional', 167 title: 'Paket Juara', 168 tagline: 'OSN Semifinal/Final, SMA Internasional & Kompetisi Global (AMO, SEAMO)', 169 badge: 'OSN Final & Global (AMO, SEAMO)', 170 price: 'Rp 200.000', 171 priceUnit: '/ Jam', 172 sessions: 'Bimbingan Champion Level Dunia', 173 themeColor: '#7c3aed' 174 } 175 }, 176 get activePackage() { 177 return this.packageDetails[this.activePackageKey] || this.packageDetails.reguler; 178 }, 179 formData: { 180 namaSiswa: '', 181 namaOrtu: '', 182 noWa: '', 183 asalSekolah: '', 184 tingkatKelas: 'SMA Kelas 12', 185 metodeBelajar: 'Online (Zoom 1-on-1)', 186 mataPelajaran: '', 187 188 // Reguler Specifics 189 regulerFocus: [], 190 regulerTargetKampus: '', 191 192 // Intensif (Exclusive) Specifics 193 intensifFocus: [], 194 intensifExperience: '', 195 196 // Internasional (Juara) Specifics 197 internasionalFocus: [], 198 internasionalTargetKampus: '', 199 200 // Scheduling 201 sesiPerBulan: '8 Sesi / Bulan (2x seminggu - Standar)', 202 waktuBelajar: 'Sore / Malam (18.30 - 21.00 WIB)', 203 hariPreferensi: ['Hari Kerja (Senin - Jumat)'], 204 catatanKhusus: '' 205 }, 206 open: function(pkgKey) { 207 this.activePackageKey = pkgKey || 'reguler'; 208 if (pkgKey === 'reguler') { 209 this.formData.tingkatKelas = 'SMA Kelas 12'; 210 } else if (pkgKey === 'intensif') { 211 this.formData.tingkatKelas = 'SMP Kelas 8'; 212 } else if (pkgKey === 'internasional') { 213 this.formData.tingkatKelas = 'SMA Kelas 11'; 214 } 215 this.showModal = true; 216 try { document.body.style.overflow = 'hidden'; } catch (e) {} 217 }, 218 setPackage: function(pkgKey) { 219 this.activePackageKey = pkgKey; 220 }, 221 close: function() { 222 this.showModal = false;
223 try { document.body.style.overflow = ''; } catch (e) {} 224 }, 225 toggleArrayItem: function(arr, item) { 226 const idx = arr.indexOf(item); 227 if (idx > -1) { 228 arr.splice(idx, 1); 229 } else { 230 arr.push(item); 231 } 232 }, 233 submitForm: function() { 234 if (!this.formData.namaSiswa.trim()) { 235 alert('Mohon masukkan Nama Lengkap Siswa.'); 236 return; 237 } 238 if (!this.formData.noWa.trim()) { 239 alert('Mohon masukkan Nomor WhatsApp aktif.'); 240 return; 241 } 242 if (!this.formData.mataPelajaran.trim()) { 243 alert('Mohon tuliskan Mata Pelajaran yang ingin dipelajari.'); 244 return; 245 } 246 247 const pkg = this.activePackage; 248 let text = '*FORMULIR PENDAFTARAN LES PRIVAT NLS*\n'; 249 text += '--------------------------------------------\n'; 250 text += 'ð *PILIHAN PAKET:* ' + pkg.title.toUpperCase() + '\n'; 251 text += 'ð° *Tarif:* ' + pkg.price + ' ' + pkg.priceUnit + ' (' + pkg.tagline + ')\n\n'; 252 253 text += 'ð¤ *DATA SISWA & ORANG TUA:*\n'; 254 text += '⢠Nama Siswa: ' + this.formData.namaSiswa + '\n'; 255 if (this.formData.namaOrtu.trim()) { 256 text += '⢠Nama Orang Tua/Wali: ' + this.formData.namaOrtu + '\n'; 257 } 258 text += '⢠WhatsApp: ' + this.formData.noWa + '\n'; 259 text += '⢠Asal Sekolah: ' + (this.formData.asalSekolah || '-') + '\n'; 260 text += '⢠Tingkat/Kelas: ' + this.formData.tingkatKelas + '\n'; 261 text += '⢠Metode Belajar: ' + this.formData.metodeBelajar; 262 if (this.formData.metodeBelajar.includes('Offline')) { 263 text += ' (+ Transport Guru Rp 50.000/sesi)'; 264 } 265 text += '\n\n'; 266 267 text += 'ð¯ *PENYESUAIAN KEBUTUHAN (' + pkg.title + '):*\n'; 268 text += '⢠Mata Pelajaran: ' + this.formData.mataPelajaran + '\n'; 269 270 if (this.activePackageKey === 'reguler') { 271 text += '⢠Diperuntukkan Untuk: ' + (this.formData.regulerFocus.join(', ') || '-') + '\n'; 272 if (this.formData.regulerTargetKampus.trim()) { 273 text += '⢠Target Kampus/Sekolah: ' + this.formData.regulerTargetKampus + '\n'; 274 } 275 } else if (this.activePackageKey === 'intensif') { 276 text += '⢠Diperuntukkan Untuk: ' + (this.formData.intensifFocus.join(', ') || '-') + '\n'; 277 text += '⢠Pengalaman Olimpiade: ' + this.formData.intensifExperience + '\n'; 278 } else if (this.activePackageKey === 'internasional') { 279 text += '⢠Diperuntukkan Untuk: ' + (this.formData.internasionalFocus.join(', ') || '-') + '\n'; 280 if (this.formData.internasionalTargetKampus.trim()) { 281 text += '⢠Target Kampus Dunia/PTN: ' + this.formData.internasionalTargetKampus + '\n'; 282 } 283 } 284 285 text += '\nâ° *RENCANA JADWAL & FREKUENSI:*\n'; 286 text += '⢠Estimasi Sesi: ' + this.formData.sesiPerBulan + '\n'; 287 text += '⢠Waktu Luang: ' + this.formData.waktuBelajar + '\n'; 288 text += '⢠Hari Belajar: ' + (this.formData.hariPreferensi.join(', ') || '-') + '\n'; 289 290 if (this.formData.catatanKhusus.trim()) { 291 text += '\nð *Catatan Khusus:*\n' + this.formData.catatanKhusus + '\n'; 292 } 293 294 text += '\n--------------------------------------------\n'; 295 text += 'Halo Admin Next Level Study, mohon info ketersediaan mentor dan jadwalnya. Terima kasih!'; 296 297 const adminWa = '6285163070002'; 298 const waUrl = 'https://wa.me/' + adminWa + '?text=' + encodeURIComponent(text); 299 window.open(waUrl, '_blank'); 300 301 this.showModal = false;
302 } 303 }); 304 } 305 306 // 2. Store: gabungPengajar 307 if (!window.Alpine.store('gabungPengajar')) { 308 window.Alpine.store('gabungPengajar', { 309 modalOpen: false, 310 showRegistrationModal: false, 311 showModal: false, 312 form: { 313 nama: '', 314 panggilan: '', 315 wa: '', 316 email: '', 317 pendidikan: '', 318 photo: '', 319 categories: [], 320 jenjang: [], 321 subject: '', 322 fokusPrivat: '', 323 filosofi: '', 324 prestasi1: '', 325 prestasi2: '', 326 prestasi3: '', 327 portfolio: '' 328 }, 329 isPhotoUploading: false, 330 handlePhotoUpload: function(event) { 331 const file = event.target.files && event.target.files[0]; 332 if (!file) return; 333 334 if (!file.type.startsWith('image/')) { 335 alert('Mohon pilih file gambar yang valid (JPG, PNG, atau WEBP).'); 336 return; 337 } 338 339 if (file.size > 10 * 1024 * 1024) { 340 alert('Ukuran foto maksimal adalah 10MB. Mohon gunakan foto yang lebih ringan.'); 341 return; 342 } 343 344 const self = this; 345 self.isPhotoUploading = true; 346 const reader = new FileReader(); 347 reader.onload = function(e) { 348 const img = new Image(); 349 img.onload = function() { 350 try { 351 const canvas = document.createElement('canvas'); 352 let width = img.width; 353 let height = img.height; 354 const maxDimension = 600; 355 356 if (width > height) { 357 if (width > maxDimension) { 358 height = Math.round((height * maxDimension) / width); 359 width = maxDimension; 360 } 361 } else { 362 if (height > maxDimension) { 363 width = Math.round((width * maxDimension) / height); 364 height = maxDimension; 365 } 366 } 367 368 canvas.width = width; 369 canvas.height = height; 370 const ctx = canvas.getContext('2d'); 371 ctx.drawImage(img, 0, 0, width, height); 372 373 // Compress into light and crisp 600px JPEG (~35-60KB) 374 const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.85); 375 self.form.photo = compressedDataUrl; 376 } catch(err) { 377 // Fallback if canvas context fails 378 self.form.photo = e.target.result; 379 } finally { 380 self.isPhotoUploading = false; 381 } 382 }; 383 img.onerror = function() { 384 self.isPhotoUploading = false; 385 alert('Gagal memproses gambar. Mohon coba file gambar lain.'); 386 }; 387 img.src = e.target.result; 388 }; 389 reader.readAsDataURL(file); 390 }, 391 submit: function() { 392 // Strict validation for Section 1 393 if (!this.form.nama || !this.form.nama.trim()) { 394 alert('Mohon isi Nama Lengkap & Gelar Anda pada Bagian 1.'); 395 return; 396 } 397 if (!this.form.panggilan || !this.form.panggilan.trim()) { 398 alert('Mohon isi Nama Panggilan / Sapaan pada Bagian 1.'); 399 return; 400 } 401 if (!this.form.wa || !this.form.wa.trim()) { 402 alert('Mohon isi Nomor WhatsApp Aktif pada Bagian 1.'); 403 return; 404 } 405 if (!this.form.email || !this.form.email.trim()) { 406 alert('Mohon isi Alamat Email Aktif pada Bagian 1.'); 407 return; 408 } 409 if (!this.form.pendidikan || !this.form.pendidikan.trim()) { 410 alert('Mohon isi Asal Universitas & Prestasi Akademik pada Bagian 1.'); 411 return; 412 } 413 if (!this.form.photo) { 414 alert('Mohon unggah Foto Profil Pengajar pada Bagian 1.'); 415 return; 416 } 417 418 // Strict validation for Section 2 419 if (!Array.isArray(this.form.categories) || this.form.categories.length === 0) { 420 alert('Mohon pilih minimal 1 Bidang Keahlian & Program pada Bagian 2.'); 421 return; 422 } 423 if (!Array.isArray(this.form.jenjang) || this.form.jenjang.length === 0) { 424 alert('Mohon pilih minimal 1 Sasaran Jenjang Mengajar pada Bagian 2.'); 425 return; 426 } 427 if (!this.form.subject || !this.form.subject.trim()) { 428 alert('Mohon isi Spesialisasi Mata Pelajaran Utama pada Bagian 2.'); 429 return; 430 } 431 432 // Strict validation for Section 3 433 if (!this.form.fokusPrivat || !this.form.fokusPrivat.trim()) { 434 alert('Mohon isi Fokus Kebutuhan Les Privat pada Bagian 3.'); 435 return; 436 } 437 if (!this.form.filosofi || !this.form.filosofi.trim()) { 438 alert('Mohon isi Kutipan / Filosofi Mengajar Anda pada Bagian 3.'); 439 return; 440 } 441 442 const catStr = this.form.categories.join(', '); 443 const jnjStr = this.form.jenjang.join(', '); 444 445 // 1. Create teacher application record for admin Teacher Verification 446 const fullName = this.form.nama.trim(); 447 const nickName = this.form.panggilan.trim(); 448 const nowIso = new Date().toISOString(); 449 450 const application = { 451 id: 'app-' + Date.now(), 452 submittedAt: nowIso, 453 applied_at: nowIso, 454 status: 'pending', 455 nama: fullName, 456 name: fullName, 457 panggilan: nickName, 458 shortName: nickName, 459 wa: this.form.wa.trim(), 460 phone: this.form.wa.trim(), 461 email: this.form.email.trim(), 462 pendidikan: this.form.pendidikan.trim(), 463 education: this.form.pendidikan.trim(), 464 photo: this.form.photo || '/images/pengajar/mentor-1-math.jpg', 465 categories: [...this.form.categories], 466 jenjang: [...this.form.jenjang], 467 jenjangLabel: this.form.jenjang.join(' & '), 468 subject: this.form.subject.trim(), 469 subjects: [this.form.subject.trim()], 470 kebutuhanPrivat: this.form.fokusPrivat.trim(), 471 fokusPrivat: this.form.fokusPrivat.trim(), 472 philosophy: this.form.filosofi.trim(), 473 filosofi: this.form.filosofi.trim(), 474 highlights: [this.form.prestasi1, this.form.prestasi2, this.form.prestasi3].filter(
474Boolean), 475 portfolio: (this.form.portfolio || '').trim(), 476 cv_link: (this.form.portfolio || '').trim(), 477 notes: '' 478 }; 479 480 // 2. Save into serverless database API, localStorage & sync cross-tab 481 try { 482 // Send to backend API 483 if (typeof fetch !== 'undefined') { 484 fetch('/api/teacher-applications', { 485 method: 'POST', 486 headers: { 'Content-Type': 'application/json' }, 487 body: JSON.stringify(application) 488 }).catch(function(err) { 489 console.warn('[NLS] API save error:', err); 490 }); 491 } 492 493 let apps = []; 494 const stored = localStorage.getItem("nls_teacher_applications_v1"); 495 if (stored) { 496 const parsed = JSON.parse(stored); 497 if (Array.isArray(parsed)) apps = parsed; 498 } 499 apps.unshift(application); 500 localStorage.setItem("nls_teacher_applications_v1", JSON.stringify(apps)); 501 502 if (typeof window !== 'undefined' && window.PengajarDatabase && typeof window.PengajarDatabase.submitApplication === 'function') { 503 try { 504 window.PengajarDatabase.submitApplication(application); 505 } catch (err) {} 506 } 507 508 if (typeof BroadcastChannel !== 'undefined') { 509 const bc = new BroadcastChannel('nls_sync_channel'); 510 bc.postMessage({ type: 'SYNC_ALL' }); 511 bc.postMessage({ type: 'TEACHER_APPLICATION_ADDED', data: application }); 512 bc.postMessage({ type: 'TEACHER_APPLICATIONS_UPDATED', data: apps }); 513 } 514 window.dispatchEvent(new CustomEvent('nls-teacher-application-added', { detail: application })); 515 window.dispatchEvent(new CustomEvent('nls-teacher-applications-updated', { detail: apps })); 516 } catch (e) { 517 console.error('Error saving teacher application:', e); 518 } 519 520 const lines = [ 521 'ð *PENDAFTARAN PENGAJAR & MENTOR - NEXT LEVEL STUDY* ð', 522 'ââââââââââââââââââââââââââââ', 523 '', 524 'ð¤ *1. DATA PRIBADI & PENDIDIKAN*', 525 `⢠*Nama Lengkap & Gelar:* ${this.form.nama || '-'}`, 526 `⢠*Nama Panggilan / Sapaan:* ${this.form.panggilan || '-'}`, 527 `⢠*Nomor WhatsApp:* ${this.form.wa || '-'}`, 528 `⢠*Alamat Email:* ${this.form.email || '-'}`, 529 `⢠*Latar Belakang Pendidikan / Kampus:* ${this.form.pendidikan || '-'}`, 530 '', 531 'ð *2. BIDANG KEAHLIAN & SASARAN PROGRAM*', 532 `⢠*Bidang Keahlian:* ${catStr}`, 533 `⢠*Sasaran Jenjang:* ${jnjStr}`, 534 `⢠*Spesialisasi Mata Pelajaran:* ${this.form.subject || '-'}`, 535 '', 536 'ð¯ *3. FOKUS KEBUTUHAN LES PRIVAT*', 537 `${this.form.fokusPrivat || '-'}`, 538 '', 539 'ð¬ *4. FILOSOFI / QUOTE MENGAJAR*', 540 `"${this.form.filosofi || '-'}"`, 541 '', 542 'ð *5. REKAM JEJAK & PRESTASI*', 543 `1. ${this.form.prestasi1 || '-'}`, 544 `2. ${this.form.prestasi2 || '-'}`, 545 `3. ${this.form.prestasi3 || '-'}`, 546 '', 547 'ð *6. LINK CV / PORTOFOLIO*', 548 `${this.form.portfolio || '-'}`, 549 '', 550 'ââââââââââââââââââââââââââââ', 551 'â *Komitmen:* Saya siap mengikuti rangkaian seleksi dan menjunjung tinggi kode etik & integritas pendidik Next Level Study.' 552 ]; 553 554 const waUrl = 'https://wa.me/6285163070002?text=' + encodeURIComponent(lines.join('\n')); 555 window.open(waUrl, '_blank'); 556 557 alert('Pendaftaran berhasil dikirim! Formulir Anda telah masuk ke antrean Teacher Verification NLS dan diteruskan ke Tim Akademik.'); 558 559 this.modalOpen = false;
560 this.showRegistrationModal = false; 561 this.showModal = false; 562 563 this.form = { 564 nama: '', panggilan: '', wa: '', email: '', pendidikan: '', 565 categories: [], jenjang: [], subject: '', fokusPrivat: '', 566 filosofi: '', prestasi1: '', prestasi2: '', prestasi3: '', portfolio: '' 567 }; 568 }, 569 submitForm: function() { 570 this.submit(); 571 } 572 }); 573 } 574} 575 576if (typeof window !== 'undefined') { 577 window.initAlpineStores = initAlpineStores; 578 if (window.Alpine) { 579 initAlpineStores(); 580 } else { 581 document.addEventListener('alpine:init', initAlpineStores); 582 document.addEventListener('DOMContentLoaded', function() { 583 if (window.Alpine) initAlpineStores(); 584 }); 585 } 586}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.