// === BUILD PLAN ===
// Style: Pixel Art - Balatro Joker Card
// Footprint: 50 wide (X) x 1 deep (Z) x 70 tall (Y)
// Card shape on XY plane facing south
//
// FRONT VIEW (X horizontal, Y vertical):
// GGGGGGGGGGGGGGGGGGG <- Gold border top
// G BALATRO JOKER G
// G +-----------+ G <- Card inner area (dark purple bg)
// G | JESTER | G <- Jester face: hat, eyes, smile
// G | /|\ /|\ | G <- Jester hat bells
// G | O O | G <- Eyes
// G | \_/ | G <- Smile
// G | diamonds | G <- Card suit symbols
// G +-----------+ G
// GGGGGGGGGGGGGGGGGGG <- Gold border bottom
//
// SIDE VIEW: 3-4 blocks deep (backing + art + glow layer)
//
// Materials:
// Border: gold_block, yellow_concrete
// Card bg: purple_concrete, blue_concrete
// Jester skin: yellow_concrete, orange_concrete
// Jester hat: red_concrete, blue_concrete (three points)
// Jester collar: white_concrete, yellow_concrete
// Eyes/mouth: black_concrete, white_concrete
// Glow border: glowstone behind red/blue stained glass
// Card corners: black_concrete (rounded effect)
// Suits: red_concrete (diamonds/hearts)
// === END PLAN ===
// --- CARD DIMENSIONS ---
var cardW = 42; // card width
var cardH = 62; // card height
var startX = 0;
var startY = 0;
var wallZ = 0; // main art plane (front face, visible from front/south)
var backZ = 1; // backing layer
var glowZ = 2; // glow layer behind
// --- HELPER: place pixel on art wall ---
// Art on Z=0 plane, no X mirror - "front" render looks from -Z toward +Z
// so what we see in isometric (from south-west) shows the art correctly
function px(x, y, block) {
pb(startX + x, startY + y, wallZ, b(block));
}
function pxBack(x, y, block) {
pb(startX + x, startY + y, backZ, b(block));
}
function pxGlow(x, y, block) {
pb(startX + x, startY + y, glowZ, b(block));
}
// === PHASE 1: BACKING LAYER (solid support) ===
for (var y = 0; y < cardH; y++) {
for (var x = 0; x < cardW; x++) {
pxBack(x, y, "black_concrete");
}
}
// === PHASE 2: GLOW BORDER ===
// Glow frame behind the card edges
for (var y = 0; y < cardH; y++) {
for (var x = 0; x < cardW; x++) {
var isEdge = (x < 3 || x >= cardW - 3 || y < 3 || y >= cardH - 3);
var isInner = (x >= 3 && x < cardW - 3 && y >= 3 && y < cardH - 3);
if (isEdge) {
// Alternate glowstone and sea_lantern for neon effect
if ((x + y) % 3 === 0) {
pxGlow(x, y, "glowstone");
} else if ((x + y) % 3 === 1) {
pxGlow(x, y, "sea_lantern");
} else {
pxGlow(x, y, "magenta_concrete");
}
}
}
}
// === PHASE 3: CARD BASE ===
// Gold border (3 blocks wide)
for (var y = 0; y < cardH; y++) {
for (var x = 0; x < cardW; x++) {
var isBorder = (x < 3 || x >= cardW - 3 || y < 3 || y >= cardH - 3);
// Rounded corners: skip corner pixels
var isCorner = false;
// bottom-left
if (x < 2 && y < 2) isCorner = true;
if (x === 0 && y === 2) isCorner = true;
if (x === 2 && y === 0) isCorner = true;
// bottom-right
if (x >= cardW - 2 && y < 2) isCorner = true;
if (x === cardW - 1 && y === 2) isCorner = true;
if (x === cardW - 3 && y === 0) isCorner = true;
// top-left
if (x < 2 && y >= cardH - 2) isCorner = true;
if (x === 0 && y === cardH - 3) isCorner = true;
if (x === 2 && y === cardH - 1) isCorner = true;
// top-right
if (x >= cardW - 2 && y >= cardH - 2) isCorner = true;
if (x === cardW - 1 && y === cardH - 3) isCorner = true;
if (x === cardW - 3 && y === cardH - 1) isCorner = true;
if (isCorner) {
// leave as air for rounded corners
} else if (isBorder) {
// Gold border with variation
if ((x + y) % 4 === 0) {
px(x, y, "gold_block");
} else {
px(x, y, "yellow_concrete");
}
} else {
// Inner card background - deep purple/dark blue gradient
var normalizedY = (y - 3) / (cardH - 6);
if (normalizedY < 0.33) {
px(x, y, "blue_concrete");
} else if (normalizedY < 0.66) {
px(x, y, "purple_concrete");
} else {
if ((x + y) % 2 === 0) {
px(x, y, "purple_concrete");
} else {
px(x, y, "blue_concrete");
}
}
}
}
}
// === PHASE 4: JOKER / JESTER CHARACTER ===
// Center of card
var cx = Math.floor(cardW / 2); // 21
var cy = Math.floor(cardH / 2); // 31
// --- JESTER HAT (3 points with bells) ---
// Hat base at head top
var hatBaseY = cy + 12;
// Center point (tallest)
for (var i = 0; i < 8; i++) {
px(cx, hatBaseY + i, "red_concrete");
px(cx - 1, hatBaseY + i, "red_concrete");
if (i < 6) {
px(cx + 1, hatBaseY + i, "red_concrete");
}
}
// Bell on center point
px(cx, hatBaseY + 8, "yellow_concrete");
px(cx - 1, hatBaseY + 8, "gold_block");
// Left point
for (var i = 0; i < 6; i++) {
px(cx - 5 - i, hatBaseY + i - 2, "blue_concrete");
px(cx - 5 - i + 1, hatBaseY + i - 2, "blue_concrete");
}
// Bell on left
px(cx - 11, hatBaseY + 3, "yellow_concrete");
px(cx - 10, hatBaseY + 4, "gold_block");
// Right point
for (var i = 0; i < 6; i++) {
px(cx + 5 + i, hatBaseY + i - 2, "purple_wool");
px(cx + 5 + i - 1, hatBaseY + i - 2, "purple_wool");
}
// Bell on right
px(cx + 11, hatBaseY + 3, "yellow_concrete");
px(cx + 10, hatBaseY + 4, "gold_block");
// Hat brim
for (var x = cx - 7; x <= cx + 7; x++) {
px(x, hatBaseY - 1, "red_concrete");
px(x, hatBaseY, "blue_concrete");
}
// --- FACE (rounded, below hat) ---
var faceTopY = hatBaseY - 2;
var faceBottomY = faceTopY - 10;
// Face oval shape
var faceRows = [
{ w: 10, c: "yellow_concrete" }, // top of head
{ w: 12, c: "yellow_concrete" },
{ w: 14, c: "yellow_concrete" },
{ w: 14, c: "yellow_concrete" },
{ w: 14, c: "orange_concrete" }, // eyes row (will overlay)
{ w: 14, c: "yellow_concrete" },
{ w: 14, c: "yellow_concrete" }, // nose
{ w: 12, c: "yellow_concrete" }, // mouth row
{ w: 12, c: "orange_concrete" }, // chin area
{ w: 10, c: "orange_concrete" },
{ w: 8, c: "orange_concrete" }, // chin
];
for (var row = 0; row < faceRows.length; row++) {
var fw = faceRows[row].w;
var fc = faceRows[row].c;
var fy = faceTopY - row;
var fxStart = cx - Math.floor(fw / 2);
for (var dx = 0; dx < fw; dx++) {
px(fxStart + dx, fy, fc);
}
}
// Eyes - large cartoonish (Balatro style)
var eyeY = faceTopY - 4;
// Left eye
px(cx - 4, eyeY, "white_concrete");
px(cx - 3, eyeY, "white_concrete");
px(cx - 4, eyeY + 1, "white_concrete");
px(cx - 3, eyeY + 1, "white_concrete");
px(cx - 3, eyeY, "black_concrete"); // pupil
px(cx - 4, eyeY + 1, "black_concrete"); // pupil
// Right eye
px(cx + 3, eyeY, "white_concrete");
px(cx + 4, eyeY, "white_concrete");
px(cx + 3, eyeY + 1, "white_concrete");
px(cx + 4, eyeY + 1, "white_concrete");
px(cx + 4, eyeY, "black_concrete"); // pupil
px(cx + 3, eyeY + 1, "black_concrete"); // pupil
// Eyebrows
px(cx - 5, eyeY + 2, "brown_concrete");
px(cx - 4, eyeY + 2, "brown_concrete");
px(cx - 3, eyeY + 2, "brown_concrete");
px(cx + 3, eyeY + 2, "brown_concrete");
px(cx + 4, eyeY + 2, "brown_concrete");
px(cx + 5, eyeY + 2, "brown_concrete");
// Red nose (clown nose!)
px(cx, eyeY - 1, "red_concrete");
px(cx - 1, eyeY - 1, "red_concrete");
px(cx, eyeY - 2, "red_concrete");
// Big smile
var smileY = faceTopY - 7;
for (var sx = -4; sx <= 4; sx++) {
px(cx + sx, smileY, "red_concrete");
}
px(cx - 5, smileY + 1, "red_concrete");
px(cx + 5, smileY + 1, "red_concrete");
// Teeth
px(cx - 1, smileY, "white_concrete");
px(cx, smileY, "white_concrete");
px(cx + 1, smileY, "white_concrete");
// --- COLLAR / RUFF ---
var collarY = faceBottomY - 1;
for (var dx = -8; dx <= 8; dx++) {
if (Math.abs(dx) % 2 === 0) {
px(cx + dx, collarY, "white_concrete");
px(cx + dx, collarY - 1, "white_concrete");
} else {
px(cx + dx, collarY, "yellow_concrete");
px(cx + dx, collarY - 1, "gold_block");
}
}
// Zigzag collar bottom
for (var dx = -8; dx <= 8; dx++) {
if (dx % 2 === 0) {
px(cx + dx, collarY - 2, "white_concrete");
}
}
// --- BODY (split two colors - classic jester) ---
var bodyTopY = collarY - 3;
var bodyBottomY = bodyTopY - 14;
for (var by = bodyBottomY; by <= bodyTopY; by++) {
// Body width tapers
var bodyProgress = (bodyTopY - by) / (bodyTopY - bodyBottomY);
var bodyHalfW = Math.floor(8 - bodyProgress * 2);
for (var dx = -bodyHalfW; dx <= bodyHalfW; dx++) {
if (dx < 0) {
// Left side - red
px(cx + dx, by, "red_concrete");
} else if (dx > 0) {
// Right side - blue
px(cx + dx, by, "blue_concrete");
} else {
// Center seam - alternate
if (by % 2 === 0) {
px(cx, by, "red_concrete");
} else {
px(cx, by, "blue_concrete");
}
}
}
}
// Diamond pattern on body
for (var dy = 0; dy < 12; dy += 4) {
var diaY = bodyTopY - 1 - dy;
// Left side diamonds (gold on red)
px(cx - 3, diaY, "gold_block");
px(cx - 4, diaY - 1, "gold_block");
px(cx - 2, diaY - 1, "gold_block");
px(cx - 3, diaY - 2, "gold_block");
// Right side diamonds (gold on blue)
px(cx + 3, diaY, "gold_block");
px(cx + 4, diaY - 1, "gold_block");
px(cx + 2, diaY - 1, "gold_block");
px(cx + 3, diaY - 2, "gold_block");
}
// --- ARMS ---
// Left arm
for (var i = 0; i < 8; i++) {
px(cx - 8 - i, bodyTopY - 2 - Math.floor(i / 2), "red_concrete");
px(cx - 8 - i, bodyTopY - 3 - Math.floor(i / 2), "red_concrete");
}
// Left hand
px(cx - 15, bodyTopY - 5, "yellow_concrete");
px(cx - 16, bodyTopY - 5, "yellow_concrete");
px(cx - 15, bodyTopY - 6, "yellow_concrete");
// Right arm
for (var i = 0; i < 8; i++) {
px(cx + 8 + i, bodyTopY - 2 - Math.floor(i / 2), "blue_concrete");
px(cx + 8 + i, bodyTopY - 3 - Math.floor(i / 2), "blue_concrete");
}
// Right hand
px(cx + 15, bodyTopY - 5, "yellow_concrete");
px(cx + 16, bodyTopY - 5, "yellow_concrete");
px(cx + 15, bodyTopY - 6, "yellow_concrete");
// === PHASE 5: CARD SUIT SYMBOLS (top-left and bottom-right) ===
// Top-left: small diamond suit
var suitX1 = 5;
var suitY1 = cardH - 8;
px(suitX1, suitY1, "red_concrete");
px(suitX1 - 1, suitY1 - 1, "red_concrete");
px(suitX1 + 1, suitY1 - 1, "red_concrete");
px(suitX1, suitY1 - 2, "red_concrete");
px(suitX1, suitY1 + 1, "red_concrete");
px(suitX1 - 1, suitY1, "red_concrete"); // make it wider
px(suitX1 + 1, suitY1, "red_concrete");
// "J" letter top-left
px(suitX1, suitY1 + 4, "white_concrete");
px(suitX1 + 1, suitY1 + 4, "white_concrete");
px(suitX1 + 1, suitY1 + 3, "white_concrete");
px(suitX1 + 1, suitY1 + 2, "white_concrete");
px(suitX1, suitY1 + 2, "white_concrete");
px(suitX1 - 1, suitY1 + 2, "white_concrete");
px(suitX1 - 1, suitY1 + 3, "white_concrete");
// Bottom-right: small diamond suit (inverted)
var suitX2 = cardW - 6;
var suitY2 = 7;
px(suitX2, suitY2, "red_concrete");
px(suitX2 - 1, suitY2 + 1, "red_concrete");
px(suitX2 + 1, suitY2 + 1, "red_concrete");
px(suitX2, suitY2 + 2, "red_concrete");
px(suitX2, suitY2 - 1, "red_concrete");
px(suitX2 - 1, suitY2, "red_concrete");
px(suitX2 + 1, suitY2, "red_concrete");
// "J" bottom-right (inverted)
px(suitX2, suitY2 - 2, "white_concrete");
px(suitX2 - 1, suitY2 - 2, "white_concrete");
px(suitX2 - 1, suitY2 - 3, "white_concrete");
px(suitX2 - 1, suitY2 - 4, "white_concrete");
px(suitX2, suitY2 - 4, "white_concrete");
px(suitX2 + 1, suitY2 - 4, "white_concrete");
px(suitX2 + 1, suitY2 - 3, "white_concrete");
// === PHASE 6: "JOKER" TEXT at bottom of card ===
// Simple pixel text "JOKER" across the bottom
var textY = 5;
var textStartX = cx - 12;
// J
var jx = textStartX;
px(jx + 2, textY + 4, "white_concrete");
px(jx + 1, textY + 4, "white_concrete");
px(jx + 2, textY + 3, "white_concrete");
px(jx + 2, textY + 2, "white_concrete");
px(jx + 2, textY + 1, "white_concrete");
px(jx + 1, textY, "white_concrete");
px(jx, textY + 1, "white_concrete");
// O
var oox = textStartX + 5;
px(oox, textY + 1, "white_concrete");
px(oox, textY + 2, "white_concrete");
px(oox, textY + 3, "white_concrete");
px(oox + 1, textY + 4, "white_concrete");
px(oox + 2, textY + 4, "white_concrete");
px(oox + 3, textY + 3, "white_concrete");
px(oox + 3, textY + 2, "white_concrete");
px(oox + 3, textY + 1, "white_concrete");
px(oox + 2, textY, "white_concrete");
px(oox + 1, textY, "white_concrete");
// K
var kx = textStartX + 10;
px(kx, textY, "white_concrete");
px(kx, textY + 1, "white_concrete");
px(kx, textY + 2, "white_concrete");
px(kx, textY + 3, "white_concrete");
px(kx, textY + 4, "white_concrete");
px(kx + 1, textY + 2, "white_concrete");
px(kx + 2, textY + 3, "white_concrete");
px(kx + 2, textY + 1, "white_concrete");
px(kx + 3, textY + 4, "white_concrete");
px(kx + 3, textY, "white_concrete");
// E
var ex = textStartX + 15;
px(ex, textY, "white_concrete");
px(ex, textY + 1, "white_concrete");
px(ex, textY + 2, "white_concrete");
px(ex, textY + 3, "white_concrete");
px(ex, textY + 4, "white_concrete");
px(ex + 1, textY, "white_concrete");
px(ex + 2, textY, "white_concrete");
px(ex + 1, textY + 2, "white_concrete");
px(ex + 2, textY + 2, "white_concrete");
px(ex + 1, textY + 4, "white_concrete");
px(ex + 2, textY + 4, "white_concrete");
// R
var rx = textStartX + 19;
px(rx, textY, "white_concrete");
px(rx, textY + 1, "white_concrete");
px(rx, textY + 2, "white_concrete");
px(rx, textY + 3, "white_concrete");
px(rx, textY + 4, "white_concrete");
px(rx + 1, textY + 4, "white_concrete");
px(rx + 2, textY + 4, "white_concrete");
px(rx + 2, textY + 3, "white_concrete");
px(rx + 1, textY + 2, "white_concrete");
px(rx + 2, textY + 2, "white_concrete");
px(rx + 2, textY + 1, "white_concrete");
px(rx + 3, textY, "white_concrete");
// === PHASE 7: DECORATIVE STARS / SPARKLES around card ===
// Small sparkle dots around the card for that Balatro magic feel
var sparkles = [
[-2, 15], [-3, 30], [-2, 50], [-1, 40],
[cardW + 1, 20], [cardW + 2, 35], [cardW + 1, 55], [cardW + 2, 45],
[10, cardH + 1], [20, cardH + 2], [30, cardH + 1],
[10, -2], [25, -1], [35, -2]
];
for (var s = 0; s < sparkles.length; s++) {
var sx2 = sparkles[s][0];
var sy2 = sparkles[s][1];
pb(startX + sx2, startY + sy2, wallZ, b("glowstone"));
}
// === LANDSCAPING ===