Membangun jembatan dengan cost dan bahan yang digunakan
<!doctype html>
<html lang="id" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simulasi Membangun Jembatan</title>
<script src="/_sdk/element_sdk.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
box-sizing: border-box;
}
.bridge-canvas {
background: linear-gradient(to bottom, #87CEEB 0%, #87CEEB 60%, #8FBC8F 60%, #228B22 100%);
position: relative;
overflow: hidden;
}
.water {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 40%;
background: linear-gradient(to bottom, #4682B4, #1E90FF);
animation: wave 3s ease-in-out infinite;
}
@keyframes wave {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-5px); }
}
.bridge-part {
position: absolute;
cursor: pointer;
transition: all 0.3s ease;
}
.bridge-part:hover {
transform: scale(1.05);
filter: brightness(1.1);
}
.material-card {
transition: all 0.3s ease;
cursor: pointer;
}
.material-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}
.material-card.selected {
border: 3px solid #3B82F6;
background: #EBF8FF;
}
.test-animation {
animation: testBridge 2s ease-in-out;
}
@keyframes testBridge {
0% { transform: translateY(0); }
25% { transform: translateY(-10px); }
50% { transform: translateY(5px); }
75% { transform: translateY(-5px); }
100% { transform: translateY(0); }
}
.success-glow {
animation: successGlow 1s ease-in-out;
}
@keyframes successGlow {
0%, 100% { box-shadow: 0 0 0 rgba(34, 197, 94, 0.4); }
50% { box-shadow: 0 0 30px rgba(34, 197, 94, 0.8); }
}
.failure-shake {
animation: failureShake 0.5s ease-in-out;
}
@keyframes failureShake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px); }
75% { transform: translateX(10px); }
}
.collapse-animation {
animation: bridgeCollapse 2s ease-in forwards;
}
@keyframes bridgeCollapse {
0% { transform: translateY(0) rotate(0deg); opacity: 1; }
30% { transform: translateY(10px) rotate(-2deg); }
60% { transform: translateY(30px) rotate(5deg); }
100% { transform: translateY(100px) rotate(-10deg); opacity: 0.3; }
}
.crack-effect {
position: relative;
}
.crack-effect::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 40%, #ff0000 50%, transparent 60%);
opacity: 0;
animation: crackAppear 1s ease-in-out forwards;
}
@keyframes crackAppear {
0% { opacity: 0; }
100% { opacity: 0.8; }
}
</style>
<style>@view-transition { navigation: auto; }</style>
<script src="/_sdk/data_sdk.js" type="text/javascript"></script>
</head>
<body class="h-full bg-gradient-to-br from-blue-50 to-green-50 font-sans">
<div class="h-full flex flex-col"><!-- Header -->
<header class="bg-white shadow-lg p-4">
<div class="max-w-6xl mx-auto flex justify-between items-center">
<h1 id="gameTitle" class="text-3xl font-bold text-blue-800">🌉 Simulasi Membangun Jembatan</h1>
<div class="flex items-center space-x-6">
<div class="text-right">
<p id="budgetLabel" class="text-sm text-gray-600">Anggaran Tersedia</p>
<p id="budget" class="text-2xl font-bold text-green-600">Rp 500.000</p>
</div>
<div class="text-right">
<p class="text-sm text-gray-600">Biaya Terpakai</p>
<p id="usedBudget" class="text-2xl font-bold text-red-600">Rp 0</p>
</div>
</div>
</div>
</header>
<main class="flex-1 flex"><!-- Material Selection Panel -->
<aside class="w-80 bg-white shadow-lg p-6 overflow-y-auto">
<h2 id="materialsHeader" class="text-xl font-bold text-gray-800 mb-4">🔧 Pilih Material</h2>
<div class="space-y-4">
<div class="material-card bg-yellow-50 border-2 border-yellow-200 rounded-lg p-4" data-material="kayu">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-yellow-800">🪵 Kayu</h3><span class="text-yellow-700 font-bold">Rp 50.000</span>
</div>
<div class="text-sm text-yellow-700">
<p>Kekuatan: ⭐⭐⭐</p>
<p>Fleksibilitas: ⭐⭐⭐⭐</p>
<p>Tahan Air: ⭐⭐</p>
</div>
</div>
<div class="material-card bg-gray-50 border-2 border-gray-200 rounded-lg p-4" data-material="beton">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-gray-800">🧱 Beton</h3><span class="text-gray-700 font-bold">Rp 100.000</span>
</div>
<div class="text-sm text-gray-700">
<p>Kekuatan: ⭐⭐⭐⭐⭐</p>
<p>Fleksibilitas: ⭐⭐</p>
<p>Tahan Air: ⭐⭐⭐⭐⭐</p>
</div>
</div>
<div class="material-card bg-blue-50 border-2 border-blue-200 rounded-lg p-4" data-material="baja">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-blue-800">🔩 Baja</h3><span class="text-blue-700 font-bold">Rp 150.000</span>
</div>
<div class="text-sm text-blue-700">
<p>Kekuatan: ⭐⭐⭐⭐⭐</p>
<p>Fleksibilitas: ⭐⭐⭐⭐⭐</p>
<p>Tahan Air: ⭐⭐⭐</p>
</div>
</div>
<div class="material-card bg-green-50 border-2 border-green-200 rounded-lg p-4" data-material="bambu">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-green-800">🎋 Bambu</h3><span class="text-green-700 font-bold">Rp 30.000</span>
</div>
<div class="text-sm text-green-700">
<p>Kekuatan: ⭐⭐</p>
<p>Fleksibilitas: ⭐⭐⭐⭐⭐</p>
<p>Tahan Air: ⭐⭐</p>
</div>
</div>
</div>
<div class="mt-6 space-y-3"><button id="buildButton" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition-colors"> 🔨 Bangun Jembatan </button>
<div class="bg-orange-50 border border-orange-200 rounded-lg p-4 mb-3">
<h3 class="font-bold text-orange-800 mb-2">⚖️ Beban Uji</h3>
<div class="flex items-center space-x-2 mb-2"><label class="text-sm text-orange-700 font-medium">Berat:</label> <input type="range" id="loadSlider" min="1" max="10" value="3" class="flex-1"> <span id="loadValue" class="text-orange-800 font-bold min-w-[60px]">3 ton</span>
</div>
<div class="text-xs text-orange-600"><span id="loadDescription">Beban Ringan - Mobil kecil</span>
</div>
</div><button id="testButton" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-lg transition-colors" disabled> 🚛 Uji Jembatan </button> <button id="resetButton" class="w-full bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-4 rounded-lg transition-colors"> 🔄 Reset </button>
</div>
<div id="infoPanel" class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h3 class="font-bold text-blue-800 mb-2">💡 Tips:</h3>
<p class="text-sm text-blue-700">Pilih material untuk setiap bagian jembatan. Pertimbangkan kekuatan, biaya, dan kondisi lingkungan!</p>
</div>
</aside><!-- Bridge Building Area -->
<section class="flex-1 p-6">
<div class="bridge-canvas h-full rounded-lg relative border-4 border-blue-300">
<div class="water"></div><!-- Bridge Foundation Left -->
<div id="foundation-left" class="bridge-part w-16 h-32 bg-gray-400 border-2 border-gray-600 rounded-t-lg" style="bottom: 40%; left: 10%; cursor: pointer;" data-part="foundation-left">
<div class="text-xs text-center mt-1 font-bold">
Pondasi Kiri
</div>
</div><!-- Bridge Foundation Right -->
<div id="foundation-right" class="bridge-part w-16 h-32 bg-gray-400 border-2 border-gray-600 rounded-t-lg" style="bottom: 40%; right: 10%; cursor: pointer;" data-part="foundation-right">
<div class="text-xs text-center mt-1 font-bold">
Pondasi Kanan
</div>
</div><!-- Bridge Deck -->
<div id="deck" class="bridge-part h-8 bg-gray-400 border-2 border-gray-600 rounded" style="bottom: 45%; left: 18%; right: 18%; cursor: pointer;" data-part="deck">
<div class="text-xs text-center mt-1 font-bold">
Lantai Jembatan
</div>
</div><!-- Bridge Support Cables -->
<div id="cables" class="bridge-part" style="bottom: 53%; left: 35%; right: 35%; height: 30px; cursor: pointer;" data-part="cables">
<svg width="100%" height="100%" class="absolute"><line x1="0%" y1="100%" x2="50%" y2="0%" stroke="#666" stroke-width="3" /> <line x1="100%" y1="100%" x2="50%" y2="0%" stroke="#666" stroke-width="3" /> <line x1="25%" y1="75%" x2="75%" y2="75%" stroke="#666" stroke-width="2" />
</svg>
<div class="text-xs text-center font-bold" style="margin-top: 25px;">
Kabel Penyangga
</div>
</div><!-- Test Truck -->
<div id="testTruck" class="absolute hidden" style="bottom: 53%; left: -100px; transition: left 3s ease-in-out;">
<div class="text-4xl">
🚛
</div>
</div><!-- Results Display -->
<div id="resultDisplay" class="absolute top-4 left-4 right-4 text-center hidden">
<div id="resultMessage" class="inline-block px-6 py-3 rounded-lg font-bold text-lg"></div>
</div>
</div>
</section>
</main>
</div>
<script>
// Configuration object for editable features
const defaultConfig = {
game_title: "🌉 Simulasi Membangun Jembatan",
budget_label: "Anggaran Tersedia",
materials_header: "🔧 Pilih Material",
build_button: "🔨 Bangun Jembatan",
test_button: "🚛 Uji Jembatan"
};
// Game state
let gameState = {
budget: 500000,
usedBudget: 0,
selectedMaterial: null,
bridgeParts: {
'foundation-left': null,
'foundation-right': null,
'deck': null,
'cables': null
},
isBuilt: false,
testLoad: 3,
isCollapsed: false
};
// Material properties
const materials = {
kayu: { name: 'Kayu', cost: 50000, strength: 3, flexibility: 4, waterResistance: 2, color: '#D2691E', emoji: '🪵' },
beton: { name: 'Beton', cost: 100000, strength: 5, flexibility: 2, waterResistance: 5, color: '#696969', emoji: '🧱' },
baja: { name: 'Baja', cost: 150000, strength: 5, flexibility: 5, waterResistance: 3, color: '#708090', emoji: '🔩' },
bambu: { name: 'Bambu', cost: 30000, strength: 2, flexibility: 5, waterResistance: 2, color: '#228B22', emoji: '🎋' }
};
// Initialize the game
function initGame() {
updateDisplay();
setupEventListeners();
updateLoadValue(); // Initialize load slider display
}
function updateDisplay() {
document.getElementById('budget').textContent = `Rp ${gameState.budget.toLocaleString('id-ID')}`;
document.getElementById('usedBudget').textContent = `Rp ${gameState.usedBudget.toLocaleString('id-ID')}`;
// Update test button state
const testButton = document.getElementById('testButton');
const allPartsBuilt = Object.values(gameState.bridgeParts).every(part => part !== null);
testButton.disabled = !allPartsBuilt;
testButton.classList.toggle('opacity-50', !allPartsBuilt);
}
function setupEventListeners() {
// Material selection
document.querySelectorAll('.material-card').forEach(card => {
card.addEventListener('click', () => selectMaterial(card.dataset.material));
});
// Bridge part selection
document.querySelectorAll('.bridge-part').forEach(part => {
part.addEventListener('click', () => buildPart(part.dataset.part));
});
// Load slider
document.getElementById('loadSlider').addEventListener('input', updateLoadValue);
// Control buttons
document.getElementById('buildButton').addEventListener('click', buildBridge);
document.getElementById('testButton').addEventListener('click', testBridge);
document.getElementById('resetButton').addEventListener('click', resetGame);
}
function updateLoadValue() {
const slider = document.getElementById('loadSlider');
const loadValue = document.getElementById('loadValue');
const loadDescription = document.getElementById('loadDescription');
gameState.testLoad = parseInt(slider.value);
loadValue.textContent = `${gameState.testLoad} ton`;
const descriptions = {
1: "Beban Sangat Ringan - Sepeda motor",
2: "Beban Ringan - Mobil kecil",
3: "Beban Sedang - Mobil sedan",
4: "Beban Menengah - SUV",
5: "Beban Berat - Truk kecil",
6: "Beban Sangat Berat - Truk sedang",
7: "Beban Ekstrem - Truk besar",
8: "Beban Berbahaya - Truk trailer",
9: "Beban Kritis - Truk kontainer",
10: "Beban Maksimal - Truk gandeng"
};
loadDescription.textContent = descriptions[gameState.testLoad];
}
function selectMaterial(materialType) {
gameState.selectedMaterial = materialType;
// Update UI
document.querySelectorAll('.material-card').forEach(card => {
card.classList.remove('selected');
});
document.querySelector(`[data-material="${materialType}"]`).classList.add('selected');
updateInfoPanel(`Material ${materials[materialType].name} dipilih! Klik bagian jembatan untuk membangunnya.`);
}
function buildPart(partName) {
if (!gameState.selectedMaterial) {
updateInfoPanel('Pilih material terlebih dahulu!', 'warning');
return;
}
const material = materials[gameState.selectedMaterial];
const cost = material.cost;
if (gameState.usedBudget + cost > gameState.budget) {
updateInfoPanel('Anggaran tidak cukup! Pilih material yang lebih murah.', 'error');
return;
}
// Update game state
gameState.bridgeParts[partName] = gameState.selectedMaterial;
gameState.usedBudget += cost;
// Update visual
const partElement = document.getElementById(partName);
partElement.style.backgroundColor = material.color;
partElement.style.borderColor = material.color;
// Add material emoji
const existingEmoji = partElement.querySelector('.material-emoji');
if (existingEmoji) existingEmoji.remove();
const emojiSpan = document.createElement('span');
emojiSpan.className = 'material-emoji absolute top-0 right-0 text-lg';
emojiSpan.textContent = material.emoji;
partElement.appendChild(emojiSpan);
updateDisplay();
updateInfoPanel(`${material.name} dipasang pada ${getPartDisplayName(partName)}! Biaya: Rp ${cost.toLocaleString('id-ID')}`);
}
function buildBridge() {
const allPartsBuilt = Object.values(gameState.bridgeParts).every(part => part !== null);
if (!allPartsBuilt) {
updateInfoPanel('Lengkapi semua bagian jembatan terlebih dahulu!', 'warning');
return;
}
gameState.isBuilt = true;
updateInfoPanel('Jembatan selesai dibangun! Sekarang Anda bisa mengujinya.', 'success');
// Enable test button
document.getElementById('testButton').disabled = false;
document.getElementById('testButton').classList.remove('opacity-50');
}
function testBridge() {
if (!gameState.isBuilt || gameState.isCollapsed) return;
// Calculate bridge strength
let totalStrength = 0;
let totalFlexibility = 0;
let totalWaterResistance = 0;
Object.values(gameState.bridgeParts).forEach(materialType => {
const material = materials[materialType];
totalStrength += material.strength;
totalFlexibility += material.flexibility;
totalWaterResistance += material.waterResistance;
});
const averageStrength = totalStrength / 4;
const averageFlexibility = totalFlexibility / 4;
const averageWaterResistance = totalWaterResistance / 4;
// Calculate load capacity vs test load
const bridgeCapacity = averageStrength * 2; // Each strength point = 2 ton capacity
const loadStress = gameState.testLoad / bridgeCapacity;
// Animate truck crossing
const truck = document.getElementById('testTruck');
truck.classList.remove('hidden');
truck.style.left = '-100px';
// Update truck size based on load
const truckEmoji = truck.querySelector('div');
if (gameState.testLoad <= 3) {
truckEmoji.textContent = '🚗';
} else if (gameState.testLoad <= 6) {
truckEmoji.textContent = '🚛';
} else {
truckEmoji.textContent = '🚚';
}
setTimeout(() => {
truck.style.left = 'calc(100% + 50px)';
}, 100);
// Add bridge animation based on load stress
document.querySelectorAll('.bridge-part').forEach(part => {
if (loadStress > 1.2) {
// Overloaded - show cracks first
part.classList.add('crack-effect');
} else {
part.classList.add('test-animation');
}
});
// Determine test result
setTimeout(() => {
const success = loadStress <= 1.0;
const catastrophicFailure = loadStress > 1.5;
if (catastrophicFailure) {
// Bridge collapses
gameState.isCollapsed = true;
document.querySelectorAll('.bridge-part').forEach(part => {
part.classList.remove('test-animation', 'crack-effect');
part.classList.add('collapse-animation');
});
showTestResult(false, averageStrength, averageFlexibility, averageWaterResistance, true, loadStress);
} else {
showTestResult(success, averageStrength, averageFlexibility, averageWaterResistance, false, loadStress);
// Reset animations
document.querySelectorAll('.bridge-part').forEach(part => {
part.classList.remove('test-animation', 'crack-effect');
if (success) {
part.classList.add('success-glow');
} else {
part.classList.add('failure-shake');
}
});
}
setTimeout(() => {
truck.classList.add('hidden');
if (!gameState.isCollapsed) {
document.querySelectorAll('.bridge-part').forEach(part => {
part.classList.remove('success-glow', 'failure-shake');
});
}
}, 2000);
}, 3000);
}
function showTestResult(success, strength, flexibility, waterResistance, collapsed, loadStress) {
const resultDisplay = document.getElementById('resultDisplay');
const resultMessage = document.getElementById('resultMessage');
const capacity = strength * 2;
if (collapsed) {
resultMessage.className = 'inline-block px-6 py-3 rounded-lg font-bold text-lg bg-red-100 text-red-800 border border-red-300';
resultMessage.innerHTML = `
💥 JEMBATAN RUNTUH! 💥<br>
<span class="text-sm">Beban ${gameState.testLoad} ton terlalu berat untuk kapasitas ${capacity.toFixed(1)} ton!<br>
Stress: ${(loadStress * 100).toFixed(0)}% | Kekuatan: ${strength.toFixed(1)} | Fleksibilitas: ${flexibility.toFixed(1)}</span>
`;
} else if (success) {
resultMessage.className = 'inline-block px-6 py-3 rounded-lg font-bold text-lg bg-green-100 text-green-800 border border-green-300';
resultMessage.innerHTML = `
🎉 JEMBATAN AMAN! 🎉<br>
<span class="text-sm">Beban ${gameState.testLoad} ton berhasil dilalui!<br>
Kapasitas: ${capacity.toFixed(1)} ton | Stress: ${(loadStress * 100).toFixed(0)}% | Kekuatan: ${strength.toFixed(1)}</span>
`;
} else {
resultMessage.className = 'inline-block px-6 py-3 rounded-lg font-bold text-lg bg-yellow-100 text-yellow-800 border border-yellow-300';
resultMessage.innerHTML = `
⚠️ JEMBATAN RETAK! ⚠️<br>
<span class="text-sm">Beban ${gameState.testLoad} ton menyebabkan kerusakan!<br>
Kapasitas: ${capacity.toFixed(1)} ton | Stress: ${(loadStress * 100).toFixed(0)}% | Berbahaya!</span>
`;
}
resultDisplay.classList.remove('hidden');
setTimeout(() => {
resultDisplay.classList.add('hidden');
}, 5000);
}
function resetGame() {
gameState = {
budget: 500000,
usedBudget: 0,
selectedMaterial: null,
bridgeParts: {
'foundation-left': null,
'foundation-right': null,
'deck': null,
'cables': null
},
isBuilt: false,
testLoad: 3,
isCollapsed: false
};
// Reset visual elements
document.querySelectorAll('.bridge-part').forEach(part => {
part.style.backgroundColor = '#9CA3AF';
part.style.borderColor = '#6B7280';
part.style.transform = '';
part.style.opacity = '';
part.classList.remove('collapse-animation', 'crack-effect', 'success-glow', 'failure-shake', 'test-animation');
const emoji = part.querySelector('.material-emoji');
if (emoji) emoji.remove();
});
document.querySelectorAll('.material-card').forEach(card => {
card.classList.remove('selected');
});
// Reset load slider
document.getElementById('loadSlider').value = 3;
updateLoadValue();
updateDisplay();
updateInfoPanel('Game direset! Mulai membangun jembatan baru.');
}
function updateInfoPanel(message, type = 'info') {
const infoPanel = document.getElementById('infoPanel');
const colors = {
info: 'bg-blue-50 border-blue-200 text-blue-700',
success: 'bg-green-50 border-green-200 text-green-700',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-700',
error: 'bg-red-50 border-red-200 text-red-700'
};
infoPanel.className = `mt-6 p-4 border rounded-lg ${colors[type]}`;
infoPanel.innerHTML = `<h3 class="font-bold mb-2">💡 Info:</h3><p class="text-sm">${message}</p>`;
}
function getPartDisplayName(partName) {
const names = {
'foundation-left': 'Pondasi Kiri',
'foundation-right': 'Pondasi Kanan',
'deck': 'Lantai Jembatan',
'cables': 'Kabel Penyangga'
};
return names[partName] || partName;
}
// Element SDK implementation
async function onConfigChange(config) {
document.getElementById('gameTitle').textContent = config.game_title || defaultConfig.game_title;
document.getElementById('budgetLabel').textContent = config.budget_label || defaultConfig.budget_label;
document.getElementById('materialsHeader').textContent = config.materials_header || defaultConfig.materials_header;
document.getElementById('buildButton').innerHTML = `${config.build_button || defaultConfig.build_button}`;
document.getElementById('testButton').innerHTML = `${config.test_button || defaultConfig.test_button}`;
}
function mapToCapabilities(config) {
return {
recolorables: [],
borderables: [],
fontEditable: undefined,
fontSizeable: undefined
};
}
function mapToEditPanelValues(config) {
return new Map([
["game_title", config.game_title || defaultConfig.game_title],
["budget_label", config.budget_label || defaultConfig.budget_label],
["materials_header", config.materials_header || defaultConfig.materials_header],
["build_button", config.build_button || defaultConfig.build_button],
["test_button", config.test_button || defaultConfig.test_button]
]);
}
// Initialize Element SDK
if (window.elementSdk) {
window.elementSdk.init({
defaultConfig,
onConfigChange,
mapToCapabilities,
mapToEditPanelValues
});
}
// Start the game
initGame();
</script>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'99dd500fa600229b',t:'MTc2MzAyNjg4MC4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>
