/**** * Classes ****/ // ClearButton class var ClearButton = Container.expand(function () { var self = Container.call(this); var btnAsset = self.attachAsset('clear_btn', { anchorX: 0.5, anchorY: 0.5 }); // Add text label var txt = new Text2('Clear', { size: 48, fill: 0x222222 }); txt.anchor.set(0.5, 0.5); txt.x = 0; txt.y = 0; self.addChild(txt); // Visual feedback self.setPressed = function (pressed) { btnAsset.tint = pressed ? 0xaaaaaa : 0xcccccc; }; return self; }); // PaletteColor class: represents a color in the palette var PaletteColor = Container.expand(function () { var self = Container.call(this); // Attach the color asset, anchor at 0.5,0.5 (center) var colorId = self.colorId; // will be set after creation var assetId = self.assetId; // will be set after creation var colorAsset = null; // Set color and asset after creation self.setColorAsset = function (assetId, colorId) { self.assetId = assetId; self.colorId = colorId; colorAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); }; // Show selection border self.setSelected = function (selected) { if (selected) { colorAsset.width = 120; colorAsset.height = 120; } else { colorAsset.width = 100; colorAsset.height = 100; } }; return self; }); // No plugins needed for MVP // Pixel class: represents a single cell in the grid var Pixel = Container.expand(function () { var self = Container.call(this); // Attach the pixel asset, anchor at 0,0 (top-left) var pixelAsset = self.attachAsset('pixel', { anchorX: 0, anchorY: 0 }); // Store color (as 0xRRGGBB) self.color = 0xffffff; // Set color self.setColor = function (newColor) { self.color = newColor; pixelAsset.color = newColor; pixelAsset.tint = newColor; }; // For hit detection, store grid position self.gridX = 0; self.gridY = 0; // For drag fill, track if this pixel was already filled in this drag self.lastFillDragId = -1; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf8f8f8 }); /**** * Game Code ****/ // --- Constants --- // 32x32 grid, each cell is a square. We'll use a single shape asset for the pixel, and color it as needed. // 56x56 so 32*56=1792, fits well in 2048 width with margin // Palette colors: We'll use 10 basic colors for the palette // Clear button var GRID_SIZE = 32; var PIXEL_SIZE = Math.floor(2048 / GRID_SIZE); // Make sure grid fits and is square var GRID_MARGIN = Math.floor((2048 - GRID_SIZE * PIXEL_SIZE) / 2); // Center horizontally var GRID_TOP = Math.floor((2048 - GRID_SIZE * PIXEL_SIZE) / 2) + 220; // Center vertically with space for palette at top // Palette colors (assetId, color) // Add eraser as a special palette entry at the end (color: null) var PALETTE = [{ assetId: 'palette_red', color: 0xff3b30 }, { assetId: 'palette_orange', color: 0xff9500 }, { assetId: 'palette_yellow', color: 0xffcc00 }, { assetId: 'palette_green', color: 0x34c759 }, { assetId: 'palette_cyan', color: 0x5ac8fa }, { assetId: 'palette_blue', color: 0x007aff }, { assetId: 'palette_purple', color: 0xaf52de }, { assetId: 'palette_brown', color: 0xa2845e }, { assetId: 'palette_black', color: 0x222222 }, { assetId: 'palette_white', color: 0xffffff }, { assetId: 'palette_gray', color: 0x888888 }, { assetId: 'palette_pink', color: 0xff2d55 }, // Eraser tool (special entry) { assetId: 'palette_white', // visually white, but will be drawn with eraser icon color: null // null means eraser }]; // --- State --- var pixels = []; // 2D array [y][x] of Pixel var paletteColors = []; // Array of PaletteColor var selectedColor = PALETTE[0].color; // Default: red var selectedPaletteIdx = 0; var clearBtn = null; var dragFilling = false; var dragFillId = 0; // Incremented for each drag to avoid double-filling var lastDragPixel = null; // --- Create grid --- for (var y = 0; y < GRID_SIZE; y++) { pixels[y] = []; for (var x = 0; x < GRID_SIZE; x++) { var px = new Pixel(); px.x = GRID_MARGIN + x * PIXEL_SIZE; px.y = GRID_TOP + y * PIXEL_SIZE; px.gridX = x; px.gridY = y; px.setColor(0xffffff); // Start as white game.addChild(px); pixels[y][x] = px; } } // --- Draw grid lines (semi-transparent black) --- var gridLineColor = 0x000000; var gridLineAlpha = 0.18; // semi-transparent // Vertical lines for (var gx = 0; gx <= GRID_SIZE; gx++) { var line = LK.getAsset('pixel', { anchorX: 0, anchorY: 0, width: 2, height: GRID_SIZE * PIXEL_SIZE, color: gridLineColor, alpha: gridLineAlpha }); line.x = GRID_MARGIN + gx * PIXEL_SIZE - 1; line.y = GRID_TOP; game.addChild(line); } // --- Add a much bigger frame below the grid (centered horizontally, with smaller gap) --- var frameBigWidth = 768; var frameBigHeight = 384; var frameBigAsset = LK.getAsset('canvas_frame', { anchorX: 0.5, anchorY: 0, width: frameBigWidth, height: frameBigHeight }); frameBigAsset.x = Math.floor(2048 / 2); frameBigAsset.y = Math.floor(GRID_TOP + GRID_SIZE * PIXEL_SIZE + 8); // 8px below grid (smaller gap) game.addChild(frameBigAsset); // --- Add paint-everywhere button next to the frame --- var paintEverywhereBtn = LK.getAsset('palette_blue', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); paintEverywhereBtn.x = frameBigAsset.x + frameBigWidth / 2 + 100; paintEverywhereBtn.y = frameBigAsset.y + frameBigHeight / 2; game.addChild(paintEverywhereBtn); // Add icon/text to button var paintIcon = new Text2('🖌', { size: 80, fill: 0xffffff }); paintIcon.anchor.set(0.5, 0.5); paintIcon.x = 0; paintIcon.y = 0; paintEverywhereBtn.addChild(paintIcon); // State: is paint-everywhere mode active? var paintEverywhereMode = false; // Visual feedback for button function updatePaintEverywhereBtn() { paintEverywhereBtn.alpha = paintEverywhereMode ? 1 : 0.6; paintEverywhereBtn.width = paintEverywhereMode ? 140 : 120; paintEverywhereBtn.height = paintEverywhereMode ? 140 : 120; } updatePaintEverywhereBtn(); // Button event handlers paintEverywhereBtn.down = function (x, y, obj) { paintEverywhereMode = !paintEverywhereMode; updatePaintEverywhereBtn(); }; for (var gy = 0; gy <= GRID_SIZE; gy++) { var line = LK.getAsset('pixel', { anchorX: 0, anchorY: 0, width: GRID_SIZE * PIXEL_SIZE, height: 2, color: gridLineColor, alpha: gridLineAlpha }); line.x = GRID_MARGIN; line.y = GRID_TOP + gy * PIXEL_SIZE - 1; game.addChild(line); } // --- Create palette --- var paletteY = 90; // Top margin for palette var paletteSpacing = 150; var paletteStartX = Math.floor((2048 - PALETTE.length * paletteSpacing) / 2) + paletteSpacing / 2; for (var i = 0; i < PALETTE.length; i++) { var pal = new PaletteColor(); pal.setColorAsset(PALETTE[i].assetId, PALETTE[i].color); pal.x = paletteStartX + i * paletteSpacing; pal.y = paletteY; pal.setSelected(i === selectedPaletteIdx); // Store index for event handler pal.paletteIdx = i; // If this is the eraser, draw an eraser icon (simple X) if (PALETTE[i].color === null) { // Draw a simple "X" using two lines (using Text2 for simplicity) var eraserTxt = new Text2('🧹', { size: 60, fill: 0x888888 }); eraserTxt.anchor.set(0.5, 0.5); eraserTxt.x = 0; eraserTxt.y = 0; pal.addChild(eraserTxt); } paletteColors.push(pal); game.addChild(pal); } // --- Create clear button --- clearBtn = new ClearButton(); clearBtn.x = 2048 - 200; clearBtn.y = paletteY; game.addChild(clearBtn); // --- GUI: Title --- var titleTxt = new Text2('Pixel Painter 32x32', { size: 80, fill: 0x333333 }); titleTxt.anchor.set(0.5, 0); LK.gui.top.addChild(titleTxt); // --- Helper: get pixel at game coordinates (x, y) --- function getPixelAt(x, y) { // Convert to grid coordinates var gx = Math.floor((x - GRID_MARGIN) / PIXEL_SIZE); var gy = Math.floor((y - GRID_TOP) / PIXEL_SIZE); if (gx >= 0 && gx < GRID_SIZE && gy >= 0 && gy < GRID_SIZE) { return pixels[gy][gx]; } return null; } // --- Helper: get palette index at (x, y) --- function getPaletteIdxAt(x, y) { for (var i = 0; i < paletteColors.length; i++) { var pal = paletteColors[i]; var px = pal.x; var py = pal.y; // Palette color is 100x100, anchor 0.5,0.5 if (x >= px - 50 && x <= px + 50 && y >= py - 50 && y <= py + 50) { return i; } } return -1; } // --- Helper: is clear button at (x, y) --- function isClearBtnAt(x, y) { var px = clearBtn.x; var py = clearBtn.y; // Button is 180x100, anchor 0.5,0.5 return x >= px - 90 && x <= px + 90 && y >= py - 50 && y <= py + 50; } // --- Event handlers --- game.down = function (x, y, obj) { // Check palette var palIdx = getPaletteIdxAt(x, y); if (palIdx !== -1) { // Select color or eraser paletteColors[selectedPaletteIdx].setSelected(false); selectedPaletteIdx = palIdx; selectedColor = PALETTE[palIdx].color; paletteColors[selectedPaletteIdx].setSelected(true); dragFilling = false; return; } // Check clear button if (isClearBtnAt(x, y)) { clearBtn.setPressed(true); dragFilling = false; return; } // Check paint-everywhere button if (x >= paintEverywhereBtn.x - paintEverywhereBtn.width / 2 && x <= paintEverywhereBtn.x + paintEverywhereBtn.width / 2 && y >= paintEverywhereBtn.y - paintEverywhereBtn.height / 2 && y <= paintEverywhereBtn.y + paintEverywhereBtn.height / 2) { // handled by .down on the button itself dragFilling = false; return; } // Check grid var px = getPixelAt(x, y); if (px) { dragFilling = true; dragFillId++; if (PALETTE[selectedPaletteIdx].color === null) { // Eraser: set to white px.setColor(0xffffff); } else { px.setColor(selectedColor); } px.lastFillDragId = dragFillId; lastDragPixel = px; return; } // Paint everywhere mode: do nothing unless a pixel is clicked if (paintEverywhereMode) { // Do not paint unless a pixel is clicked (handled above) dragFilling = false; return; } dragFilling = false; }; game.move = function (x, y, obj) { // If dragging to fill if (dragFilling) { var px = getPixelAt(x, y); if (px && px.lastFillDragId !== dragFillId) { if (PALETTE[selectedPaletteIdx].color === null) { // Eraser: set to white px.setColor(0xffffff); } else { px.setColor(selectedColor); } px.lastFillDragId = dragFillId; lastDragPixel = px; } } }; game.up = function (x, y, obj) { // If released on clear button, clear grid if (isClearBtnAt(x, y)) { clearBtn.setPressed(false); // Clear all pixels for (var y2 = 0; y2 < GRID_SIZE; y2++) { for (var x2 = 0; x2 < GRID_SIZE; x2++) { pixels[y2][x2].setColor(0xffffff); } } dragFilling = false; return; } dragFilling = false; lastDragPixel = null; }; // --- Visual feedback for clear button --- clearBtn.down = function (x, y, obj) { clearBtn.setPressed(true); }; clearBtn.up = function (x, y, obj) { clearBtn.setPressed(false); }; // --- No update loop needed for MVP --- // --- Prevent elements in top-left 100x100 (menu area) --- /* All elements are placed away from (0,0)-(100,100) */
/****
* Classes
****/
// ClearButton class
var ClearButton = Container.expand(function () {
var self = Container.call(this);
var btnAsset = self.attachAsset('clear_btn', {
anchorX: 0.5,
anchorY: 0.5
});
// Add text label
var txt = new Text2('Clear', {
size: 48,
fill: 0x222222
});
txt.anchor.set(0.5, 0.5);
txt.x = 0;
txt.y = 0;
self.addChild(txt);
// Visual feedback
self.setPressed = function (pressed) {
btnAsset.tint = pressed ? 0xaaaaaa : 0xcccccc;
};
return self;
});
// PaletteColor class: represents a color in the palette
var PaletteColor = Container.expand(function () {
var self = Container.call(this);
// Attach the color asset, anchor at 0.5,0.5 (center)
var colorId = self.colorId; // will be set after creation
var assetId = self.assetId; // will be set after creation
var colorAsset = null;
// Set color and asset after creation
self.setColorAsset = function (assetId, colorId) {
self.assetId = assetId;
self.colorId = colorId;
colorAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
// Show selection border
self.setSelected = function (selected) {
if (selected) {
colorAsset.width = 120;
colorAsset.height = 120;
} else {
colorAsset.width = 100;
colorAsset.height = 100;
}
};
return self;
});
// No plugins needed for MVP
// Pixel class: represents a single cell in the grid
var Pixel = Container.expand(function () {
var self = Container.call(this);
// Attach the pixel asset, anchor at 0,0 (top-left)
var pixelAsset = self.attachAsset('pixel', {
anchorX: 0,
anchorY: 0
});
// Store color (as 0xRRGGBB)
self.color = 0xffffff;
// Set color
self.setColor = function (newColor) {
self.color = newColor;
pixelAsset.color = newColor;
pixelAsset.tint = newColor;
};
// For hit detection, store grid position
self.gridX = 0;
self.gridY = 0;
// For drag fill, track if this pixel was already filled in this drag
self.lastFillDragId = -1;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xf8f8f8
});
/****
* Game Code
****/
// --- Constants ---
// 32x32 grid, each cell is a square. We'll use a single shape asset for the pixel, and color it as needed.
// 56x56 so 32*56=1792, fits well in 2048 width with margin
// Palette colors: We'll use 10 basic colors for the palette
// Clear button
var GRID_SIZE = 32;
var PIXEL_SIZE = Math.floor(2048 / GRID_SIZE); // Make sure grid fits and is square
var GRID_MARGIN = Math.floor((2048 - GRID_SIZE * PIXEL_SIZE) / 2); // Center horizontally
var GRID_TOP = Math.floor((2048 - GRID_SIZE * PIXEL_SIZE) / 2) + 220; // Center vertically with space for palette at top
// Palette colors (assetId, color)
// Add eraser as a special palette entry at the end (color: null)
var PALETTE = [{
assetId: 'palette_red',
color: 0xff3b30
}, {
assetId: 'palette_orange',
color: 0xff9500
}, {
assetId: 'palette_yellow',
color: 0xffcc00
}, {
assetId: 'palette_green',
color: 0x34c759
}, {
assetId: 'palette_cyan',
color: 0x5ac8fa
}, {
assetId: 'palette_blue',
color: 0x007aff
}, {
assetId: 'palette_purple',
color: 0xaf52de
}, {
assetId: 'palette_brown',
color: 0xa2845e
}, {
assetId: 'palette_black',
color: 0x222222
}, {
assetId: 'palette_white',
color: 0xffffff
}, {
assetId: 'palette_gray',
color: 0x888888
}, {
assetId: 'palette_pink',
color: 0xff2d55
},
// Eraser tool (special entry)
{
assetId: 'palette_white',
// visually white, but will be drawn with eraser icon
color: null // null means eraser
}];
// --- State ---
var pixels = []; // 2D array [y][x] of Pixel
var paletteColors = []; // Array of PaletteColor
var selectedColor = PALETTE[0].color; // Default: red
var selectedPaletteIdx = 0;
var clearBtn = null;
var dragFilling = false;
var dragFillId = 0; // Incremented for each drag to avoid double-filling
var lastDragPixel = null;
// --- Create grid ---
for (var y = 0; y < GRID_SIZE; y++) {
pixels[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
var px = new Pixel();
px.x = GRID_MARGIN + x * PIXEL_SIZE;
px.y = GRID_TOP + y * PIXEL_SIZE;
px.gridX = x;
px.gridY = y;
px.setColor(0xffffff); // Start as white
game.addChild(px);
pixels[y][x] = px;
}
}
// --- Draw grid lines (semi-transparent black) ---
var gridLineColor = 0x000000;
var gridLineAlpha = 0.18; // semi-transparent
// Vertical lines
for (var gx = 0; gx <= GRID_SIZE; gx++) {
var line = LK.getAsset('pixel', {
anchorX: 0,
anchorY: 0,
width: 2,
height: GRID_SIZE * PIXEL_SIZE,
color: gridLineColor,
alpha: gridLineAlpha
});
line.x = GRID_MARGIN + gx * PIXEL_SIZE - 1;
line.y = GRID_TOP;
game.addChild(line);
}
// --- Add a much bigger frame below the grid (centered horizontally, with smaller gap) ---
var frameBigWidth = 768;
var frameBigHeight = 384;
var frameBigAsset = LK.getAsset('canvas_frame', {
anchorX: 0.5,
anchorY: 0,
width: frameBigWidth,
height: frameBigHeight
});
frameBigAsset.x = Math.floor(2048 / 2);
frameBigAsset.y = Math.floor(GRID_TOP + GRID_SIZE * PIXEL_SIZE + 8); // 8px below grid (smaller gap)
game.addChild(frameBigAsset);
// --- Add paint-everywhere button next to the frame ---
var paintEverywhereBtn = LK.getAsset('palette_blue', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
paintEverywhereBtn.x = frameBigAsset.x + frameBigWidth / 2 + 100;
paintEverywhereBtn.y = frameBigAsset.y + frameBigHeight / 2;
game.addChild(paintEverywhereBtn);
// Add icon/text to button
var paintIcon = new Text2('🖌', {
size: 80,
fill: 0xffffff
});
paintIcon.anchor.set(0.5, 0.5);
paintIcon.x = 0;
paintIcon.y = 0;
paintEverywhereBtn.addChild(paintIcon);
// State: is paint-everywhere mode active?
var paintEverywhereMode = false;
// Visual feedback for button
function updatePaintEverywhereBtn() {
paintEverywhereBtn.alpha = paintEverywhereMode ? 1 : 0.6;
paintEverywhereBtn.width = paintEverywhereMode ? 140 : 120;
paintEverywhereBtn.height = paintEverywhereMode ? 140 : 120;
}
updatePaintEverywhereBtn();
// Button event handlers
paintEverywhereBtn.down = function (x, y, obj) {
paintEverywhereMode = !paintEverywhereMode;
updatePaintEverywhereBtn();
};
for (var gy = 0; gy <= GRID_SIZE; gy++) {
var line = LK.getAsset('pixel', {
anchorX: 0,
anchorY: 0,
width: GRID_SIZE * PIXEL_SIZE,
height: 2,
color: gridLineColor,
alpha: gridLineAlpha
});
line.x = GRID_MARGIN;
line.y = GRID_TOP + gy * PIXEL_SIZE - 1;
game.addChild(line);
}
// --- Create palette ---
var paletteY = 90; // Top margin for palette
var paletteSpacing = 150;
var paletteStartX = Math.floor((2048 - PALETTE.length * paletteSpacing) / 2) + paletteSpacing / 2;
for (var i = 0; i < PALETTE.length; i++) {
var pal = new PaletteColor();
pal.setColorAsset(PALETTE[i].assetId, PALETTE[i].color);
pal.x = paletteStartX + i * paletteSpacing;
pal.y = paletteY;
pal.setSelected(i === selectedPaletteIdx);
// Store index for event handler
pal.paletteIdx = i;
// If this is the eraser, draw an eraser icon (simple X)
if (PALETTE[i].color === null) {
// Draw a simple "X" using two lines (using Text2 for simplicity)
var eraserTxt = new Text2('🧹', {
size: 60,
fill: 0x888888
});
eraserTxt.anchor.set(0.5, 0.5);
eraserTxt.x = 0;
eraserTxt.y = 0;
pal.addChild(eraserTxt);
}
paletteColors.push(pal);
game.addChild(pal);
}
// --- Create clear button ---
clearBtn = new ClearButton();
clearBtn.x = 2048 - 200;
clearBtn.y = paletteY;
game.addChild(clearBtn);
// --- GUI: Title ---
var titleTxt = new Text2('Pixel Painter 32x32', {
size: 80,
fill: 0x333333
});
titleTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(titleTxt);
// --- Helper: get pixel at game coordinates (x, y) ---
function getPixelAt(x, y) {
// Convert to grid coordinates
var gx = Math.floor((x - GRID_MARGIN) / PIXEL_SIZE);
var gy = Math.floor((y - GRID_TOP) / PIXEL_SIZE);
if (gx >= 0 && gx < GRID_SIZE && gy >= 0 && gy < GRID_SIZE) {
return pixels[gy][gx];
}
return null;
}
// --- Helper: get palette index at (x, y) ---
function getPaletteIdxAt(x, y) {
for (var i = 0; i < paletteColors.length; i++) {
var pal = paletteColors[i];
var px = pal.x;
var py = pal.y;
// Palette color is 100x100, anchor 0.5,0.5
if (x >= px - 50 && x <= px + 50 && y >= py - 50 && y <= py + 50) {
return i;
}
}
return -1;
}
// --- Helper: is clear button at (x, y) ---
function isClearBtnAt(x, y) {
var px = clearBtn.x;
var py = clearBtn.y;
// Button is 180x100, anchor 0.5,0.5
return x >= px - 90 && x <= px + 90 && y >= py - 50 && y <= py + 50;
}
// --- Event handlers ---
game.down = function (x, y, obj) {
// Check palette
var palIdx = getPaletteIdxAt(x, y);
if (palIdx !== -1) {
// Select color or eraser
paletteColors[selectedPaletteIdx].setSelected(false);
selectedPaletteIdx = palIdx;
selectedColor = PALETTE[palIdx].color;
paletteColors[selectedPaletteIdx].setSelected(true);
dragFilling = false;
return;
}
// Check clear button
if (isClearBtnAt(x, y)) {
clearBtn.setPressed(true);
dragFilling = false;
return;
}
// Check paint-everywhere button
if (x >= paintEverywhereBtn.x - paintEverywhereBtn.width / 2 && x <= paintEverywhereBtn.x + paintEverywhereBtn.width / 2 && y >= paintEverywhereBtn.y - paintEverywhereBtn.height / 2 && y <= paintEverywhereBtn.y + paintEverywhereBtn.height / 2) {
// handled by .down on the button itself
dragFilling = false;
return;
}
// Check grid
var px = getPixelAt(x, y);
if (px) {
dragFilling = true;
dragFillId++;
if (PALETTE[selectedPaletteIdx].color === null) {
// Eraser: set to white
px.setColor(0xffffff);
} else {
px.setColor(selectedColor);
}
px.lastFillDragId = dragFillId;
lastDragPixel = px;
return;
}
// Paint everywhere mode: do nothing unless a pixel is clicked
if (paintEverywhereMode) {
// Do not paint unless a pixel is clicked (handled above)
dragFilling = false;
return;
}
dragFilling = false;
};
game.move = function (x, y, obj) {
// If dragging to fill
if (dragFilling) {
var px = getPixelAt(x, y);
if (px && px.lastFillDragId !== dragFillId) {
if (PALETTE[selectedPaletteIdx].color === null) {
// Eraser: set to white
px.setColor(0xffffff);
} else {
px.setColor(selectedColor);
}
px.lastFillDragId = dragFillId;
lastDragPixel = px;
}
}
};
game.up = function (x, y, obj) {
// If released on clear button, clear grid
if (isClearBtnAt(x, y)) {
clearBtn.setPressed(false);
// Clear all pixels
for (var y2 = 0; y2 < GRID_SIZE; y2++) {
for (var x2 = 0; x2 < GRID_SIZE; x2++) {
pixels[y2][x2].setColor(0xffffff);
}
}
dragFilling = false;
return;
}
dragFilling = false;
lastDragPixel = null;
};
// --- Visual feedback for clear button ---
clearBtn.down = function (x, y, obj) {
clearBtn.setPressed(true);
};
clearBtn.up = function (x, y, obj) {
clearBtn.setPressed(false);
};
// --- No update loop needed for MVP ---
// --- Prevent elements in top-left 100x100 (menu area) ---
/* All elements are placed away from (0,0)-(100,100) */