<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Mini Scratch 3.0 — Kompakt Sonsuz Kod Alanı Editörü</title>
<style>
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{width:100%;height:100%;margin:0;padding:0;overflow:hidden;font-family:Arial,sans-serif;background:#e9ecef;color:#222;font-size:11px;user-select:none}
header{height:40px;background:#4c97ff;color:white;display:flex;align-items:center;gap:8px;padding:0 10px;font-weight:bold;z-index:20;position:relative;box-shadow:0 2px 4px rgba(0,0,0,0.15)}
header .brand{font-size:15px;margin-right:auto;display:flex;align-items:center;gap:5px}
header button{border:0;border-radius:5px;padding:5px 10px;cursor:pointer;font-weight:bold;font-size:11px;display:flex;align-items:center;gap:3px;transition:transform 0.1s}
header button:active{transform:scale(0.95)}
.run{background:#4cbf56;color:white}
.stop{background:#ffab19;color:white}
.clear{background:#ff6680;color:white}
.download{background:#ffbf00;color:#333}
main{display:grid;grid-template-columns:210px 1fr 270px;height:calc(100vh - 40px);width:100vw;overflow:hidden;position:relative}
/* Sol Panel: Kategoriler ve Palette */
.left-panel{display:flex;background:#ffffff;border-right:1px solid #d9d9d9;z-index:10;height:100%}
.categories-bar{width:52px;background:#f9f9f9;border-right:1px solid #e0e0e0;display:flex;flex-direction:column;align-items:center;padding:6px 0;gap:8px}
.cat-btn{background:none;border:none;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:3px;font-size:9px;font-weight:bold;color:#575e75;width:100%}
.cat-dot{width:16px;height:16px;border-radius:50%}
.cat-btn.active{color:#4c97ff}
.cat-btn.motion .cat-dot{background:#4c97ff}
.cat-btn.looks .cat-dot{background:#9966ff}
.cat-btn.events .cat-dot{background:#ffd500}
.cat-btn.control .cat-dot{background:#ffab19}
.cat-btn.sound .cat-dot{background:#cf63b8}
.palette-area{flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:8px;background:#ffffff}
/* Genel Blok Stilleri (Kompakt Puzzle Yapısı) */
.block{
color:white;font-weight:bold;font-size:11px;padding:6px 10px;cursor:grab;user-select:none;
display:inline-flex;align-items:center;gap:5px;width:fit-content;min-height:30px;
position:relative;box-shadow:0 1px 3px rgba(0,0,0,0.15);
clip-path: polygon(0 0, 10px 0, 14px 3px, 24px 3px, 28px 0, 100% 0, 100% calc(100% - 3px), calc(100% - 3px) 100%, 28px 100%, 24px calc(100% + 3px), 14px calc(100% + 3px), 10px 100%, 0 100%);
margin-bottom:2px;
}
.block-hat{
border-top-left-radius:12px;border-top-right-radius:12px;
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 3px), calc(100% - 3px) 100%, 28px 100%, 24px calc(100% + 3px), 14px calc(100% + 3px), 10px 100%, 0 100%);
}
.b-motion{background:#4c97ff}
.b-looks{background:#9966ff}
.b-events{background:#ffd500;color:#575e75}
.b-control{background:#ffab19}
.b-sound{background:#cf63b8}
.block input{width:38px;border:none;border-radius:8px;padding:2px 4px;text-align:center;font-weight:bold;font-size:10px;outline:none;background:white;color:#333}
/* Orta Panel: Sonsuz Açık Gri Kod Alanı */
.workspace-container{position:relative;background:#f9f9fb;overflow:hidden;touch-action:none;cursor:grab}
.workspace-container:active{cursor:grabbing}
.workspace-canvas{position:absolute;top:0;left:0;width:100%;height:100%;transform-origin:0 0}
/* Yakınlaştırma (Zoom) Kontrolleri */
.zoom-controls{position:absolute;bottom:12px;right:12px;display:flex;gap:4px;background:white;padding:3px;border-radius:16px;box-shadow:0 2px 6px rgba(0,0,0,0.15);z-index:15}
.zoom-btn{width:26px;height:26px;border-radius:50%;border:none;background:#f0f0f5;color:#575e75;font-weight:bold;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center}
.zoom-btn:hover{background:#e2e2e8}
/* Tuval üzerindeki blok grupları */
.canvas-block-group{position:absolute;display:flex;flex-direction:column;align-items:flex-start;z-index:5}
.canvas-block-group .block{margin-bottom:-3px;cursor:move}
.canvas-block-group .block .remove-btn{margin-left:6px;background:rgba(0,0,0,0.2);border:none;color:white;border-radius:50%;width:14px;height:14px;font-size:9px;cursor:pointer;display:flex;align-items:center;justify-content:center}
/* Sağ Panel: Sahne ve Kuklalar */
.right-panel{background:#ffffff;border-left:1px solid #d9d9d9;display:flex;flex-direction:column;height:100%}
.stage-wrap{padding:8px;background:#f9f9fb;border-bottom:1px solid #e0e0e0}
.stage-header{display:flex;justify-content:space-between;font-weight:bold;font-size:11px;color:#575e75;margin-bottom:4px}
.stage{width:100%;aspect-ratio:4/3;background:#ffffff;border:2px solid #e0e0e0;border-radius:6px;position:relative;overflow:hidden;box-shadow:inset 0 0 8px rgba(0,0,0,0.05)}
.sprite-element{position:absolute;font-size:36px;transform:translate(-50%,-50%);user-select:none;transition:none;pointer-events:none}
.speech-bubble{position:absolute;background:white;border:2px solid #575e75;border-radius:10px;padding:3px 6px;font-size:10px;font-weight:bold;white-space:nowrap;transform:translate(-50%,-100%);top:-8px;left:50%;box-shadow:0 2px 4px rgba(0,0,0,0.1);display:none}
.speech-bubble::after{content:'';position:absolute;bottom:-5px;left:50%;transform:translateX(-50%);border-width:5px 5px 0;border-style:solid;border-color:white transparent;display:block;width:0}
/* Kukla Yönetim Paneli */
.sprites-manager{flex:1;display:flex;flex-direction:column;padding:8px;overflow:hidden;position:relative}
.sprites-header{font-size:11px;font-weight:bold;color:#575e75;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center}
.sprites-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(58px,1fr));gap:6px;overflow-y:auto;flex:1;align-content:start}
.sprite-card{border:2px solid #e0e0e0;border-radius:6px;padding:6px;display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;background:#f9f9fb;position:relative}
.sprite-card.active{border-color:#4c97ff;background:#e9f1ff}
.sprite-card .icon{font-size:22px}
.sprite-card .name{font-size:10px;font-weight:bold;color:#575e75}
.sprite-card .del{position:absolute;top:-3px;right:-3px;background:#ff6680;color:white;border:none;border-radius:50%;width:15px;height:15px;font-size:9px;cursor:pointer;display:none;align-items:center;justify-content:center}
.sprite-card:hover .del{display:flex}
.add-sprite-btn{position:absolute;bottom:8px;right:8px;width:36px;height:36px;border-radius:50%;background:#4c97ff;color:white;border:none;font-size:18px;cursor:pointer;box-shadow:0 3px 8px rgba(76,151,255,0.4);display:flex;align-items:center;justify-content:center;transition:transform 0.2s}
.add-sprite-btn:hover{transform:scale(1.08)}
/* Modal Kukla Seçici */
.modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.5);display:none;align-items:center;justify-content:center;z-index:100}
.modal{background:white;border-radius:10px;width:90%;max-width:350px;padding:12px;box-shadow:0 8px 20px rgba(0,0,0,0.2)}
.modal-title{font-weight:bold;font-size:14px;color:#575e75;margin-bottom:10px}
.animal-selector{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;max-height:220px;overflow-y:auto}
.animal-option{border:1px solid #e0e0e0;border-radius:6px;padding:8px;font-size:24px;text-align:center;cursor:pointer;transition:background 0.2s}
.animal-option:hover{background:#e9f1ff;border-color:#4c97ff}
.console{border-top:1px solid #e0e0e0;padding:6px 10px;font-size:10px;color:#666;background:#f9f9fb;max-height:50px;overflow-y:auto}
@media(max-width:850px){
main{grid-template-columns:160px 1fr 210px}
}
</style>
</head>
<body>
<header>
<div class="brand">🟣 Mini Scratch 3.0</div>
<button class="run" id="btnRun">▶ Yeşil Bayrak</button>
<button class="stop" id="btnStop">⏹ Durdur</button>
<button class="clear" id="btnClear">Temizle</button>
<button class="download" id="btnDownload">💾 Projeyi İndir</button>
</header>
<main>
<!-- SOL PANEL: KATEGORİ VE PALET -->
<section class="left-panel">
<div class="categories-bar">
<button class="cat-btn motion active" data-cat="motion"><div class="cat-dot"></div>Hareket</button>
<button class="cat-btn looks" data-cat="looks"><div class="cat-dot"></div>Görünüm</button>
<button class="cat-btn events" data-cat="events"><div class="cat-dot"></div>Olaylar</button>
<button class="cat-btn control" data-cat="control"><div class="cat-dot"></div>Kontrol</button>
<button class="cat-btn sound" data-cat="sound"><div class="cat-dot"></div>Ses</button>
</div>
<div class="palette-area" id="paletteArea"></div>
</section>
<!-- ORTA PANEL: AÇIK GRİ SONSUZ KOD ALANI -->
<section class="workspace-container" id="workspaceContainer">
<div class="workspace-canvas" id="workspaceCanvas"></div>
<div class="zoom-controls">
<button class="zoom-btn" id="zoomIn">+</button>
<button class="zoom-btn" id="zoomReset">=</button>
<button class="zoom-btn" id="zoomOut">-</button>
</div>
</section>
<!-- SAĞ PANEL: SAHNE VE KUKLALAR -->
<section class="right-panel">
<div class="stage-wrap">
<div class="stage-header"><span>🎭 Sahne</span><small>480 × 360</small></div>
<div class="stage" id="stage"></div>
</div>
<div class="sprites-manager">
<div class="sprites-header"><span>🐾 Kuklalar</span><small id="activeSpriteName">Kedi</small></div>
<div class="sprites-grid" id="spritesGrid"></div>
<button class="add-sprite-btn" id="btnAddSpriteModal" title="Kukla Ekle">+</button>
</div>
<div class="console" id="console">Hazır! Kukla seç ve kod bloklarını sürükle.</div>
</section>
</main>
<!-- KUKLA EKLEME MODAL PENCERESİ -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal">
<div class="modal-title">Bir Kukla Seç</div>
<div class="animal-selector" id="animalSelector"></div>
</div>
</div>
<script>
const blockDefinitions = {
motion: [
{ id: 'move', text: '10 adım git', type: 'b-motion', inputType: 'number', defaultValue: '10' },
{ id: 'turn', text: '15 derece dön', type: 'b-motion', inputType: 'number', defaultValue: '15' },
{ id: 'goto', text: 'x: 0 y: 0 git', type: 'b-motion', inputType: 'xy', defaultValue: '0,0' },
{ id: 'bounce', text: 'Kenara geldiyse zıpla', type: 'b-motion' }
],
looks: [
{ id: 'say', text: 'Merhaba! de', type: 'b-looks', inputType: 'text', defaultValue: 'Merhaba!' },
{ id: 'size', text: 'Boyutu %100 yap', type: 'b-looks', inputType: 'number', defaultValue: '100' },
{ id: 'hide', text: 'Gizlen', type: 'b-looks' },
{ id: 'show', text: 'Görün', type: 'b-looks' }
],
events: [
{ id: 'flag', text: '🟢 Yeşil bayrak tıklanınca', type: 'b-events block-hat' }
],
control: [
{ id: 'repeat', text: '3 kez tekrarla', type: 'b-control', inputType: 'number', defaultValue: '3' },
{ id: 'wait', text: '1 saniye bekle', type: 'b-control', inputType: 'number', defaultValue: '1' }
],
sound: [
{ id: 'sound', text: 'Ses çal', type: 'b-sound' }
]
};
const animals = ['🐱','🐶','🐸','🦊','🐼','🐧','👾','🦄','🐰','🦁','🐵','🐯'];
let sprites = [
{ id: 'sprite_1', name: 'Kedi', icon: '🐱', x: 50, y: 50, angle: 0, size: 40, visible: true, speech: '', panX: 0, panY: 0, zoom: 1, stacks: [] }
];
let activeSpriteId = 'sprite_1';
let activeCategory = 'motion';
let isGlobalRunning = false;
let globalExecutionToken = 0;
const paletteArea = document.getElementById('paletteArea');
const workspaceContainer = document.getElementById('workspaceContainer');
const workspaceCanvas = document.getElementById('workspaceCanvas');
const stage = document.getElementById('stage');
const spritesGrid = document.getElementById('spritesGrid');
const consoleEl = document.getElementById('console');
const modalOverlay = document.getElementById('modalOverlay');
const animalSelector = document.getElementById('animalSelector');
function init() {
renderCategories();
renderPalette();
renderSpritesGrid();
renderStage();
renderWorkspace();
setupPanAndZoom();
setupModal();
}
function renderCategories() {
document.querySelectorAll('.cat-btn').forEach(btn => {
btn.onclick = () => {
document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeCategory = btn.dataset.cat;
renderPalette();
};
});
}
function renderPalette() {
paletteArea.innerHTML = '';
const defs = blockDefinitions[activeCategory] || [];
defs.forEach(def => {
const el = createBlockElement(def, true);
paletteArea.appendChild(el);
});
}
function createBlockElement(def, isPalette = false, blockData = null) {
const el = document.createElement('div');
el.className = `block ${def.type}`;
const span = document.createElement('span');
span.textContent = def.text.replace(/10|15|0,0|Merhaba!|100|3|1/, '');
el.appendChild(span);
let input = null;
if (def.inputType) {
input = document.createElement('input');
input.value = blockData ? blockData.val : def.defaultValue;
input.onclick = (e) => e.stopPropagation();
input.oninput = (e) => { if(blockData) blockData.val = e.target.value; };
el.appendChild(input);
}
if (isPalette) {
setupPaletteDrag(el, def);
}
return el;
}
function setupPaletteDrag(el, def) {
el.onpointerdown = (e) => {
e.preventDefault();
const activeSprite = getActiveSprite();
const rect = workspaceContainer.getBoundingClientRect();
const canvasX = (e.clientX - rect.left - activeSprite.panX) / activeSprite.zoom;
const canvasY = (e.clientY - rect.top - activeSprite.panY) / activeSprite.zoom;
const newBlock = { id: def.id, val: def.defaultValue || '' };
const newStack = { x: canvasX, y: canvasY, blocks: [newBlock] };
activeSprite.stacks.push(newStack);
renderWorkspace();
const groupEl = workspaceCanvas.lastElementChild;
startGroupDrag(e, activeSprite, newStack, groupEl);
};
}
function setupPanAndZoom() {
let isPanning = false;
let startX, startY;
workspaceContainer.onpointerdown = (e) => {
if (e.target === workspaceContainer || e.target === workspaceCanvas) {
isPanning = true;
startX = e.clientX;
startY = e.clientY;
}
};
window.addEventListener('pointermove', (e) => {
if (isPanning) {
const activeSprite = getActiveSprite();
activeSprite.panX += e.clientX - startX;
activeSprite.panY += e.clientY - startY;
startX = e.clientX;
startY = e.clientY;
applyCanvasTransform();
}
});
window.addEventListener('pointerup', () => { isPanning = false; });
document.getElementById('zoomIn').onclick = () => changeZoom(0.1);
document.getElementById('zoomOut').onclick = () => changeZoom(-0.1);
document.getElementById('zoomReset').onclick = () => {
const s = getActiveSprite();
s.zoom = 1; s.panX = 0; s.panY = 0;
applyCanvasTransform();
};
}
function changeZoom(delta) {
const s = getActiveSprite();
s.zoom = Math.max(0.4, Math.min(1.8, s.zoom + delta));
applyCanvasTransform();
}
function applyCanvasTransform() {
const s = getActiveSprite();
workspaceCanvas.style.transform = `translate(${s.panX}px, ${s.panY}px) scale(${s.zoom})`;
}
function renderWorkspace() {
workspaceCanvas.innerHTML = '';
const sprite = getActiveSprite();
applyCanvasTransform();
sprite.stacks.forEach((stack, stackIdx) => {
const groupEl = document.createElement('div');
groupEl.className = 'canvas-block-group';
groupEl.style.left = stack.x + 'px';
groupEl.style.top = stack.y + 'px';
stack.blocks.forEach((bData, bIdx) => {
const def = findBlockDef(bData.id);
const bEl = createBlockElement(def, false, bData);
const delBtn = document.createElement('button');
delBtn.className = 'remove-btn';
delBtn.textContent = '×';
delBtn.onclick = (e) => {
e.stopPropagation();
stack.blocks.splice(bIdx, 1);
if (stack.blocks.length === 0) sprite.stacks.splice(stackIdx, 1);
renderWorkspace();
};
bEl.appendChild(delBtn);
groupEl.appendChild(bEl);
});
groupEl.onpointerdown = (e) => {
e.stopPropagation();
startGroupDrag(e, sprite, stack, groupEl);
};
workspaceCanvas.appendChild(groupEl);
});
}
function startGroupDrag(e, sprite, stack, groupEl) {
let startX = e.clientX;
let startY = e.clientY;
const onMove = (me) => {
const dx = (me.clientX - startX) / sprite.zoom;
const dy = (me.clientY - startY) / sprite.zoom;
stack.x += dx;
stack.y += dy;
groupEl.style.left = stack.x + 'px';
groupEl.style.top = stack.y + 'px';
startX = me.clientX;
startY = me.clientY;
};
const onUp = () => {
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', onUp);
checkSnap(sprite, stack);
renderWorkspace();
};
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
}
function checkSnap(sprite, targetStack) {
const SNAP_DIST = 30;
for (let otherStack of sprite.stacks) {
if (otherStack === targetStack) continue;
const dx = Math.abs(otherStack.x - targetStack.x);
const dy = Math.abs((otherStack.y + otherStack.blocks.length * 28) - targetStack.y);
if (dx < SNAP_DIST && dy < SNAP_DIST) {
otherStack.blocks.push(...targetStack.blocks);
const idx = sprite.stacks.indexOf(targetStack);
if (idx > -1) sprite.stacks.splice(idx, 1);
break;
}
}
}
function getActiveSprite() {
return sprites.find(s => s.id === activeSpriteId) || sprites[0];
}
function renderSpritesGrid() {
spritesGrid.innerHTML = '';
sprites.forEach(s => {
const card = document.createElement('div');
card.className = `sprite-card ${s.id === activeSpriteId ? 'active' : ''}`;
card.innerHTML = `<div class="icon">${s.icon}</div><div class="name">${s.name}</div>`;
if (sprites.length > 1) {
const del = document.createElement('button');
del.className = 'del';
del.textContent = '×';
del.onclick = (e) => {
e.stopPropagation();
sprites = sprites.filter(x => x.id !== s.id);
if (activeSpriteId === s.id) activeSpriteId = sprites[0].id;
renderSpritesGrid();
renderStage();
renderWorkspace();
};
card.appendChild(del);
}
card.onclick = () => {
activeSpriteId = s.id;
document.getElementById('activeSpriteName').textContent = s.name;
renderSpritesGrid();
renderWorkspace();
};
spritesGrid.appendChild(card);
});
}
function renderStage() {
stage.innerHTML = '';
sprites.forEach(s => {
const el = document.createElement('div');
el.className = 'sprite-element';
el.style.left = s.x + '%';
el.style.top = s.y + '%';
el.style.fontSize = s.size + 'px';
el.style.transform = `translate(-50%, -50%) rotate(${s.angle}deg)`;
el.style.display = s.visible ? 'block' : 'none';
el.textContent = s.icon;
if (s.speech) {
const bubble = document.createElement('div');
bubble.className = 'speech-bubble';
bubble.style.display = 'block';
bubble.textContent = s.speech;
el.appendChild(bubble);
}
stage.appendChild(el);
});
}
function setupModal() {
const btn = document.getElementById('btnAddSpriteModal');
btn.onclick = () => modalOverlay.style.display = 'flex';
modalOverlay.onclick = (e) => { if(e.target === modalOverlay) modalOverlay.style.display = 'none'; };
animalSelector.innerHTML = '';
animals.forEach(icon => {
const opt = document.createElement('div');
opt.className = 'animal-option';
opt.textContent = icon;
opt.onclick = () => {
const newSprite = {
id: 'sprite_' + Date.now(),
name: 'Kukla ' + (sprites.length + 1),
icon: icon,
x: 30 + Math.random() * 40,
y: 30 + Math.random() * 40,
angle: 0,
size: 40,
visible: true,
speech: '',
panX: 0, panY: 0, zoom: 1,
stacks: []
};
sprites.push(newSprite);
activeSpriteId = newSprite.id;
modalOverlay.style.display = 'none';
renderSpritesGrid();
renderStage();
renderWorkspace();
};
animalSelector.appendChild(opt);
});
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function runProgram() {
isGlobalRunning = true;
globalExecutionToken++;
const token = globalExecutionToken;
log('🟢 Program başlatıldı...');
const promises = sprites.map(sprite => executeSpriteStacks(sprite, token));
await Promise.all(promises);
if (token === globalExecutionToken) {
isGlobalRunning = false;
log('⏹ Program tamamlandı.');
}
}
async function executeSpriteStacks(sprite, token) {
for (let stack of sprite.stacks) {
if (!stack.blocks.length || stack.blocks[0].id !== 'flag') continue;
for (let bData of stack.blocks) {
if (!isGlobalRunning || token !== globalExecutionToken) break;
await executeBlock(sprite, bData, token);
renderStage();
await sleep(150);
}
}
}
async function executeBlock(sprite, bData, token) {
const val = bData.val;
const num = Number(val);
switch(bData.id) {
case 'move':
const rad = sprite.angle * Math.PI / 180;
sprite.x = Math.max(5, Math.min(95, sprite.x + Math.cos(rad) * (num || 10) / 4));
sprite.y = Math.max(5, Math.min(95, sprite.y + Math.sin(rad) * (num || 10) / 4));
break;
case 'turn':
sprite.angle += (num || 15);
break;
case 'goto':
const parts = (val || '0,0').split(',');
sprite.x = Math.max(5, Math.min(95, 50 + Number(parts[0] || 0) / 5));
sprite.y = Math.max(5, Math.min(95, 50 - Number(parts[1] || 0) / 5));
break;
case 'bounce':
if (sprite.x >= 90 || sprite.x <= 10) sprite.angle = 180 - sprite.angle;
if (sprite.y >= 90 || sprite.y <= 10) sprite.angle = -sprite.angle;
break;
case 'say':
sprite.speech = val;
renderStage();
await sleep(1500);
sprite.speech = '';
break;
case 'size':
sprite.size = Math.max(14, Math.min(100, num || 100)) * 0.4;
break;
case 'hide': sprite.visible = false; break;
case 'show': sprite.visible = true; break;
case 'repeat': break;
case 'wait':
await sleep((num || 1) * 1000);
break;
case 'sound':
playBeep();
log(`♪ ${sprite.name} ses çıkardı!`);
break;
}
}
function playBeep() {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.frequency.value = 440;
gain.gain.value = 0.1;
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 0.15);
} catch(e){}
}
function findBlockDef(id) {
for (let cat of Object.values(blockDefinitions)) {
const found = cat.find(b => b.id === id);
if (found) return found;
}
return { id, text: id, type: 'b-motion' };
}
function log(msg) { consoleEl.textContent = msg; }
document.getElementById('btnRun').onclick = runProgram;
document.getElementById('btnStop').onclick = () => {
isGlobalRunning = false;
globalExecutionToken++;
log('⏹ Durduruldu.');
};
document.getElementById('btnClear').onclick = () => {
const s = getActiveSprite();
s.stacks = [];
renderWorkspace();
log('Kod alanı temizlendi.');
};
document.getElementById('btnDownload').onclick = () => {
const htmlContent = "<!doctype html>\n" + document.documentElement.outerHTML;
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'scratch_projesi.html';
a.click();
URL.revokeObjectURL(url);
log('💾 Proje başarıyla indirildi!');
};
window.onload = init;
</script>
</body>
</html>