// 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 && ( )} */}
); } function RoleCard({ color, icon, title, desc, onClick }) { const accent = color === 'orange' ? '#FFA94D' : '#4DABF7'; const accentDeep = color === 'orange' ? '#E8852C' : '#1971C2'; return ( ); } // ============ 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 && (
)}
{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'}

{sessionCode}
{names.map((name, idx) => (
Pion {PLAYER_NAMES_ID[idx]}
{ const n = [...names]; n[idx] = e.target.value; setNames(n); }} placeholder={`Nama pemain ${idx + 1}`} style={{ width: '100%', border: '2px solid #E5EEF4', borderRadius: 10, padding: '8px 12px', fontSize: 15, fontFamily: 'Fredoka', fontWeight: 600, color: 'var(--ink)', outline: 'none' }} onFocus={e => e.target.style.borderColor = '#FFA94D'} onBlur={e => e.target.style.borderColor = '#E5EEF4'} />
{name.trim().length >= 2 && (
โ— SIAP
)}
))}
{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}
{/* */}
{[ { 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 => ( ))}
{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}
))}
); } function BoardMonitorPanel({ players, currentTurnIdx, gameLog }) { return (
Monitor Permainan
{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) => ( ))}
JUMLAH POIN
{[-10, -5, -2, +2, +5, +10, +20].map(v => ( ))}
); } 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 => ( ))}
); } // ============ 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}
{p.score || 0}
poin
))}
๐Ÿ’ญ
Refleksi Bersama

Cyberbullying bisa terjadi pada siapa saja, tapi kita semua punya kekuatan untuk menghentikannya. Mulai hari ini, mari jadi upstander โ€” bukan sekadar bystander.

{role === 'guru' ? ( <> ) : ( )}
); } 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 && (
)}
{'๐ŸŽฒ'}

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}
)}
); } // ============ 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 (
{/* Header Vertikal */}
{/* 1. Label Mode Siswa */}
Mode Siswa
{/* 2. Area Kode Ruangan (Vertikal) */}
Kode Ruangan
{room.code}
{/* 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) => (
Pion {PLAYER_NAMES_ID[idx]}
{ const n = [...names]; n[idx] = e.target.value; setNames(n); }} placeholder={'Nama pemain ' + (idx + 1)} style={{ width: '100%', border: 'none', background: 'transparent', fontSize: 14, fontFamily: 'Fredoka', fontWeight: 700, color: 'var(--ink)', outline: 'none' }} />
{name.trim().length >= 2 && {'โœ“'}}
))}
) : (
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}
); } if (!room) return null; const filledSlots = (room.players || []).filter(p => p.name).length; return (
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 ? 'โœ…' : 'โณ'}
))}
{!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, });