2026-03-05 14:19:22 +07:00
<!DOCTYPE html>
< html lang = "id" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" >
< title > SMAN 1 Garut - Presensi Siswa< / title >
< link rel = "stylesheet" href = "./styles.css" >
< / head >
< body >
<!-- Peringatan: jangan buka dari file:// (browser blokir koneksi ke server) -->
< div id = "file-protocol-warning" class = "file-protocol-warning hidden" >
< div class = "file-protocol-warning-inner" >
< strong > ⚠️ Buka aplikasi lewat HTTP< / strong >
< p > Aplikasi ini dibuka dari < strong > file://< / strong > . Browser memblokir koneksi ke server. Agar bisa terhubung:< / p >
< ol >
< li > Buka CMD / Terminal.< / li >
< li > Masuk ke folder < code > mobile< / code > : < code > cd c:\laragon\www\sman1\mobile< / code > < / li >
< li > Jalankan: < code > php -S localhost:8001< / code > < / li >
< li > Buka di browser: < a href = "http://localhost:8001" target = "_blank" rel = "noopener" > http://localhost:8001< / a > < / li >
< / ol >
< / div >
< / div >
<!-- Toast container (untuk notifikasi dari app.js) -->
< div id = "toast-container" class = "toast-container" > < / div >
<!-- Modal Settings (Backend URL) -->
< div id = "settings-modal" class = "modal hidden" >
< div class = "modal-backdrop" id = "modal-backdrop" > < / div >
< div class = "modal-content" >
< div class = "modal-header" >
< h2 > Pengaturan Server< / h2 >
< button type = "button" id = "btn-close-settings" class = "modal-close" aria-label = "Tutup" > × < / button >
< / div >
< div class = "modal-body" >
< p class = "modal-hint" > Pastikan aplikasi dibuka lewat < strong > http://< / strong > (bukan file://), misalnya < code > http://localhost:8001< / code > setelah menjalankan < code > php -S localhost:8001< / code > di folder mobile.< / p >
< div class = "form-group" >
< label class = "input-label" for = "backend-url" > URL Backend (SMAN 1)< / label >
< input id = "backend-url" type = "text" class = "input-android input-full" placeholder = "http://localhost/sman1/backend/public" autocomplete = "off" >
< / div >
< p id = "config-status" class = "config-status" > < / p >
< div class = "modal-actions" >
< button type = "button" id = "btn-test-connection" class = "secondary-button" > Cek koneksi< / button >
< button type = "button" id = "btn-save-config" class = "login-button" > Simpan< / button >
< / div >
< / div >
< / div >
< / div >
< div class = "container" >
<!-- Header (tombol settings untuk buka Backend URL) -->
< div class = "app-header" >
< button type = "button" id = "btn-open-settings" class = "back-button btn-settings" aria-label = "Pengaturan" >
< svg viewBox = "0 0 24 24" > < path d = "M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" / > < / svg >
< / button >
< span class = "app-title" > Presensi Online< / span >
< / div >
<!-- ========== SCREEN WELCOME ========== -->
< div id = "screen-welcome" class = "screen" >
< div class = "android-illustration" >
< div class = "android-logo" >
< img src = "./assets/img/logo_sman1garut.png" alt = "Logo SMAN 1 Garut" >
< / div >
< h1 class = "welcome-text" > Presensi Online< / h1 >
< p class = "sub-text" > SMAN 1 Garut< / p >
< / div >
< button type = "button" id = "btn-go-login" class = "login-button ripple" > Masuk< / button >
< div class = "signup-link" >
Belum punya akun? < a href = "#" id = "btn-go-register" > Daftar< / a >
< / div >
< / div >
<!-- ========== SCREEN LOGIN ========== -->
< div id = "screen-login" class = "screen hidden" >
< div class = "android-illustration android-illustration-small" >
< h1 class = "welcome-text" > Masuk< / h1 >
< p class = "sub-text" > NISN & PIN< / p >
< / div >
< div class = "form-group" >
< div class = "input-label" > NISN< / div >
< div class = "input-android" >
< svg viewBox = "0 0 24 24" > < path d = "M20 3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H4V5h16v14z" / > < path d = "M9 8c-1.38 0-2.5 1.12-2.5 2.5S7.62 13 9 13s2.5-1.12 2.5-2.5S10.38 8 9 8z" / > < / svg >
< input id = "login-nisn" type = "text" inputmode = "numeric" placeholder = "Masukkan NISN" >
< / div >
< / div >
< div class = "form-group" >
< div class = "input-label" > PIN< / div >
< div class = "input-android" >
< svg viewBox = "0 0 24 24" > < path d = "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" / > < / svg >
< input id = "login-pin" type = "password" inputmode = "numeric" maxlength = "6" placeholder = "Masukkan PIN" >
< / div >
< / div >
< p id = "login-status" class = "form-status" > < / p >
< div class = "forgot-section" >
< a href = "#" id = "link-forgot-pin" class = "forgot-link" > Lupa PIN?< / a >
< / div >
< button type = "button" id = "btn-login" class = "login-button ripple" > MASUK< / button >
< div class = "signup-link" >
Belum punya akun? < a href = "#" id = "link-go-register" > Daftar< / a >
< / div >
< / div >
<!-- ========== SCREEN LUPA PIN ========== -->
< div id = "screen-forgot-pin" class = "screen hidden" >
< div class = "android-illustration android-illustration-small" >
< h1 class = "welcome-text" > Lupa PIN< / h1 >
< p class = "sub-text" > Reset PIN dengan NISN Anda< / p >
< / div >
< div class = "form-group" >
< div class = "input-label" > NISN< / div >
< div class = "input-android" >
< input id = "forgot-nisn" type = "text" inputmode = "numeric" placeholder = "Masukkan NISN" >
< / div >
< / div >
< div class = "form-group" >
< div class = "input-label" > PIN Baru (min 4 digit)< / div >
< div class = "input-android" >
< input id = "forgot-new-pin" type = "password" inputmode = "numeric" maxlength = "6" placeholder = "PIN baru" >
< / div >
< / div >
< div class = "form-group" >
< div class = "input-label" > Ulangi PIN Baru< / div >
< div class = "input-android" >
< input id = "forgot-new-pin2" type = "password" inputmode = "numeric" maxlength = "6" placeholder = "Ulangi PIN baru" >
< / div >
< / div >
< p id = "forgot-pin-status" class = "form-status" > < / p >
< button type = "button" id = "btn-reset-pin" class = "login-button ripple" > Reset PIN< / button >
< div class = "signup-link" >
< a href = "#" id = "link-back-to-login" > Kembali ke Masuk< / a >
< / div >
< / div >
<!-- ========== SCREEN REGISTER NISN ========== -->
< div id = "screen-register-nisn" class = "screen hidden" >
< div class = "android-illustration android-illustration-small" >
< h1 class = "welcome-text" > Daftar< / h1 >
< p class = "sub-text" > Cek NISN dulu< / p >
< / div >
< div class = "form-group" >
< div class = "input-label" > NISN< / div >
< div class = "input-android" >
< input id = "reg-nisn" type = "text" inputmode = "numeric" placeholder = "Masukkan NISN" >
< / div >
< / div >
< p id = "reg-nisn-status" class = "form-status" > < / p >
< button type = "button" id = "btn-check-nisn" class = "login-button ripple" > Lanjutkan< / button >
< div class = "signup-link" >
Sudah punya akun? < a href = "#" id = "link-back-login-1" > Masuk< / a >
< / div >
< / div >
<!-- ========== SCREEN REGISTER PIN ========== -->
< div id = "screen-register-pin" class = "screen hidden" >
< div class = "android-illustration android-illustration-small" >
< h1 class = "welcome-text" > Lengkapi Data< / h1 >
< p class = "sub-text" > Pilih kelas & buat PIN< / p >
< / div >
< div id = "reg-student-summary" class = "student-summary" > < / div >
< div class = "form-group" >
< div class = "input-label" > Kelas< / div >
< select id = "reg-class" class = "input-android input-full" >
< option value = "" > -- Pilih kelas --< / option >
< / select >
< / div >
< div class = "form-group" >
< div class = "input-label" > PIN (min 4 digit)< / div >
< div class = "input-android" >
< input id = "reg-pin" type = "password" inputmode = "numeric" maxlength = "6" placeholder = "PIN" >
< / div >
< / div >
< div class = "form-group" >
< div class = "input-label" > Ulangi PIN< / div >
< div class = "input-android" >
< input id = "reg-pin2" type = "password" inputmode = "numeric" maxlength = "6" placeholder = "Ulangi PIN" >
< / div >
< / div >
< p id = "reg-pin-status" class = "form-status" > < / p >
< button type = "button" id = "btn-complete-register" class = "login-button ripple" > Simpan & Mulai< / button >
< div class = "signup-link" >
< a href = "#" id = "btn-register-pin-batal" > Batal< / a >
< / div >
< / div >
<!-- ========== SCREEN HOME (setelah login) ========== -->
< div id = "screen-home" class = "screen hidden" >
< div class = "home-greeting" >
< h2 id = "home-greeting-text" class = "home-greeting-title" > Presensi< / h2 >
< p class = "home-greeting-sub" id = "home-date-label" > < / p >
< / div >
< div id = "home-student-summary" class = "student-summary student-summary-compact" > < / div >
<!-- Kartu status / aksi presensi (enterprise style) -->
< div class = "home-presence-grid" >
<!-- Masuk -->
< div class = "presence-card" id = "card-masuk" >
< div class = "presence-card-icon presence-card-icon-masuk" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M12 3v18M5 12l7 7 7-7" / > < / svg >
< / div >
< div class = "presence-card-body" >
< div class = "presence-card-label" > Absen Masuk< / div >
< div id = "masuk-status" class = "presence-card-status hidden" > Sudah masuk< / div >
< button type = "button" id = "btn-absen-masuk" class = "presence-card-btn presence-card-btn-primary ripple" > Absen Masuk< / button >
< / div >
< / div >
<!-- Check - in Mapel -->
< div class = "presence-card presence-card-featured" id = "card-mapel" >
< div class = "presence-card-icon presence-card-icon-mapel" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < rect x = "3" y = "3" width = "7" height = "7" / > < rect x = "14" y = "3" width = "7" height = "7" / > < rect x = "14" y = "14" width = "7" height = "7" / > < rect x = "3" y = "14" width = "7" height = "7" / > < / svg >
< / div >
< div class = "presence-card-body" >
< div class = "presence-card-label" > Jam Belajar< / div >
< p class = "presence-card-hint" > Scan QR dari guru untuk absen mapel< / p >
< button type = "button" id = "btn-scan-qr" class = "presence-card-btn presence-card-btn-featured ripple" > Check-in Mapel< / button >
< / div >
< / div >
<!-- Pulang -->
< div class = "presence-card" id = "card-pulang" >
< div class = "presence-card-icon presence-card-icon-pulang" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M12 21V3M5 12l7-7 7 7" / > < / svg >
< / div >
< div class = "presence-card-body" >
< div class = "presence-card-label" > Absen Pulang< / div >
< div id = "pulang-status" class = "presence-card-status hidden" > Sudah pulang< / div >
< button type = "button" id = "btn-absen-pulang" class = "presence-card-btn presence-card-btn-secondary ripple" > Absen Pulang< / button >
< / div >
< / div >
< / div >
<!-- Lokasi ringkas -->
< div class = "home-location-bar" >
< span id = "location-status" class = "location-status-inline" > Mengambil lokasi…< / span >
< button type = "button" id = "btn-refresh-location" class = "link-button" aria-label = "Refresh" > ↻< / button >
< / div >
<!-- Hasil absen (popup rapi, bisa ditutup) -->
< div id = "result-box" class = "result-popup hidden" >
< div class = "result-popup-inner" >
< div id = "result-icon" class = "result-popup-icon" > < / div >
< div id = "result-status" class = "result-popup-status" > -< / div >
< p id = "result-message" class = "result-popup-message" > < / p >
< button type = "button" id = "result-close-btn" class = "btn-outline btn-sm" > Tutup< / button >
< / div >
< / div >
< div class = "home-footer" >
< a href = "#" id = "btn-logout" class = "logout-link" > Keluar< / a >
< / div >
< / div >
< / div >
<!-- Modal Scan QR Absen Mapel -->
< div id = "scan-qr-modal" class = "modal modal-overlay hidden" >
< div class = "modal-backdrop" id = "scan-qr-backdrop" > < / div >
< div class = "modal-panel" >
< div class = "modal-panel-header" >
< h2 class = "modal-panel-title" > Check-in Mapel< / h2 >
< button type = "button" id = "scan-qr-close" class = "modal-panel-close" aria-label = "Tutup" > × < / button >
< / div >
< div class = "modal-panel-body" >
< p class = "modal-panel-hint" > Arahkan kamera ke QR yang ditampilkan guru di kelas.< / p >
< div class = "qr-reader-wrap" >
< div id = "qr-reader" > < / div >
< / div >
< p id = "scan-qr-status" class = "modal-panel-status" > < / p >
< / div >
< / div >
< / div >
<!-- Modal Konfirmasi PIN (setelah scan QR) -->
< div id = "qr-pin-modal" class = "modal hidden" >
< div class = "modal-backdrop" id = "qr-pin-backdrop" > < / div >
< div class = "modal-panel" >
< div class = "modal-panel-header" >
< h2 class = "modal-panel-title" > Konfirmasi PIN< / h2 >
< button type = "button" id = "qr-pin-close" class = "modal-panel-close" > × < / button >
< / div >
< div class = "modal-panel-body" >
< p class = "modal-panel-hint" > Masukkan PIN untuk mengirim absen mapel.< / p >
< div class = "form-group" >
< label class = "input-label" for = "qr-pin-input" > PIN< / label >
< input id = "qr-pin-input" type = "password" inputmode = "numeric" maxlength = "6" placeholder = "Masukkan PIN" class = "input-android input-full" >
< / div >
< div class = "modal-panel-actions modal-panel-actions-row" >
< button type = "button" id = "qr-pin-cancel" class = "btn-outline" > Batal< / button >
< button type = "button" id = "qr-pin-submit" class = "btn-primary" > Kirim Absen< / button >
< / div >
< / div >
< / div >
< / div >
<!-- Modal Kamera (verifikasi wajah sebelum check - in) -->
< div id = "camera-modal" class = "modal modal-overlay hidden" >
< div class = "camera-modal-backdrop" id = "camera-backdrop" > < / div >
< div class = "modal-panel modal-panel-camera" >
< div class = "modal-panel-header" >
< h2 class = "modal-panel-title" > Verifikasi Wajah - Smart Presensi< / h2 >
< button type = "button" id = "camera-modal-close" class = "modal-panel-close" aria-label = "Tutup" > × < / button >
< / div >
< div class = "modal-panel-body" >
< p class = "modal-panel-hint" > Smart Presensi: arahkan wajah ke kamera. Setelah terverifikasi, Anda akan diminta PIN untuk menyelesaikan absen.< / p >
< p id = "camera-face-status" class = "camera-face-status hidden" > < / p >
< div class = "camera-frame" >
< video id = "camera-video" class = "camera-video" playsinline autoplay muted > < / video >
< div id = "camera-error" class = "camera-frame-error hidden" > Kamera tidak tersedia atau izin ditolak.< / div >
< / div >
< div class = "modal-panel-actions" >
< button type = "button" id = "camera-btn-cancel" class = "btn-outline" > Batal< / button >
< / div >
< / div >
< / div >
< / div >
2026-03-08 14:32:13 +07:00
<!-- Modal Rekam Wajah (saat daftar pertama) -->
< div id = "enroll-face-modal" class = "modal modal-overlay hidden" >
< div class = "modal-backdrop" id = "enroll-face-backdrop" > < / div >
< div class = "modal-panel modal-panel-camera" >
< div class = "modal-panel-header" >
< h2 class = "modal-panel-title" > Rekam Wajah (Wajib)< / h2 >
< / div >
< div class = "modal-panel-body" >
< p class = "modal-panel-hint" > Arahkan wajah ke kamera lalu tekan < strong > Rekam< / strong > . Data wajah dari HP dipakai untuk verifikasi saat absen masuk/pulang.< / p >
< p id = "enroll-face-status" class = "camera-face-status hidden" > < / p >
< div class = "camera-frame" >
< video id = "enroll-face-video" class = "camera-video" playsinline autoplay muted > < / video >
< div id = "enroll-face-error" class = "camera-frame-error hidden" > Kamera tidak tersedia atau izin ditolak.< / div >
< / div >
< div class = "modal-panel-actions" >
< button type = "button" id = "enroll-face-start" class = "btn-primary btn-full" > Rekam (3– 5 foto)< / button >
< / div >
< / div >
< / div >
< / div >
2026-03-05 14:19:22 +07:00
< script src = "https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js" > < / script >
< script src = "./app.js" > < / script >
< script >
(function () {
if (window.location.protocol === 'file:') {
var el = document.getElementById('file-protocol-warning');
if (el) el.classList.remove('hidden');
}
})();
< / script >
< / body >
< / html >