// MODIBU - Screens (Landing, Lobby, TeacherPanel, Results)
const { useState: useStateS, useEffect: useEffectS } = React;
const PLAYER_COLORS = ['#FF6B6B', '#4DABF7', '#51CF66', '#FFD93D']; // Red, Blue, Green, Yellow
const PLAYER_ICONS = ['/car.png', '/cruise.png', '/motorbike.png', '/train.png'];
const PLAYER_NAMES_ID = ['Merah', 'Biru', 'Hijau', 'Kuning'];
// ============ Landing ============
function LandingScreen({ onPickRole, onReset }) {
return (
Belajar mengenali, mencegah, dan melawan cyberbullying melalui permainan papan digital yang seru โ bersama teman dan Guru BK.
onPickRole('siswa')} />
onPickRole('guru')} />
{/* {onReset && (
๐ Reset Permainan (Hapus Data Sesi)
)} */}
);
}
function RoleCard({ color, icon, title, desc, onClick }) {
const accent = color === 'orange' ? '#FFA94D' : '#4DABF7';
const accentDeep = color === 'orange' ? '#E8852C' : '#1971C2';
return (
e.currentTarget.style.transform = 'translateY(2px)'}
onMouseUp={e => e.currentTarget.style.transform = ''}
onMouseLeave={e => e.currentTarget.style.transform = ''}>
{icon}
{title}
{desc}
);
}
// ============ Lobby ============
function LobbyScreen({ role, sessionCode, onStart, onBack }) {
const [names, setNames] = useStateS(['', '', '', '']);
const allFilled = names.every(n => n.trim().length >= 2);
const handleStart = () => {
if (allFilled) onStart(names.map(n => n.trim()));
};
return (
{onBack && (
{
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = '0 7px 0 #4DABF788';
}}
onMouseLeave={e => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 5px 0 #4DABF788';
}}
onMouseDown={e => {
e.currentTarget.style.transform = 'translateY(3px)';
e.currentTarget.style.boxShadow = '0 2px 0 #4DABF788';
}}
onMouseUp={e => {
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = '0 7px 0 #4DABF788';
}}
>
โ
Kembali
)}
{role === 'guru' ? 'Mode Guru BK' : 'Mode Siswa'}
Ruang Tunggu
{role === 'guru' ? 'Masukkan nama 4 siswa untuk memulai sesi' : 'Masukkan nama 4 pemain untuk memulai permainan'}
{names.map((name, idx) => (
{name.trim().length >= 2 && (
โ SIAP
)}
))}
๐ฒ Mulai Permainan
{allFilled ? 'Semua pemain siap!' : 'Isi nama minimal 2 huruf untuk setiap pemain'}
);
}
// ============ Teacher Panel ============
function TeacherPanel({ players, currentTurnIdx, sessionCode, round, gradingQueue, gameLog, onGrade, onBack, onEndGame, onManualPoints, onTriggerMaterial, onReset }) {
const [tab, setTab] = useStateS('antrian');
return (
โ
Panel Guru BK
Sesi {sessionCode} ยท 4 pemain ยท Ronde {round}
{/* Reset */}
Akhiri Sesi
{[
{ id: 'antrian', label: '๐ฅ Antrian Nilai', badge: gradingQueue.length },
{ id: 'monitor', label: '๐ฎ Monitor Papan' },
{ id: 'poin', label: 'โ Set Poin' },
{ id: 'materi', label: '๐ก Trigger Materi' },
].map(t => (
setTab(t.id)} className="btn btn-sm"
style={{ background: tab === t.id ? 'var(--ink)' : 'white', color: tab === t.id ? 'white' : 'var(--ink)', boxShadow: tab === t.id ? '0 4px 0 #000' : '0 3px 0 rgba(0,0,0,0.06)', border: tab === t.id ? '3px solid white' : '2px solid #E5EEF4', position: 'relative' }}>
{t.label}
{t.badge > 0 && {t.badge} }
))}
{tab === 'antrian' &&
}
{tab === 'monitor' &&
}
{tab === 'poin' &&
}
{tab === 'materi' &&
}
);
}
function GradingQueuePanel({ queue, onGrade }) {
if (queue.length === 0) {
return (
Semua jawaban sudah dinilai!
Tunggu giliran pemain berikutnya.
);
}
return (
{queue.map(s => (
{s.player.name}
{s.type === 'tantangan' ? 'Tantangan' : 'Refleksi'}
Soal: {s.question}
{s.answer}
onGrade(s.id, 20)}>โ Benar (+20)
onGrade(s.id, 10)}>โ Cukup (+10)
onGrade(s.id, 0)}>โ Salah (+0)
))}
);
}
function BoardMonitorPanel({ players, currentTurnIdx, gameLog }) {
return (
{players.map((p, idx) => (
{p.name}
Kotak #{p.position} ยท {p.score} poin{p.isInJail ? ' ยท ๐ Penjara' : ''}
{idx === currentTurnIdx &&
GILIRAN }
))}
Log aktivitas:
{gameLog.slice(0, 10).map((l, i) =>
โข {l.time} โ {l.msg}
)}
{gameLog.length === 0 &&
Belum ada aktivitas.
}
);
}
function ManualPointsPanelReal({ players, onManualPoints }) {
const [selectedPlayer, setSelectedPlayer] = useStateS(0);
const [amount, setAmount] = useStateS(5);
return (
Atur Poin Manual
Tambah atau kurangi poin untuk situasi khusus.
PILIH PEMAIN
{players.map((p, i) => (
setSelectedPlayer(i)} style={{
background: selectedPlayer === i ? `var(--p-${p.color === 'red' ? 'merah' : p.color === 'blue' ? 'biru' : p.color === 'green' ? 'hijau' : 'kuning'})` : 'white',
border: selectedPlayer === i ? '3px solid var(--ink)' : '3px solid #E5EEF4',
borderRadius: 18, padding: 12, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 10,
color: selectedPlayer === i ? 'white' : 'var(--ink)', fontFamily: 'inherit', fontWeight: 600
}}>
{p.name}
))}
JUMLAH POIN
{[-10, -5, -2, +2, +5, +10, +20].map(v => (
setAmount(v)} className={`btn btn-sm ${amount === v ? (v < 0 ? 'btn-red' : 'btn-green') : 'btn-ghost'}`} style={{ minWidth: 64 }}>
{v > 0 ? '+' : ''}{v}
))}
onManualPoints(selectedPlayer, amount)}>
Terapkan {amount > 0 ? '+' : ''}{amount} ke {players[selectedPlayer]?.name}
);
}
function MaterialTriggerPanel({ onTrigger }) {
const materials = [
{ id: 1, title: 'Apa itu Cyberbullying?', icon: '๐ฑ', color: '#51CF66' },
{ id: 2, title: 'Dampak Psikologis', icon: '๐', color: '#FF6B6B' },
{ id: 3, title: 'Cara Melapor', icon: '๐', color: '#4DABF7' },
{ id: 4, title: 'Empati Digital', icon: '๐ก', color: '#9775FA' },
{ id: 5, title: 'UU ITE Singkat', icon: 'โ๏ธ', color: '#FFA94D' },
{ id: 6, title: 'Tips Aman Medsos', icon: '๐ก๏ธ', color: '#FF8FAB' },
];
return (
Trigger Popup Materi
Tampilkan materi edukasi singkat di layar semua pemain.
{materials.map(m => (
{m.icon}
{m.title}
Tampilkan ke semua pemain
))}
);
}
// ============ Results ============
function ResultsScreen({ role, players, sessionCode, onPlayAgain, onHome, onReset }) {
const ranked = [...(players || [])]
.sort((a, b) => (b?.score || 0) - (a?.score || 0))
.map((p, i) => ({ ...p, rank: i + 1 }));
if (!ranked.length) {
return (
Memuat hasil...
);
}
return (
Selamat! ๐
Permainan selesai. Berikut hasil dan refleksi sesi kalian.
{ranked.length >= 3 && (
{ranked[1] &&
}
{ranked[0] &&
}
{ranked[2] &&
}
)}
Detail Skor
{ranked.map(p => (
{p.rank}
{p.name || 'Pemain'}
Edukasi: {p.stats?.edukasi || 0} ยท Fakta: {p.stats?.fakta || 0} ยท Tantangan: {p.stats?.tantangan || 0} ยท Refleksi: {p.stats?.refleksi || 0}
))}
Cyberbullying bisa terjadi pada siapa saja, tapi kita semua punya kekuatan untuk menghentikannya.
Mulai hari ini, mari jadi upstander โ bukan sekadar bystander.
{role === 'guru' ? (
<>
๐ฒ Main Lagi
๐๏ธ Hapus Sesi
>
) : (
๐ Kembali ke Beranda
)}
);
}
function PodiumStep({ player, height, accent, medal, winner }) {
if (!player) return null;
return (
{medal}
{player.name || 'Pemain'}
{player.score || 0} poin
{player.rank || '?'}
);
}
function ConfettiBackdrop() {
const dots = Array.from({ length: 24 }).map((_, i) => ({
left: (i * 4.3 + (i % 3) * 12) % 100,
top: (i * 7) % 90,
color: ['#FF6B6B', '#4DABF7', '#FFD93D', '#51CF66', '#FF8FAB', '#9775FA'][i % 6],
size: 10 + (i % 3) * 4,
rot: i * 15
}));
return (
{dots.map((d, i) => (
))}
);
}
// ============ Room Code Entry (Siswa) ============
function RoomCodeEntryScreen({ onBack, onJoin }) {
const [code, setCode] = useStateS('');
const [loading, setLoading] = useStateS(false);
const [error, setError] = useStateS('');
const handleJoin = async () => {
const trimmed = code.trim().toUpperCase();
if (trimmed.length < 3) return;
setLoading(true);
setError('');
try {
const res = await window.RoomAPI.getRoom(trimmed);
if (res.success) {
if (res.room.status === 'finished') {
setError('Sesi ini sudah selesai. Minta kode baru dari Guru BK.');
} else if (res.room.status === 'playing') {
setError('Permainan sudah dimulai. Terlambat bergabung.');
} else {
onJoin(res.room);
}
} else {
setError(res.message || 'Kode ruangan tidak ditemukan.');
}
} catch {
setError('Tidak bisa terhubung ke server. Periksa koneksi internet.');
}
setLoading(false);
};
return (
{onBack && (
{'โ'} Kembali
)}
{'๐ฒ'}
Masukkan Kode Ruangan
Minta kode MDB-XXX dari Guru BK kamu
{ setCode(e.target.value.toUpperCase()); setError(''); }}
onKeyDown={e => e.key === 'Enter' && handleJoin()}
placeholder="Contoh: MDB-X7K"
maxLength={8}
style={{ width: '100%', padding: '16px 20px', fontSize: 26, fontFamily: 'Fredoka', fontWeight: 700, textAlign: 'center', letterSpacing: '0.15em', border: '3px solid #E5EEF4', borderRadius: 16, outline: 'none', boxSizing: 'border-box', color: 'var(--ink)', background: '#F8FAFE' }}
onFocus={e => e.target.style.borderColor = '#FFA94D'}
onBlur={e => e.target.style.borderColor = '#E5EEF4'}
/>
{error && (
{'โ'} {error}
)}
{loading ? 'โณ Memeriksa...' : '๐ Masuk Ruangan'}
);
}
// ============ Student Lobby ============
function StudentLobbyScreen({ room: initialRoom, onBack, onGameStart }) {
const [room, setRoom] = useStateS(initialRoom);
const [names, setNames] = useStateS(['', '', '', '']);
const [saving, setSaving] = useStateS(false);
const [saved, setSaved] = useStateS(false);
const allFilled = names.every(n => n.trim().length >= 2);
useEffectS(() => {
if (!window.EchoInstance) return;
window.EchoInstance.on('room.' + room.code, 'room.updated', (data) => {
setRoom(data);
if (data.status === 'playing' || data.status === 'finished') onGameStart(data);
});
return () => { if (room && window.EchoInstance) window.EchoInstance.leave('room.' + room.code); };
}, [room.code]);
const handleSave = async () => {
if (!allFilled) return;
setSaving(true);
try {
const res = await window.RoomAPI.setPlayers(room.code, names.map(n => n.trim()));
if (res.success) {
setRoom(res.room);
setSaved(true);
}
} catch (err) {
alert('Gagal menyimpan nama. Periksa koneksi server.');
}
setSaving(false);
};
const filledSlots = (room.players || []).filter(p => p.name).length;
return (
{'โ'} Kembali
{/* Header Vertikal */}
{/* 1. Label Mode Siswa */}
Mode Siswa
{/* 2. Area Kode Ruangan (Vertikal) */}
{/* 3. Judul & Deskripsi */}
Ruang Tunggu Siswa
Silakan isi nama 4 pion di bawah ini, lalu tunggu Guru BK memulai sesi permainan
{!saved ? (
NAMA 4 PEMAIN
{names.map((name, idx) => (
{name.trim().length >= 2 &&
{'โ'} }
))}
{saving ? 'โณ Menyimpan...' : 'Simpan Nama Pemain'}
) : (
Nama tersimpan!
Menunggu Guru BK memulai permainan...
)}
{'โณ'} Menunggu Guru BK memulai sesi...
);
}
// ============ Teacher Lobby ============
function TeacherLobbyScreen({ onBack, onGameStart }) {
const [room, setRoom] = useStateS(null);
const [loading, setLoading] = useStateS(true);
const [error, setError] = useStateS('');
const [starting, setStarting] = useStateS(false);
const createRoom = async () => {
if (!window.RoomAPI) {
setError('Sistem API belum siap. Silakan refresh halaman.');
return;
}
setLoading(true);
setError('');
try {
const res = await window.RoomAPI.createRoom();
if (res.success) {
setRoom(res.room);
} else {
setError(res.message || 'Gagal membuat ruangan.');
}
} catch (err) {
setError('Koneksi server bermasalah: ' + (err.message || 'Unknown error'));
}
setLoading(false);
};
useEffectS(() => {
createRoom();
}, []);
useEffectS(() => {
if (!room || !window.EchoInstance) return;
window.EchoInstance.on('room.' + room.code, 'room.updated', (data) => {
setRoom(data);
if (data.status === 'playing' || data.status === 'finished') onGameStart(data);
});
return () => { if (room && window.EchoInstance) window.EchoInstance.leave('room.' + room.code); };
}, [room ? room.code : null]);
const handleStart = async () => {
if (!room || !room.is_full) return;
setStarting(true);
await window.RoomAPI.startGame(room.code);
setStarting(false);
};
if (loading) {
return (
{'โณ'}
Membuat ruangan...
);
}
if (error) {
return (
{'โ'}
{error}
Kembali
Coba Lagi
);
}
if (!room) return null;
const filledSlots = (room.players || []).filter(p => p.name).length;
return (
{'โ'} Kembali
Mode Guru BK
Ruang Tunggu Guru BK
Bagikan kode ini ke siswa, lalu tunggu semua pion terisi
KODE RUANGAN โ BAGIKAN KE SISWA
{room.code}
Siswa ketik kode ini di halaman "Mulai sebagai Siswa"
STATUS PION (LIVE)
{filledSlots === 4 ? '๐ Semua Siap!' : filledSlots + '/4 Siap'}
{(room.players || []).map((p, i) => (
{p.name || 'Belum bergabung'}
Pion {PLAYER_NAMES_ID[i]}
{p.name ? 'โ
' : 'โณ'}
))}
{starting ? 'โณ Memulai...' : room.is_full ? '๐ฎ Mulai Permainan!' : 'โณ Menunggu ' + (4 - filledSlots) + ' pemain lagi...'}
{!room.is_full &&
Tombol aktif saat semua 4 pion terisi
}
);
}
Object.assign(window, {
LandingScreen, RoleCard, LobbyScreen,
RoomCodeEntryScreen, StudentLobbyScreen, TeacherLobbyScreen,
TeacherPanel, GradingQueuePanel, BoardMonitorPanel, ManualPointsPanelReal, MaterialTriggerPanel,
ResultsScreen, PodiumStep, ConfettiBackdrop,
});