// MODIBU shared components const { useState, useEffect, useRef, useMemo } = React; // ============ Logo ============ function ModibuLogo({ size = 48, withTagline = false }) { return (
Monopoli CyberCare
{withTagline && (
Bermain, belajar, dan peduli
cegah cyberbullying!
)}
); } // ============ Decorative background ============ function CloudBG({ density = 'normal' }) { return ( ); } function Cloud({ style }) { // simple cloud: a pill + two circles via pseudos (CSS .c) return
; } // ============ Skyline (silhouette of buildings at bottom) ============ function Skyline() { return ( {/* Back row */} {/* Front row darker */} {/* Windows dots */} {Array.from({ length: 48 }).map((_, i) => ( ))} ); } // ============ Avatar ============ function Avatar({ color, label, size = 56, active = false }) { const cls = `avatar ${color}${active ? ' active' : ''}`; const sizeStr = typeof size === 'number' ? `${size}px` : size; const fontSize = typeof size === 'number' ? `${size * 0.4}px` : `calc(${size} * 0.4)`; const isImage = label && (label.startsWith('/') || label.includes('.png')); return (
{isImage ? ( icon ) : label}
); } // ============ Pawn ============ function Pawn({ color, icon, size = 1 }) { const isImage = icon && (icon.startsWith('/') || icon.includes('.png')); if (isImage) { return (
pawn
); } return (
); } // ============ Dice ============ const DICE_FACES = { 1: [4], 2: [0, 8], 3: [0, 4, 8], 4: [0, 2, 6, 8], 5: [0, 2, 4, 6, 8], 6: [0, 2, 3, 5, 6, 8], }; function Dice({ value, rolling, size = 72 }) { const dots = DICE_FACES[value] || [4]; const sizeStr = typeof size === 'number' ? `${size}px` : size; return (
{Array.from({ length: 9 }).map((_, i) => (
))}
); } // ============ Score Pill ============ function ScorePill({ player, active }) { return (
{player.name} {player.score} poin
); } // ============ Tile icon (category) ============ function TileIcon({ type, size = 22 }) { const s = typeof size === 'string' ? size : `${size}px`; // Mapping to new public sticker images const StickerMap = { edukasi: '/book.png', fakta: '/fun-fact.png', tantangan: '/target.png', refleksi: '/thinking.png', kesempatan: '/dice-game.png', hukuman: '/shock.png', start: '/lets-go.png', finish: '/trophy.png', bonus: '/celebrating.png', penjara: '/not-accept.png' }; const src = StickerMap[type]; if (src) { return ( {type} ); } // Fallback to SVGs if image missing const Icons = { edukasi: ( ), fakta: ( ), tantangan: ( ), refleksi: ( ), kesempatan: ( ), hukuman: ( ), start: ( ), finish: ( ), penjara: ( ), bonus: ( ), }; return Icons[type] || null; } // ============ Chip ============ function Chip({ color, label, icon }) { return ( {color && } {icon} {label} ); } // ============ Section header ============ function SectionHeader({ title, sub, accent }) { return (
{accent && } {title}
{sub &&
{sub}
}
); } // ============ Decorative monochrome backdrop pattern for tile ============ function TilePattern() { return (