<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GAME KAMERA BENAR ATAU SALAH</title>
<style>
/* === RESET & VARIABEL === */
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--success: #16a34a;
--danger: #dc2626;
--warning: #eab308;
--dark: #1e293b;
--light: #f8fafc;
--gray: #94a3b8;
--border: #cbd5e1;
--bg: #e2e8f0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
user-select: none;
}
body {
background-color: #000;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
overflow: hidden;
}
/* === LAYOUT UTAMA 16:9 === */
#app-container {
position: relative;
background-color: var(--bg);
width: 100%;
max-width: 100vw;
max-height: 100vh;
aspect-ratio: 16 / 9;
overflow: hidden;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
}
/* === HEADER UTAMA === */
header {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 50;
pointer-events: none;
}
.btn-header {
pointer-events: auto;
background: rgba(255, 255, 255, 0.9);
border: 2px solid var(--dark);
color: var(--dark);
padding: 8px 16px;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
transition: all 0.2s;
}
.btn-header:hover { background: var(--dark); color: white; }
#btn-stop-game { display: none; background: var(--danger); color: white; border-color: var(--danger); }
#btn-stop-game:hover { background: #b91c1c; }
/* === FOOTER CREATOR === */
footer {
position: absolute;
bottom: 10px;
left: 0;
right: 0;
text-align: center;
font-size: 14px;
font-weight: bold;
color: var(--dark);
z-index: 40;
opacity: 0.7;
}
/* === HALAMAN === */
.screen {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
display: flex;
flex-direction: column;
padding: 60px 20px 40px 20px;
z-index: 10;
background-color: var(--bg);
transition: opacity 0.3s;
}
.hidden { display: none !important; opacity: 0; z-index: -1; }
/* === BERANDA === */
#home-screen {
align-items: center;
justify-content: center;
}
.app-title {
font-size: 3.5vw;
color: var(--dark);
text-transform: uppercase;
text-shadow: 2px 2px 0px white;
margin-bottom: 30px;
text-align: center;
}
.info-panel {
background: white;
padding: 20px 40px;
border-radius: 15px;
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
display: flex;
gap: 40px;
margin-bottom: 40px;
border: 3px solid var(--border);
}
.info-item { text-align: center; }
.info-label { font-size: 1vw; color: var(--gray); font-weight: bold; }
.info-value { font-size: 1.5vw; color: var(--dark); font-weight: 900; }
.btn-group { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; }
.btn-main {
padding: 15px 30px;
font-size: 1.2vw;
font-weight: bold;
border-radius: 12px;
cursor: pointer;
border: none;
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
transition: transform 0.1s, filter 0.2s;
}
.btn-main:active { transform: scale(0.95); }
.btn-main:disabled { background: var(--gray); cursor: not-allowed; transform: none; }
.btn-start { background: var(--success); color: white; }
.btn-start:hover:not(:disabled) { background: #15803d; }
.btn-camera { background: var(--primary); color: white; }
.btn-settings { background: var(--warning); color: var(--dark); }
/* === PENGATURAN GURU (MODAL/SCREEN) === */
#settings-screen {
background: white;
overflow-y: auto;
padding: 20px;
z-index: 30;
}
.settings-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 2px solid var(--border); padding-bottom: 10px;}
.settings-header h2 { font-size: 24px; color: var(--dark); }
.btn-close { background: var(--danger); color: white; border: none; padding: 8px 15px; border-radius: 5px; cursor: pointer; font-weight: bold; }
.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; font-weight: bold; margin-bottom: 5px; color: var(--dark); }
.form-control { width: 100%; padding: 10px; border: 2px solid var(--border); border-radius: 8px; font-size: 14px; }
textarea.form-control { resize: vertical; min-height: 80px; }
.bank-soal-section { margin-top: 30px; border-top: 2px solid var(--border); padding-top: 20px;}
.bank-soal-header { display: flex; justify-content: space-between; margin-bottom: 15px; }
.table-container { overflow-x: auto; max-height: 300px; border: 1px solid var(--border); border-radius: 8px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid var(--border); font-size: 14px; }
th { background: var(--light); position: sticky; top: 0; }
.btn-sm { padding: 5px 10px; font-size: 12px; border: none; border-radius: 4px; cursor: pointer; margin-right: 5px; color: white;}
.btn-edit { background: var(--warning); color: black; }
.btn-del { background: var(--danger); }
/* Modal Form Soal */
#modal-soal { position: absolute; top:0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.8); z-index: 100; display: flex; justify-content: center; align-items: center; }
.modal-content { background: white; width: 90%; max-width: 600px; padding: 20px; border-radius: 10px; }
/* === LAYOUT GAME === */
#game-screen {
justify-content: flex-start;
align-items: center;
padding-top: 60px;
}
.game-top-panel {
width: 90%;
display: flex;
justify-content: space-between;
align-items: center;
background: white;
padding: 10px 20px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 15px;
border: 2px solid var(--dark);
}
.score-box { display: flex; align-items: center; gap: 10px; font-size: 1.5vw; font-weight: bold; color: var(--dark); }
.score-val { background: var(--bg); padding: 5px 15px; border-radius: 8px; border: 2px solid var(--border); }
.score-btn { background: var(--primary); color: white; border: none; width: 30px; height: 30px; border-radius: 5px; cursor: pointer; font-size: 20px; font-weight: bold; line-height: 1; }
.question-panel {
flex-grow: 1;
text-align: center;
padding: 0 20px;
}
.question-number { font-size: 1vw; color: var(--gray); font-weight: bold; }
.question-text { font-size: 2vw; font-weight: 900; color: var(--dark); line-height: 1.2; margin-top: 5px; }
.timer-box { font-size: 2.5vw; font-weight: 900; color: var(--danger); background: #fee2e2; padding: 5px 20px; border-radius: 10px; border: 2px solid var(--danger); }
/* === KAMERA CONTAINER (15:6) === */
.camera-wrapper {
position: relative;
width: 90%;
aspect-ratio: 15 / 6;
background: #000;
border: 4px solid var(--dark);
border-radius: 15px;
overflow: hidden;
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
}
#video-feed { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); /* Mirror */ }
/* Garis Tengah & Area */
.camera-divider {
position: absolute;
top: 0; bottom: 0; left: 50%;
width: 4px;
background-color: black;
transform: translateX(-50%);
z-index: 5;
}
.cam-box {
position: absolute;
top: 0; bottom: 0;
width: 50%;
border: 4px solid black;
z-index: 4;
pointer-events: none;
transition: background-color 0.3s;
}
.cam-box.left { left: 0; border-right: none; }
.cam-box.right { right: 0; border-left: none; }
.box-label {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
font-size: 2vw;
font-weight: 900;
color: black;
background: rgba(255,255,255,0.7);
padding: 5px 15px;
border-radius: 8px;
border: 2px solid black;
}
/* Overlay Jawaban */
#answer-reveal {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: var(--warning);
color: black;
padding: 15px 40px;
font-size: 3vw;
font-weight: 900;
border: 6px solid black;
border-radius: 15px;
z-index: 10;
box-shadow: 0 10px 20px rgba(0,0,0,0.5);
display: none;
}
.overlay-green { background-color: rgba(22, 163, 74, 0.6) !important; }
.overlay-red { background-color: rgba(220, 38, 38, 0.6) !important; }
/* Test Camera Modal */
#test-cam-modal { position: absolute; top:0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.9); z-index: 100; display: flex; flex-direction:column; justify-content: center; align-items: center; }
#test-video { width: 60%; aspect-ratio: 16/9; background: #222; border: 2px solid white; transform: scaleX(-1); object-fit: cover; }
</style>
</head>
<body>
<div id="app-container">
<!-- Header Controls -->
<header>
<button id="btn-stop-game" class="btn-header" onclick="stopGame()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>
Hentikan Game
</button>
<div style="flex-grow: 1;"></div>
<button id="btn-fullscreen" class="btn-header" onclick="toggleFullscreen()">
<svg id="icon-fs-enter" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>
<span id="text-fs">Layar Penuh</span>
</button>
</header>
<!-- 1. HALAMAN BERANDA -->
<div id="home-screen" class="screen">
<h1 class="app-title">GAME KAMERA BENAR ATAU SALAH</h1>
<div class="info-panel">
<div class="info-item">
<div class="info-label">Jumlah Soal</div>
<div class="info-value" id="info-jml-soal">0 Soal</div>
</div>
<div class="info-item">
<div class="info-label">Waktu Per Soal</div>
<div class="info-value" id="info-waktu">10 Detik</div>
</div>
</div>
<div class="btn-group">
<button class="btn-main btn-start" id="btn-start" onclick="startGame()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
Mulai Game
</button>
<button class="btn-main btn-camera" onclick="openTestCamera()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg>
Aktifkan / Uji Kamera
</button>
<button class="btn-main btn-settings" onclick="showScreen('settings-screen')">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
Pengaturan Guru
</button>
</div>
</div>
<!-- 2. PENGATURAN GURU & BANK SOAL -->
<div id="settings-screen" class="screen hidden">
<div class="settings-header">
<h2>Pengaturan Guru & Konfigurasi Pembelajaran</h2>
<button class="btn-close" onclick="showScreen('home-screen')">Kembali ke Beranda</button>
</div>
<div class="settings-grid">
<div>
<div class="form-group">
<label>Jenjang Pendidikan & Kelas</label>
<input type="text" id="cfg-jenjang" class="form-control" value="SMP / Kelas 7">
</div>
<div class="form-group">
<label>Mata Pelajaran</label>
<input type="text" id="cfg-mapel" class="form-control" value="Pendidikan Agama Islam">
</div>
<div class="form-group">
<label>Topik / Materi Pembelajaran</label>
<input type="text" id="cfg-topik" class="form-control" value="Meneladani Sifat-Sifat Allah SWT melalui Asmaulhusna">
</div>
</div>
<div>
<div class="form-group">
<label>Waktu Per Soal (Detik)</label>
<input type="number" id="cfg-waktu" class="form-control" value="10" min="5" max="60">
</div>
<div class="form-group">
<label>Tujuan Pembelajaran</label>
<textarea id="cfg-tujuan" class="form-control">Menjelaskan Al-Alim, Al-Khabir, As-Sami', Al-Bashir, Al-Aziz, menganalisis bukti fenomena, & melafalkan dalil naqli.</textarea>
</div>
<div class="form-group">
<label>Petunjuk / Aturan Tambahan</label>
<textarea id="cfg-aturan" class="form-control">Berdirilah di area kamera yang sesuai dengan jawaban pilihan kelompok kalian (Kiri untuk A. BENAR, Kanan untuk B. SALAH).</textarea>
</div>
</div>
</div>
<button class="btn-main btn-start" style="padding: 10px 20px; font-size:14px; margin-top:10px;" onclick="saveConfig()">Simpan Konfigurasi</button>
<div class="bank-soal-section">
<div class="bank-soal-header">
<h2>Bank Soal</h2>
<button class="btn-main btn-camera" style="padding: 8px 15px; font-size:14px; border-radius:5px;" onclick="openModalSoal()">+ Tambah Soal</button>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th width="5%">No</th>
<th width="45%">Pertanyaan</th>
<th width="15%">Kunci</th>
<th width="15%">Kesulitan</th>
<th width="20%">Aksi</th>
</tr>
</thead>
<tbody id="table-soal-body">
<!-- Data Soal dirender JS -->
</tbody>
</table>
</div>
</div>
</div>
<!-- 3. GAMEPLAY SCREEN -->
<div id="game-screen" class="screen hidden">
<!-- Top Panel (Skor, Soal, Timer) -->
<div class="game-top-panel">
<div class="score-box">
<span>Tim Kiri (A)</span>
<button class="score-btn" onclick="updateScore('A', -1)">-</button>
<div class="score-val" id="score-a">0</div>
<button class="score-btn" onclick="updateScore('A', 1)">+</button>
</div>
<div class="question-panel">
<div class="question-number" id="game-q-num">Soal 1 dari 10</div>
<div class="question-text" id="game-q-text">Pertanyaan akan muncul di sini...</div>
</div>
<div class="score-box">
<div class="timer-box" id="game-timer">10</div>
</div>
<div class="score-box">
<button class="score-btn" onclick="updateScore('B', -1)">-</button>
<div class="score-val" id="score-b">0</div>
<button class="score-btn" onclick="updateScore('B', 1)">+</button>
<span>Tim Kanan (B)</span>
</div>
</div>
<!-- Camera Area 15:6 -->
<div class="camera-wrapper">
<video id="game-video" autoplay playsinline muted></video>
<!-- Kotak Pembatas -->
<div class="cam-box left" id="box-left">
<div class="box-label">A. BENAR</div>
</div>
<div class="camera-divider"></div>
<div class="cam-box right" id="box-right">
<div class="box-label">B. SALAH</div>
</div>
<!-- Overlay Jawaban Tengah -->
<div id="answer-reveal">JAWABAN A</div>
</div>
</div>
<!-- Footer Creator -->
<footer>Creator: Samrin Umasugi</footer>
</div>
<!-- Modal Tambah/Edit Soal -->
<div id="modal-soal" class="hidden">
<div class="modal-content">
<h3 id="modal-title" style="margin-bottom:15px; color:var(--dark);">Tambah Soal</h3>
<input type="hidden" id="soal-id">
<div class="form-group">
<label>Pertanyaan</label>
<textarea id="soal-pertanyaan" class="form-control" rows="3"></textarea>
</div>
<div class="form-group">
<label>Kunci Jawaban</label>
<select id="soal-kunci" class="form-control">
<option value="A">A. Benar</option>
<option value="B">B. Salah</option>
</select>
</div>
<div class="form-group">
<label>Tingkat Kesulitan</label>
<select id="soal-kesulitan" class="form-control">
<option value="Mudah">Mudah</option>
<option value="Sedang" selected>Sedang</option>
<option value="Sulit">Sulit</option>
</select>
</div>
<div class="form-group">
<label>Pembahasan (Opsional, untuk arsip)</label>
<textarea id="soal-pembahasan" class="form-control" rows="2"></textarea>
</div>
<div style="display:flex; gap:10px; justify-content:flex-end; margin-top:20px;">
<button class="btn-main" style="padding:8px 15px; font-size:14px; background:var(--gray);" onclick="closeModalSoal()">Batal</button>
<button class="btn-main btn-start" style="padding:8px 15px; font-size:14px;" onclick="saveSoal()">Simpan Soal</button>
</div>
</div>
</div>
<!-- Modal Test Kamera -->
<div id="test-cam-modal" class="hidden">
<h2 style="color:white; margin-bottom:20px;">Uji Coba Kamera</h2>
<video id="test-video" autoplay playsinline muted></video>
<div style="margin-top:20px; display:flex; gap:15px;">
<button class="btn-main btn-start" onclick="closeTestCamera()">Tutup & Simpan</button>
</div>
</div>
<script>
// === DATA & STATE MANAGEMENT ===
const DEFAULT_QUESTIONS = [
{ id: 1, pertanyaan: "Allah Maha Mengetahui segala sesuatu, baik yang tampak maupun gaib. Ini adalah makna dari Al-Alim.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "A", pembahasan: "Al-Alim berarti Maha Mengetahui.", tingkat_kesulitan: "Mudah" },
{ id: 2, pertanyaan: "Al-Khabir berarti Allah Maha Mendengar segala ucapan hamba-Nya.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "B", pembahasan: "Al-Khabir berarti Maha Teliti/Waspada, Maha Mendengar adalah As-Sami'.", tingkat_kesulitan: "Sedang" },
{ id: 3, pertanyaan: "Bukti Allah As-Sami' adalah Allah mengabulkan doa hamba-Nya yang diucapkan dalam hati.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "A", pembahasan: "Allah Maha Mendengar segalanya termasuk yang di dalam hati.", tingkat_kesulitan: "Mudah" },
{ id: 4, pertanyaan: "Al-Bashir menunjukkan bahwa penglihatan Allah terbatas pada hal-hal yang besar saja.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "B", pembahasan: "Allah Maha Melihat segala sesuatu, bahkan yang terkecil sekalipun.", tingkat_kesulitan: "Sedang" },
{ id: 5, pertanyaan: "Sifat Al-Aziz berarti Allah Maha Perkasa dan tidak ada yang bisa mengalahkan-Nya.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "A", pembahasan: "Al-Aziz bermakna Maha Perkasa/Mulia.", tingkat_kesulitan: "Mudah" },
{ id: 6, pertanyaan: "Kita meneladani sifat Al-Alim dengan cara bermalas-malasan dalam belajar.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "B", pembahasan: "Harusnya rajin belajar.", tingkat_kesulitan: "Mudah" },
{ id: 7, pertanyaan: "Berhati-hati dalam bertindak karena menyadari Allah Maha Teliti adalah wujud meneladani Al-Khabir.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "A", pembahasan: "Sikap hati-hati cerminan Al-Khabir.", tingkat_kesulitan: "Sedang" },
{ id: 8, pertanyaan: "Allah As-Sami' berarti Allah hanya mendengar doa yang diucapkan dengan suara keras.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "B", pembahasan: "Allah mendengar suara sekecil apapun.", tingkat_kesulitan: "Mudah" },
{ id: 9, pertanyaan: "Menjauhi perbuatan maksiat walau saat sendirian adalah cerminan iman kepada Al-Bashir.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "A", pembahasan: "Merasa diawasi oleh Allah Yang Maha Melihat.", tingkat_kesulitan: "Sedang" },
{ id: 10, pertanyaan: "Menyombongkan diri adalah sikap yang sejalan dengan keimanan kepada sifat Al-Aziz.", jenis_soal: "Benar-Salah", pilihan: ["Benar", "Salah"], kunci_jawaban: "B", pembahasan: "Justru bertentangan, karena hanya Allah yang berhak sombong/perkasa.", tingkat_kesulitan: "Sulit" }
];
let bankSoal = JSON.parse(localStorage.getItem('kameraGame_soal')) || DEFAULT_QUESTIONS;
let configGame = JSON.parse(localStorage.getItem('kameraGame_config')) || {
jenjang: "SMP / Kelas 7", mapel: "Pendidikan Agama Islam", topik: "Meneladani Sifat-Sifat Allah SWT melalui Asmaulhusna",
waktu: 10, tujuan: "", aturan: ""
};
let streamGlobal = null;
let currentQuestionIndex = 0;
let gameTimerInterval = null;
let timeLeft = 10;
let scoreA = 0;
let scoreB = 0;
let isPlaying = false;
// === AUDIO API UNTUK EFEK SUARA OFFLINE ===
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
function playTone(freq, type, duration) {
if(audioCtx.state === 'suspended') audioCtx.resume();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = freq;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + duration);
osc.stop(audioCtx.currentTime + duration);
}
function soundCorrect() { playTone(600, 'sine', 0.2); setTimeout(()=>playTone(800, 'sine', 0.4), 200); }
function soundWrong() { playTone(300, 'sawtooth', 0.5); }
function soundTick() { playTone(1000, 'square', 0.05); }
// === INISIALISASI & NAVIGASI ===
function initApp() {
updateHomeInfo();
loadConfigToForm();
renderTableSoal();
}
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(el => el.classList.add('hidden'));
document.getElementById(screenId).classList.remove('hidden');
}
function updateHomeInfo() {
document.getElementById('info-jml-soal').innerText = `${bankSoal.length} Soal`;
document.getElementById('info-waktu').innerText = `${configGame.waktu} Detik`;
const btnStart = document.getElementById('btn-start');
if(bankSoal.length === 0) {
btnStart.disabled = true;
btnStart.title = "Bank soal kosong!";
} else {
btnStart.disabled = false;
btnStart.title = "";
}
}
// === FULLSCREEN API ===
function toggleFullscreen() {
const app = document.documentElement; // Buat fullscreen seluruh halaman
const fsBtnText = document.getElementById('text-fs');
if (!document.fullscreenElement) {
if (app.requestFullscreen) { app.requestFullscreen(); }
else if (app.webkitRequestFullscreen) { app.webkitRequestFullscreen(); }
fsBtnText.innerText = "Keluar Layar Penuh";
} else {
if (document.exitFullscreen) { document.exitFullscreen(); }
else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); }
fsBtnText.innerText = "Layar Penuh";
}
}
document.addEventListener('fullscreenchange', () => {
const fsBtnText = document.getElementById('text-fs');
if (!document.fullscreenElement) {
fsBtnText.innerText = "Layar Penuh";
} else {
fsBtnText.innerText = "Keluar Layar Penuh";
}
});
// === KAMERA API ===
async function startCamera(videoElementId) {
try {
if(streamGlobal) {
streamGlobal.getTracks().forEach(track => track.stop());
}
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: "user" } });
streamGlobal = stream;
const videoEl = document.getElementById(videoElementId);
videoEl.srcObject = stream;
videoEl.play();
return true;
} catch (err) {
alert("Gagal mengakses kamera! Pastikan browser memiliki izin akses kamera.");
console.error(err);
return false;
}
}
function stopCamera() {
if (streamGlobal) {
streamGlobal.getTracks().forEach(track => track.stop());
streamGlobal = null;
}
}
// Modal Uji Kamera
async function openTestCamera() {
document.getElementById('test-cam-modal').classList.remove('hidden');
await startCamera('test-video');
}
function closeTestCamera() {
document.getElementById('test-cam-modal').classList.add('hidden');
stopCamera();
}
// === PENGATURAN & CRUD SOAL ===
function loadConfigToForm() {
document.getElementById('cfg-jenjang').value = configGame.jenjang;
document.getElementById('cfg-mapel').value = configGame.mapel;
document.getElementById('cfg-topik').value = configGame.topik;
document.getElementById('cfg-waktu').value = configGame.waktu;
document.getElementById('cfg-tujuan').value = configGame.tujuan;
document.getElementById('cfg-aturan').value = configGame.aturan;
}
function saveConfig() {
configGame = {
jenjang: document.getElementById('cfg-jenjang').value,
mapel: document.getElementById('cfg-mapel').value,
topik: document.getElementById('cfg-topik').value,
waktu: parseInt(document.getElementById('cfg-waktu').value) || 10,
tujuan: document.getElementById('cfg-tujuan').value,
aturan: document.getElementById('cfg-aturan').value
};
localStorage.setItem('kameraGame_config', JSON.stringify(configGame));
updateHomeInfo();
alert("Konfigurasi berhasil disimpan!");
}
function renderTableSoal() {
const tbody = document.getElementById('table-soal-body');
tbody.innerHTML = '';
bankSoal.forEach((soal, index) => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${index + 1}</td>
<td>${soal.pertanyaan}</td>
<td>${soal.kunci_jawaban}</td>
<td>${soal.tingkat_kesulitan}</td>
<td>
<button class="btn-sm btn-edit" onclick="editSoal(${soal.id})">Edit</button>
<button class="btn-sm btn-del" onclick="hapusSoal(${soal.id})">Hapus</button>
</td>
`;
tbody.appendChild(tr);
});
localStorage.setItem('kameraGame_soal', JSON.stringify(bankSoal));
updateHomeInfo();
}
function openModalSoal(id = null) {
document.getElementById('modal-soal').classList.remove('hidden');
if(id) {
document.getElementById('modal-title').innerText = "Edit Soal";
const soal = bankSoal.find(s => s.id === id);
document.getElementById('soal-id').value = soal.id;
document.getElementById('soal-pertanyaan').value = soal.pertanyaan;
document.getElementById('soal-kunci').value = soal.kunci_jawaban;
document.getElementById('soal-kesulitan').value = soal.tingkat_kesulitan;
document.getElementById('soal-pembahasan').value = soal.pembahasan;
} else {
document.getElementById('modal-title').innerText = "Tambah Soal";
document.getElementById('soal-id').value = '';
document.getElementById('soal-pertanyaan').value = '';
document.getElementById('soal-kunci').value = 'A';
document.getElementById('soal-kesulitan').value = 'Sedang';
document.getElementById('soal-pembahasan').value = '';
}
}
function closeModalSoal() { document.getElementById('modal-soal').classList.add('hidden'); }
function saveSoal() {
const id = document.getElementById('soal-id').value;
const baru = {
id: id ? parseInt(id) : Date.now(),
pertanyaan: document.getElementById('soal-pertanyaan').value,
jenis_soal: "Benar-Salah",
pilihan: ["Benar", "Salah"],
kunci_jawaban: document.getElementById('soal-kunci').value,
tingkat_kesulitan: document.getElementById('soal-kesulitan').value,
pembahasan: document.getElementById('soal-pembahasan').value
};
if(!baru.pertanyaan) return alert("Pertanyaan tidak boleh kosong!");
if(id) {
const idx = bankSoal.findIndex(s => s.id === parseInt(id));
bankSoal[idx] = baru;
} else {
bankSoal.push(baru);
}
closeModalSoal();
renderTableSoal();
}
function hapusSoal(id) {
if(confirm("Yakin ingin menghapus soal ini?")) {
bankSoal = bankSoal.filter(s => s.id !== id);
renderTableSoal();
}
}
function editSoal(id) { openModalSoal(id); }
// === GAMEPLAY LOGIC ===
async function startGame() {
if (bankSoal.length === 0) {
alert("Bank soal belum tersedia! Silakan tambahkan soal terlebih dahulu.");
return;
}
showScreen('game-screen');
const camReady = await startCamera('game-video');
if(!camReady) {
showScreen('home-screen');
return;
}
document.getElementById('btn-stop-game').style.display = 'flex';
isPlaying = true;
currentQuestionIndex = 0;
scoreA = 0;
scoreB = 0;
document.getElementById('score-a').innerText = scoreA;
document.getElementById('score-b').innerText = scoreB;
resetBoxColors();
loadQuestion();
}
function stopGame() {
isPlaying = false;
clearInterval(gameTimerInterval);
stopCamera();
document.getElementById('btn-stop-game').style.display = 'none';
showScreen('home-screen');
}
function updateScore(team, val) {
if(!isPlaying) return;
if(team === 'A') { scoreA += val; document.getElementById('score-a').innerText = scoreA; }
if(team === 'B') { scoreB += val; document.getElementById('score-b').innerText = scoreB; }
}
function loadQuestion() {
if(currentQuestionIndex >= bankSoal.length) {
alert(`Game Selesai!\nSkor Tim A: ${scoreA}\nSkor Tim B: ${scoreB}`);
stopGame();
return;
}
const q = bankSoal[currentQuestionIndex];
document.getElementById('game-q-num').innerText = `Soal ${currentQuestionIndex + 1} dari ${bankSoal.length}`;
document.getElementById('game-q-text').innerText = q.pertanyaan;
timeLeft = configGame.waktu;
document.getElementById('game-timer').innerText = timeLeft;
resetBoxColors();
gameTimerInterval = setInterval(() => {
timeLeft--;
document.getElementById('game-timer').innerText = timeLeft;
if(timeLeft > 0 && timeLeft <= 3) soundTick();
if(timeLeft <= 0) {
clearInterval(gameTimerInterval);
revealAnswer(q.kunci_jawaban);
}
}, 1000);
}
function revealAnswer(kunci) {
const overlay = document.getElementById('answer-reveal');
const boxLeft = document.getElementById('box-left');
const boxRight = document.getElementById('box-right');
overlay.innerText = `JAWABAN ${kunci}`;
overlay.style.display = 'block';
if(kunci === 'A') {
boxLeft.classList.add('overlay-green');
boxRight.classList.add('overlay-red');
soundCorrect();
} else {
boxRight.classList.add('overlay-green');
boxLeft.classList.add('overlay-red');
soundCorrect(); // Gunakan suara yang sama atau bedakan sesuai selera
}
// Tahan 3 detik lalu lanjut soal
setTimeout(() => {
overlay.style.display = 'none';
currentQuestionIndex++;
loadQuestion();
}, 3000);
}
function resetBoxColors() {
document.getElementById('box-left').className = 'cam-box left';
document.getElementById('box-right').className = 'cam-box right';
document.getElementById('answer-reveal').style.display = 'none';
}
// Jalankan init saat pertama kali
window.onload = initApp;
</script>
</body>
</html>

0 Komentar