User prompt
Undo the last thing
User prompt
Whenever they shoot make them walk a bit faster
User prompt
Make code so that wherever the player shoots that's where they go
User prompt
Make code so zombies don't do as much damage
User prompt
I didn't say make them instantly die make it like it was
User prompt
Can you code it so you can't get stuck Inside zombies so you die instantly please
User prompt
Can you make code for the player to be able to choose where to move
User prompt
Can you write code for it
Code edit (1 edits merged)
Please save this source code
User prompt
Zombie Bros: Apocalypse Duo
Initial prompt
Can you make a game about two brothers battling zombies in the apocalypse and you only control one brother. Make it feel like it is multiplayer with the other brother and also make it in an 8-bit type style please.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var AmmoDisplay = Container.expand(function () { var self = Container.call(this); self.indicators = []; self.player = null; self.init = function (playerRef) { self.player = playerRef; // Clear existing indicators for (var i = 0; i < self.indicators.length; i++) { self.removeChild(self.indicators[i]); } self.indicators = []; // Create new indicators for (var i = 0; i < self.player.maxAmmo; i++) { var indicator = LK.getAsset('ammoIndicator', { anchorX: 0.5, anchorY: 0.5, x: i * 20, y: 0 }); self.addChild(indicator); self.indicators.push(indicator); } }; self.update = function () { if (!self.player) { return; } // Update visibility based on current ammo for (var i = 0; i < self.indicators.length; i++) { self.indicators[i].visible = i < self.player.ammo; // Fade effect when reloading if (self.player.isReloading) { var reloadProgress = 1 - self.player.reloadCounter / self.player.reloadTime; if (i < Math.floor(reloadProgress * self.player.maxAmmo)) { self.indicators[i].visible = true; self.indicators[i].alpha = 0.5; } else { self.indicators[i].visible = false; } } else { self.indicators[i].alpha = 1; } } }; return self; }); var Blood = Container.expand(function () { var self = Container.call(this); var bloodGraphics = self.attachAsset('blood', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); self.lifespan = 60; // Frames self.init = function (x, y) { self.x = x; self.y = y; self.scale.x = 0.5 + Math.random() * 0.5; self.scale.y = 0.5 + Math.random() * 0.5; self.rotation = Math.random() * Math.PI * 2; self.alpha = 0.8; }; self.update = function () { self.lifespan--; if (self.lifespan <= 20) { self.alpha = self.lifespan / 20 * 0.8; } if (self.lifespan <= 0) { self.shouldDestroy = true; } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 15; self.damage = 25; self.direction = { x: 0, y: 0 }; self.shooter = null; self.update = function () { self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Destroy bullet if it goes off screen if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.shouldDestroy = true; } }; self.fire = function (startX, startY, targetX, targetY, shooterRef) { self.x = startX; self.y = startY; self.shooter = shooterRef; // Calculate direction var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.direction.x = dx / distance; self.direction.y = dy / distance; } }; return self; }); var HealthBar = Container.expand(function () { var self = Container.call(this); var background = self.attachAsset('healthBarBackground', { anchorX: 0, anchorY: 0 }); var bar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0, x: 2, y: 2 }); var playerText = new Text2("P1", { size: 20, fill: 0xFFFFFF }); playerText.anchor.set(0.5); playerText.x = background.width / 2; playerText.y = background.height / 2; self.addChild(playerText); self.player = null; self.init = function (playerRef, isPlayer1) { self.player = playerRef; playerText.setText(isPlayer1 ? "P1" : "P2"); }; self.update = function () { if (!self.player) { return; } var healthPercent = self.player.health / self.player.maxHealth; bar.scale.x = healthPercent; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); self.isPlayer1 = true; // Will be set when initializing var graphicsId = 'player1'; // Default self.health = 100; self.maxHealth = 100; self.moveSpeed = 5; self.fireRate = 15; // Frames between shots self.fireCounter = 0; self.ammo = 12; self.maxAmmo = 12; self.reloadTime = 120; // 2 seconds at 60fps self.reloadCounter = 0; self.isReloading = false; self.isDead = false; self.lastMoveDirection = { x: 0, y: -1 }; // Default facing up self.init = function (isFirstPlayer) { self.isPlayer1 = isFirstPlayer; graphicsId = isFirstPlayer ? 'player1' : 'player2'; if (self.graphics) { self.removeChild(self.graphics); } self.graphics = self.attachAsset(graphicsId, { anchorX: 0.5, anchorY: 0.5, tint: isFirstPlayer ? 0xff0000 : 0x0000ff // Red for player1, Blue for player2 }); self.health = self.maxHealth; var nameTag = new Text2(isFirstPlayer ? "Player 1" : "Player 2", { // Name tag size: 20, fill: 0xFFFFFF }); nameTag.anchor.set(0.5); nameTag.y = -self.graphics.height / 2 - 10; // Position above the player self.addChild(nameTag); self.ammo = self.maxAmmo; self.fireCounter = 0; self.reloadCounter = 0; self.isReloading = false; self.isDead = false; }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.health -= amount; if (self.health <= 0) { self.health = 0; self.die(); } else { // Flash red when hit LK.effects.flashObject(self, self.isPlayer1 ? 0xff0000 : 0x0000ff, 300); // Red flash for player1, Blue flash for player2 LK.getSound('playerHit').play(); } }; self.die = function () { self.isDead = true; self.alpha = 0.5; // Flash red when dead LK.effects.flashObject(self, 0xff0000, 1000); }; self.shoot = function (targetX, targetY) { if (self.isDead || self.isReloading) { return null; } if (self.ammo <= 0) { self.reload(); return null; } if (self.fireCounter <= 0) { // Create bullet var bullet = new Bullet(); bullet.fire(self.x, self.y, targetX, targetY, self); // Update state self.ammo--; self.fireCounter = self.fireRate; // Keep track of facing direction based on shot var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.lastMoveDirection.x = dx / distance; self.lastMoveDirection.y = dy / distance; } // Play sound LK.getSound('shoot').play(); if (self.isPlayer1) { LK.getSound('player1Shoot').play(); // Unique sound for player 1 } else { LK.getSound('player2Shoot').play(); // Unique sound for player 2 } return bullet; } return null; }; self.reload = function () { if (self.isDead || self.isReloading || self.ammo === self.maxAmmo) { return; } self.isReloading = true; self.reloadCounter = self.reloadTime; LK.getSound('reload').play(); }; self.move = function (dx, dy) { if (self.isDead) { return; } // If moving, update last move direction if (dx !== 0 || dy !== 0) { var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { self.lastMoveDirection.x = dx / length; self.lastMoveDirection.y = dy / length; } } // Apply movement var newX = self.x + dx * self.moveSpeed; var newY = self.y + dy * self.moveSpeed; // Constrain to game bounds with a margin var margin = 50; newX = Math.max(margin, Math.min(2048 - margin, newX)); newY = Math.max(margin, Math.min(2732 - margin, newY)); self.x = newX; self.y = newY; }; self.update = function () { // Update reload state if (self.isReloading) { self.reloadCounter--; if (self.reloadCounter <= 0) { self.isReloading = false; self.ammo = self.maxAmmo; } } // Update fire rate counter if (self.fireCounter > 0) { self.fireCounter--; } // AI behavior for Player 2 if (!self.isPlayer1 && !self.isDead) { self.updateAI(); } }; self.updateAI = function () { // Find the nearest zombie var nearestZombie = null; var nearestDistance = Number.MAX_VALUE; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var distance = getDistance(self, zombie); if (distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } // Move and shoot logic if (nearestZombie) { // If zombie is close, move away from it if (nearestDistance < 300) { var dx = self.x - nearestZombie.x; var dy = self.y - nearestZombie.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; self.move(dx, dy); } } // Otherwise, stay close to player 1 else { var distToPlayer1 = getDistance(self, player1); if (distToPlayer1 > 300) { var dx = player1.x - self.x; var dy = player1.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; self.move(dx, dy); } } } // Shoot at nearest zombie if not reloading and it's in range if (!self.isReloading && nearestDistance < 600) { var bullet = self.shoot(nearestZombie.x, nearestZombie.y); if (bullet) { bullets.push(bullet); game.addChild(bullet); } } // Reload if low on ammo and no zombies nearby if (self.ammo <= self.maxAmmo / 4 && nearestDistance > 400) { self.reload(); } } }; return self; }); var Zombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.moveSpeed = 2; self.damage = 5; self.attackRate = 60; // 1 second between attacks self.attackCounter = 0; self.isDead = false; self.init = function (x, y, moveSpeed, health) { self.x = x; self.y = y; self.moveSpeed = moveSpeed || 2; self.health = health || 100; }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.health -= amount; // Flash red when hit LK.effects.flashObject(self, 0xff0000, 150); LK.getSound('zombieHit').play(); if (self.health <= 0) { self.die(); } }; self.die = function () { self.isDead = true; self.shouldDestroy = true; // Create blood splatter createBlood(self.x, self.y); // Increase score currentScore += 10; updateScoreText(); }; self.attack = function (player) { if (self.isDead || self.attackCounter > 0) { return; } player.takeDamage(self.damage); self.attackCounter = self.attackRate; }; self.update = function () { if (self.isDead) { return; } // Update attack counter if (self.attackCounter > 0) { self.attackCounter--; } // Target the nearest player that's alive var target = null; if (!player1.isDead && !player2.isDead) { // Target closest player var dist1 = getDistance(self, player1); var dist2 = getDistance(self, player2); target = dist1 < dist2 ? player1 : player2; } else if (!player1.isDead) { target = player1; } else if (!player2.isDead) { target = player2; } if (target) { // Move towards target var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var moveX = dx / distance * self.moveSpeed; var moveY = dy / distance * self.moveSpeed; self.x += moveX; self.y += moveY; } // Attack if close enough if (distance < 80) { self.attack(target); // Push the player out if they are stuck inside the zombie if (distance < 40) { var pushX = dx / distance * (40 - distance); var pushY = dy / distance * (40 - distance); target.x += pushX; target.y += pushY; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game state variables var player1; var player2; var player1HealthBar; var player2HealthBar; var player1AmmoDisplay; var player2AmmoDisplay; var bullets = []; var zombies = []; var bloodSplatters = []; var currentWave = 0; var zombiesInWave = 0; var zombiesLeft = 0; var currentScore = 0; var gameActive = false; var waveText; var scoreText; var highScoreText; var gameStartText; var groundTiles = []; // Helper functions function getDistance(obj1, obj2) { var dx = obj1.x - obj2.x; var dy = obj1.y - obj2.y; return Math.sqrt(dx * dx + dy * dy); } function createGround() { var tileSize = 128; var tilesX = Math.ceil(2048 / tileSize); var tilesY = Math.ceil(2732 / tileSize); for (var y = 0; y < tilesY; y++) { for (var x = 0; x < tilesX; x++) { var tile = LK.getAsset('groundTile', { anchorX: 0, anchorY: 0, x: x * tileSize, y: y * tileSize, alpha: 0.3 }); // Add slight randomness to tiles tile.tint = 0x666666 + Math.floor(Math.random() * 0x111111); groundTiles.push(tile); game.addChild(tile); } } } function updateScoreText() { scoreText.setText("Score: " + currentScore); // Update high score if needed if (currentScore > storage.highScore) { storage.highScore = currentScore; highScoreText.setText("High Score: " + storage.highScore); } } function createBlood(x, y) { for (var i = 0; i < 3; i++) { var blood = new Blood(); blood.init(x + (Math.random() * 60 - 30), y + (Math.random() * 60 - 30)); bloodSplatters.push(blood); game.addChild(blood); } } function startGame() { // Reset game state gameActive = true; currentWave = 0; currentScore = 0; // Clear any existing entities clearEntities(); // Create players player1 = new Player(); player1.init(true); player1.x = 2048 / 2 - 150; player1.y = 2732 / 2; game.addChild(player1); player2 = new Player(); player2.init(false); player2.x = 2048 / 2 + 150; player2.y = 2732 / 2; game.addChild(player2); // Create health bars player1HealthBar = new HealthBar(); player1HealthBar.init(player1, true); player1HealthBar.x = 20; player1HealthBar.y = 100; LK.gui.left.addChild(player1HealthBar); player2HealthBar = new HealthBar(); player2HealthBar.init(player2, false); player2HealthBar.x = 20; player2HealthBar.y = 160; LK.gui.left.addChild(player2HealthBar); // Create ammo displays player1AmmoDisplay = new AmmoDisplay(); player1AmmoDisplay.init(player1); player1AmmoDisplay.x = 250; player1AmmoDisplay.y = 115; LK.gui.left.addChild(player1AmmoDisplay); player2AmmoDisplay = new AmmoDisplay(); player2AmmoDisplay.init(player2); player2AmmoDisplay.x = 250; player2AmmoDisplay.y = 175; LK.gui.left.addChild(player2AmmoDisplay); // Add UI elements for health and ammo var player1UI = new Container(); player1UI.addChild(player1HealthBar); player1UI.addChild(player1AmmoDisplay); LK.gui.left.addChild(player1UI); var player2UI = new Container(); player2UI.addChild(player2HealthBar); player2UI.addChild(player2AmmoDisplay); LK.gui.left.addChild(player2UI); // Update score display updateScoreText(); // Start first wave startNextWave(); // Hide start game text gameStartText.visible = false; // Play music LK.playMusic('bgMusic', { fade: { start: 0, end: 0.3, duration: 2000 } }); } function clearEntities() { // Remove all bullets for (var i = 0; i < bullets.length; i++) { if (bullets[i].parent) { bullets[i].parent.removeChild(bullets[i]); } } bullets = []; // Remove all zombies for (var i = 0; i < zombies.length; i++) { if (zombies[i].parent) { zombies[i].parent.removeChild(zombies[i]); } } zombies = []; // Remove all blood splatters for (var i = 0; i < bloodSplatters.length; i++) { if (bloodSplatters[i].parent) { bloodSplatters[i].parent.removeChild(bloodSplatters[i]); } } bloodSplatters = []; // Remove players and their UI if they exist if (player1 && player1.parent) { player1.parent.removeChild(player1); } if (player2 && player2.parent) { player2.parent.removeChild(player2); } // Remove UI elements if they exist if (player1HealthBar && player1HealthBar.parent) { player1HealthBar.parent.removeChild(player1HealthBar); } if (player2HealthBar && player2HealthBar.parent) { player2HealthBar.parent.removeChild(player2HealthBar); } if (player1AmmoDisplay && player1AmmoDisplay.parent) { player1AmmoDisplay.parent.removeChild(player1AmmoDisplay); } if (player2AmmoDisplay && player2AmmoDisplay.parent) { player2AmmoDisplay.parent.removeChild(player2AmmoDisplay); } } function spawnZombie() { if (zombiesLeft <= 0) { return; } var zombie = new Zombie(); // Determine zombie spawn position (outside the screen) var spawnSide = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left var x, y; switch (spawnSide) { case 0: // top x = Math.random() * 2048; y = -100; break; case 1: // right x = 2148; y = Math.random() * 2732; break; case 2: // bottom x = Math.random() * 2048; y = 2832; break; case 3: // left x = -100; y = Math.random() * 2732; break; } // Scale zombie difficulty based on wave var healthMultiplier = 1 + currentWave * 0.1; var speedMultiplier = 1 + currentWave * 0.05; zombie.init(x, y, 2 * speedMultiplier, 100 * healthMultiplier); zombies.push(zombie); game.addChild(zombie); zombiesLeft--; } function startNextWave() { currentWave++; zombiesInWave = 5 + currentWave * 3; zombiesLeft = zombiesInWave; waveText.setText("Wave " + currentWave); tween(waveText, { alpha: 1 }, { duration: 500 }); // Play wave start sound LK.getSound('waveStart').play(); // After 2 seconds, fade out the wave text LK.setTimeout(function () { tween(waveText, { alpha: 0 }, { duration: 500 }); }, 2000); // Initial zombie spawn for (var i = 0; i < Math.min(5, zombiesInWave); i++) { spawnZombie(); } } function checkGameOver() { if (!gameActive) { return; } // Game over if both players are dead if (player1.isDead && player2.isDead) { gameActive = false; // Show game over screen after a short delay LK.setTimeout(function () { LK.showGameOver(); }, 1500); // Stop music with fade out LK.playMusic('bgMusic', { fade: { start: 0.3, end: 0, duration: 1500 } }); } } function handlePlayerMovement() { if (!gameActive || player1.isDead) { return; } var dx = 0; var dy = 0; if (moveUp) { dy -= 1; } if (moveDown) { dy += 1; } if (moveLeft) { dx -= 1; } if (moveRight) { dx += 1; } // Normalize diagonal movement if (dx !== 0 && dy !== 0) { var length = Math.sqrt(dx * dx + dy * dy); dx /= length; dy /= length; } player1.move(dx, dy); } // Initialize UI elements function createUI() { // Wave text waveText = new Text2("Wave 1", { size: 100, fill: 0xFFFFFF }); waveText.anchor.set(0.5); waveText.alpha = 0; LK.gui.center.addChild(waveText); // Score text scoreText = new Text2("Score: 0", { size: 40, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); scoreText.y = 20; LK.gui.top.addChild(scoreText); // High score text highScoreText = new Text2("High Score: " + storage.highScore, { size: 30, fill: 0xAAAAAA }); highScoreText.anchor.set(0.5, 0); highScoreText.y = 70; LK.gui.top.addChild(highScoreText); // Game start text gameStartText = new Text2("TAP TO START", { size: 80, fill: 0xFFFFFF }); gameStartText.anchor.set(0.5); LK.gui.center.addChild(gameStartText); // Pulse animation for game start text function pulseText() { tween(gameStartText.scale, { x: 1.1, y: 1.1 }, { duration: 800, easing: tween.sinceOut, onFinish: function onFinish() { tween(gameStartText.scale, { x: 1, y: 1 }, { duration: 800, easing: tween.sinceIn, onFinish: pulseText }); } }); } pulseText(); } // Input tracking variables var moveUp = false; var moveDown = false; var moveLeft = false; var moveRight = false; var isShooting = false; var shootTarget = { x: 0, y: 0 }; // Input handlers game.down = function (x, y, obj) { if (!gameActive) { startGame(); return; } // Determine which side of the screen was pressed if (x < 2048 / 2) { // Left side controls movement var centerX = 2048 / 4; var centerY = 2732 / 2; var dx = x - centerX; var dy = y - centerY; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; } moveUp = dy < -0.5; moveDown = dy > 0.5; moveLeft = dx < -0.5; moveRight = dx > 0.5; } else { // Right side controls shooting isShooting = true; shootTarget.x = x; shootTarget.y = y; } }; game.up = function (x, y, obj) { // Stop movement or shooting based on which side was released if (x < 2048 / 2) { moveUp = moveDown = moveLeft = moveRight = false; } else { isShooting = false; } }; game.move = function (x, y, obj) { // Update shoot target if shooting if (isShooting && x > 2048 / 2) { shootTarget.x = x; shootTarget.y = y; } // Update movement direction if on left side if (x < 2048 / 2) { var centerX = 2048 / 4; var centerY = 2732 / 2; var dx = x - centerX; var dy = y - centerY; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; } moveUp = dy < -0.5; moveDown = dy > 0.5; moveLeft = dx < -0.5; moveRight = dx > 0.5; } }; // Create the ground and UI createGround(); createUI(); // Main game update loop game.update = function () { if (gameActive) { // Handle player movement handlePlayerMovement(); // Handle player shooting if (isShooting && !player1.isDead) { var bullet = player1.shoot(shootTarget.x, shootTarget.y); if (bullet) { bullets.push(bullet); game.addChild(bullet); } } // Update players player1.update(); player2.update(); // Update health bars player1HealthBar.update(); player2HealthBar.update(); // Update ammo displays player1AmmoDisplay.update(); player2AmmoDisplay.update(); // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; bullet.update(); // Check for bullet collisions with zombies for (var j = zombies.length - 1; j >= 0; j--) { var zombie = zombies[j]; if (!zombie.isDead && bullet.intersects(zombie)) { zombie.takeDamage(bullet.damage); bullet.shouldDestroy = true; break; } } // Remove bullets marked for destruction if (bullet.shouldDestroy) { game.removeChild(bullet); bullets.splice(i, 1); } } // Update zombies for (var i = zombies.length - 1; i >= 0; i--) { var zombie = zombies[i]; zombie.update(); // Remove zombies marked for destruction if (zombie.shouldDestroy) { game.removeChild(zombie); zombies.splice(i, 1); } } // Update blood splatters for (var i = bloodSplatters.length - 1; i >= 0; i--) { var blood = bloodSplatters[i]; blood.update(); // Remove blood marked for destruction if (blood.shouldDestroy) { game.removeChild(blood); bloodSplatters.splice(i, 1); } } // Spawn more zombies if needed if (zombies.length === 0 && zombiesLeft === 0) { // All zombies in wave defeated, start next wave startNextWave(); } else if (zombies.length < 10 && zombiesLeft > 0 && LK.ticks % 60 === 0) { // Spawn a new zombie every second if there are less than 10 on screen spawnZombie(); } // Check for game over checkGameOver(); } }; // Play music with low volume LK.playMusic('bgMusic', { volume: 0.3 });
===================================================================
--- original.js
+++ change.js
@@ -238,16 +238,8 @@
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.lastMoveDirection.x = dx / distance;
self.lastMoveDirection.y = dy / distance;
- // Temporarily increase speed after shooting
- var originalSpeed = self.moveSpeed;
- self.moveSpeed *= 1.5; // Increase speed by 50%
- self.move(self.lastMoveDirection.x, self.lastMoveDirection.y); // Move player towards the shot direction
- // Reset speed after a short duration
- LK.setTimeout(function () {
- self.moveSpeed = originalSpeed;
- }, 500); // Speed boost lasts for 500ms
}
// Play sound
LK.getSound('shoot').play();
if (self.isPlayer1) {
Make it a 8 bit zombie that is menacing. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Make an 8 bit boy. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Make an 8 bit black and white grayscale boy. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Make an 8bit red blood splater. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Make an green grass 8bit texture 2d and flat. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows