Geordie Lander - Newcastle Arcade
by LunarPanda99675 lines19.7 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Geordie Lander - Newcastle Arcade</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
touch-action: none; /* Prevents mobile scrolling/zooming interference */
}
body {
background-color: #0b0f19;
color: #f3f4f6;
font-family: 'Courier New', Courier, monospace;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
overflow: hidden;
user-select: none;
-webkit-user-select: none;
}
#game-container {
position: relative;
width: 100%;
max-width: 420px;
aspect-ratio: 3 / 4;
box-shadow: 0 0 30px rgba(0, 229, 255, 0.3);
border: 3px solid #1f2937;
border-radius: 12px;
background: #050508;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display: block;
background: linear-gradient(to bottom, #020617 0%, #0f172a 75%, #1e1b4b 100%);
}
/* Very subtle CRT scanline effect */
#game-container::after {
content: " ";
display: block;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.05) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.005), rgba(0, 255, 0, 0.002), rgba(0, 0, 255, 0.005));
z-index: 10;
background-size: 100% 4px, 6px 100%;
pointer-events: none;
}
.ui-overlay {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
display: flex;
justify-content: space-between;
pointer-events: none;
z-index: 20;
font-size: clamp(10px, 2.8vw, 13px);
font-weight: bold;
text-shadow: 2px 2px #000;
color: #38bdf8;
letter-spacing: 1px;
}
.hud-box {
background: rgba(15, 23, 42, 0.88);
padding: 6px 10px;
border: 2px solid #38bdf8;
border-radius: 6px;
}
.fuel-bar-container {
width: 75px;
height: 9px;
background: #334155;
border: 1px solid #94a3b8;
margin-top: 4px;
border-radius: 2px;
overflow: hidden;
}
.fuel-bar-fill {
height: 100%;
background: #22c55e;
width: 100%;
transition: width 0.1s linear;
}
#mobile-controls {
position: absolute;
bottom: 10px;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 0 12px;
z-index: 25;
pointer-events: auto;
}
.mob-group {
display: flex;
gap: 8px;
}
.mob-btn {
background: rgba(30, 41, 59, 0.92);
border: 2px solid #38bdf8;
color: #38bdf8;
font-size: 16px;
padding: 12px 16px;
border-radius: 10px;
font-family: inherit;
font-weight: bold;
cursor: pointer;
box-shadow: 0 3px 8px rgba(0,0,0,0.5);
outline: none;
}
.mob-btn:active, .mob-btn.pressed {
background: #38bdf8;
color: #0f172a;
transform: scale(0.95);
}
#btn-thrust {
border-color: #22c55e;
color: #22c55e;
padding: 12px 22px;
}
#btn-thrust:active, #btn-thrust.pressed {
background: #22c55e;
color: #0f172a;
}
</style>
</head>
<body>
<div id="game-container">
<div class="ui-overlay">
<div class="hud-box">
<div>SCORE: <span id="score-val">0</span></div>
<div>PILOT: <span id="pilot-val" style="color: #facc15;">PJ</span></div>
</div>
<div class="hud-box" style="text-align: right;">
<div>TOON FUEL</div>
<div class="fuel-bar-container">
<div id="fuel-fill" class="fuel-bar-fill"></div>
</div>
</div>
</div>
<canvas id="canvas" width="600" height="800"></canvas>
<div id="mobile-controls">
<div class="mob-group">
<button class="mob-btn" id="btn-left">◀</button>
<button class="mob-btn" id="btn-right">▶</button>
</div>
<button class="mob-btn" id="btn-thrust">🚀 THRUST</button>
</div>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// UI Elements
const scoreEl = document.getElementById('score-val');
const pilotEl = document.getElementById('pilot-val');
const fuelFillEl = document.getElementById('fuel-fill');
// Audio Context for sound effects
let audioCtx = null;
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
}
function playSound(type) {
if (!audioCtx) return;
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
if (type === 'thrust') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(80, now);
osc.frequency.exponentialRampToValueAtTime(40, now + 0.1);
gain.gain.setValueAtTime(0.15, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.1);
osc.start(now);
osc.stop(now + 0.1);
} else if (type === 'success') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(300, now);
osc.frequency.exponentialRampToValueAtTime(600, now + 0.3);
gain.gain.setValueAtTime(0.2, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.3);
osc.start(now);
osc.stop(now + 0.3);
} else if (type === 'crash') {
osc.type = 'square';
osc.frequency.setValueAtTime(120, now);
osc.frequency.linearRampToValueAtTime(30, now + 0.5);
gain.gain.setValueAtTime(0.3, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.5);
osc.start(now);
osc.stop(now + 0.5);
}
}
// Game States
const STATE_START = 0;
const STATE_PLAYING = 1;
const STATE_LANDED = 2;
const STATE_CRASHED = 3;
let gameState = STATE_START;
let score = 0;
let currentPilot = 0; // 0 = PJ, 1 = Duncan
const pilotNames = ["PJ", "Duncan"];
// Lander physics
let lander = {
x: 300,
y: 90,
vx: 0.3,
vy: 0,
angle: 0,
fuel: 1000,
maxFuel: 1000,
width: 36,
height: 44
};
// Alan Shearer Encouragement Coach
let shearer = {
active: false,
text: "",
timer: 0,
x: -300
};
let shearerCooldown = 0;
const shearerQuotes = [
"Haway man! Keep her steady, son!",
"Absolute belta! That's how we do it in Newcastle!",
"Even Kevin Keegan would be proud of that!",
"Show them how number 9 does it!",
"Divvent crash into the Tyne Bridge, pet!",
"Why aye! Champion flying!",
"Proper mint landing, that!",
"Smashing effort, marra!",
"Gan canny with the throttle, pet!",
"Sweeter than a Greggs steak bake!",
"Belter of a flight path, marra!",
"Even the Mackems couldn't fly it better!",
"Pure radge behavior up there, son!",
"Hadaway man, watch your descent!"
];
function triggerShearer(msg = null) {
shearer.text = msg || shearerQuotes[Math.floor(Math.random() * shearerQuotes.length)];
shearer.active = true;
shearer.timer = 240; // 4 seconds visible
}
// Landing Pads definitions
const pads = [
{ x: 60, y: 710, w: 90, multiplier: 4, name: "St. James' Park Roof (Hard)" },
{ x: 230, y: 730, w: 120, multiplier: 2, name: "Grey Street (Medium)" },
{ x: 420, y: 720, w: 120, multiplier: 1, name: "The Quayside (Easy)" }
];
// Controls state
const keys = { left: false, right: false, thrust: false };
window.addEventListener('keydown', e => {
initAudio();
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = true;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = true;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') {
keys.thrust = true;
e.preventDefault();
}
if (e.code === 'Enter' && (gameState === STATE_START || gameState === STATE_LANDED || gameState === STATE_CRASHED)) {
resetGame(gameState === STATE_CRASHED);
}
});
window.addEventListener('keyup', e => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
if (e.code === 'ArrowUp' || e.code === 'KeyW' || e.code === 'Space') keys.thrust = false;
});
// Robust Mobile Touch Controls
function bindTouch(id, keyName) {
const el = document.getElementById(id);
const activate = (e) => {
initAudio();
keys[keyName] = true;
el.classList.add('pressed');
e.preventDefault();
};
const deactivate = (e) => {
keys[keyName] = false;
el.classList.remove('pressed');
e.preventDefault();
};
el.addEventListener('touchstart', activate, { passive: false });
el.addEventListener('touchend', deactivate, { passive: false });
el.addEventListener('touchcancel', deactivate, { passive: false });
el.addEventListener('mousedown', (e) => { initAudio(); keys[keyName] = true; el.classList.add('pressed'); });
el.addEventListener('mouseup', (e) => { keys[keyName] = false; el.classList.remove('pressed'); });
el.addEventListener('mouseleave', (e) => { keys[keyName] = false; el.classList.remove('pressed'); });
}
bindTouch('btn-left', 'left');
bindTouch('btn-right', 'right');
bindTouch('btn-thrust', 'thrust');
canvas.addEventListener('click', () => {
initAudio();
if (gameState === STATE_START || gameState === STATE_LANDED || gameState === STATE_CRASHED) {
resetGame(gameState === STATE_CRASHED);
}
});
function resetGame(hardReset = false) {
if (hardReset) {
score = 0;
currentPilot = 0;
} else if (gameState === STATE_LANDED) {
currentPilot = 1 - currentPilot;
}
lander.x = 80 + Math.random() * 440;
lander.y = 80;
lander.vx = (Math.random() - 0.5) * 1.2;
lander.vy = 0.4;
lander.angle = 0;
lander.fuel = lander.maxFuel;
gameState = STATE_PLAYING;
shearerCooldown = 0;
pilotEl.innerText = pilotNames[currentPilot];
triggerShearer("Right then, let's fly over the Toon!");
}
// Draw Newcastle Background Landmarks
function drawBackground() {
ctx.fillStyle = '#ffffff';
ctx.fillRect(80, 60, 2, 2);
ctx.fillRect(200, 120, 2, 2);
ctx.fillRect(350, 50, 3, 3);
ctx.fillRect(510, 100, 2, 2);
ctx.fillRect(550, 40, 2, 2);
// Grey's Monument
ctx.fillStyle = '#334155';
ctx.fillRect(45, 520, 22, 210);
ctx.fillStyle = '#475569';
ctx.fillRect(35, 505, 42, 18);
ctx.fillStyle = '#facc15';
ctx.fillRect(48, 488, 16, 18);
// Tyne Bridge
ctx.strokeStyle = '#1e293b';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(300, 730, 140, Math.PI, 0, false);
ctx.stroke();
ctx.fillStyle = '#1e293b';
ctx.fillRect(190, 665, 220, 9);
ctx.fillRect(210, 674, 14, 60);
ctx.fillRect(376, 674, 14, 60);
// River Tyne
const grad = ctx.createLinearGradient(0, 730, 0, 800);
grad.addColorStop(0, '#0284c7');
grad.addColorStop(1, '#0369a1');
ctx.fillStyle = grad;
ctx.fillRect(0, 730, 600, 70);
// Landing Pads
pads.forEach(pad => {
ctx.fillStyle = '#facc15';
ctx.fillRect(pad.x, pad.y, pad.w, 10);
ctx.fillStyle = '#ef4444';
ctx.fillRect(pad.x - 4, pad.y + 4, 4, 6);
ctx.fillRect(pad.x + pad.w, pad.y + 4, 4, 6);
ctx.fillStyle = '#ffffff';
ctx.font = '11px Courier New';
ctx.textAlign = 'center';
ctx.fillText(`${pad.multiplier}x`, pad.x + pad.w/2, pad.y - 5);
});
}
// Draw Lander Ship
function drawLander() {
ctx.save();
ctx.translate(lander.x, lander.y);
ctx.rotate(lander.angle);
if (keys.thrust && lander.fuel > 0) {
ctx.fillStyle = '#f97316';
ctx.beginPath();
ctx.moveTo(-8, 22);
ctx.lineTo(0, 38 + Math.random() * 10);
ctx.lineTo(8, 22);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#facc15';
ctx.beginPath();
ctx.moveTo(-4, 22);
ctx.lineTo(0, 30 + Math.random() * 6);
ctx.lineTo(4, 22);
ctx.closePath();
ctx.fill();
}
ctx.strokeStyle = '#94a3b8';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(-16, 15);
ctx.lineTo(-24, 25);
ctx.moveTo(16, 15);
ctx.lineTo(24, 25);
ctx.stroke();
ctx.fillStyle = '#e2e8f0';
ctx.fillRect(-16, -14, 32, 30);
ctx.strokeStyle = '#64748b';
ctx.lineWidth = 2;
ctx.strokeRect(-16, -14, 32, 30);
ctx.fillStyle = '#0284c7';
ctx.beginPath();
ctx.arc(0, -10, 11, Math.PI, 0, false);
ctx.fill();
ctx.stroke();
if (currentPilot === 0) {
ctx.fillStyle = '#854d0e';
ctx.fillRect(-6, -18, 12, 6);
ctx.fillStyle = '#fde047';
ctx.fillRect(-8, -10, 16, 4);
} else {
ctx.fillStyle = '#1e293b';
ctx.fillRect(-6, -18, 12, 5);
ctx.fillStyle = '#ec4899';
ctx.fillRect(-8, -10, 16, 4);
}
ctx.fillStyle = '#000000';
ctx.fillRect(-4, 2, 8, 8);
ctx.fillStyle = '#ffffff';
ctx.fillRect(-2, 4, 4, 4);
ctx.restore();
}
// Draw Alan Shearer Speech Bubble (Moved down below HUD at boxY = 115)
function drawShearer() {
if (!shearer.active) return;
if (shearer.x < 15) {
shearer.x += 8;
}
const boxX = shearer.x;
const boxY = 115;
const boxW = 310;
const boxH = 68;
ctx.fillStyle = 'rgba(15, 23, 42, 0.95)';
ctx.strokeStyle = '#facc15';
ctx.lineWidth = 2;
ctx.fillRect(boxX, boxY, boxW, boxH);
ctx.strokeRect(boxX, boxY, boxW, boxH);
const avX = boxX + 8;
const avY = boxY + 8;
ctx.fillStyle = '#000000';
ctx.fillRect(avX, avY + 32, 46, 18);
ctx.fillStyle = '#ffffff';
ctx.fillRect(avX + 9, avY + 32, 9, 18);
ctx.fillRect(avX + 27, avY + 32, 9, 18);
ctx.fillStyle = '#facc15';
ctx.font = 'bold 9px Courier New';
ctx.textAlign = 'center';
ctx.fillText("9", avX + 23, avY + 44);
ctx.fillStyle = '#f5d0b1';
ctx.fillRect(avX + 10, avY + 8, 26, 26);
ctx.fillStyle = '#d4d4d4';
ctx.fillRect(avX + 10, avY + 6, 26, 6);
ctx.fillStyle = '#0f172a';
ctx.fillRect(avX + 15, avY + 16, 4, 3);
ctx.fillRect(avX + 26, avY + 16, 4, 3);
ctx.fillRect(avX + 18, avY + 25, 10, 2);
ctx.textAlign = 'left';
ctx.fillStyle = '#facc15';
ctx.font = 'bold 10px Courier New';
ctx.fillText("ALAN SHEARER (#9)", boxX + 62, boxY + 22);
ctx.fillStyle = '#f3f4f6';
ctx.font = '10px Courier New';
ctx.fillText(shearer.text, boxX + 62, boxY + 44, 235);
shearer.timer--;
if (shearer.timer <= 0) {
shearer.x -= 8;
if (shearer.x <= -320) {
shearer.active = false;
}
}
}
// Update game physics
function update() {
if (gameState !== STATE_PLAYING) return;
if (keys.left) lander.angle -= 0.04;
if (keys.right) lander.angle += 0.04;
if (keys.thrust && lander.fuel > 0) {
const power = 0.12;
lander.vx += Math.sin(lander.angle) * power;
lander.vy -= Math.cos(lander.angle) * power;
lander.fuel -= 1.5;
if (Math.random() < 0.3) playSound('thrust');
}
lander.vy += 0.032;
lander.x += lander.vx;
lander.y += lander.vy;
if (lander.x < 0) lander.x = 600;
if (lander.x > 600) lander.x = 0;
shearerCooldown++;
if (!shearer.active && shearerCooldown > 500) {
shearerCooldown = 0;
triggerShearer();
}
scoreEl.innerText = score;
fuelFillEl.style.width = Math.max(0, (lander.fuel / lander.maxFuel) * 100) + '%';
if (lander.y + 22 >= 730) {
let landedPad = null;
pads.forEach(pad => {
if (lander.x >= pad.x && lander.x <= pad.x + pad.w) {
landedPad = pad;
}
});
const safeSpeed = lander.vy < 2.2 && Math.abs(lander.vx) < 1.2;
const safeAngle = Math.abs(lander.angle % (Math.PI * 2)) < 0.25;
if (landedPad && safeSpeed && safeAngle) {
gameState = STATE_LANDED;
const points = landedPad.multiplier * 150;
score += points;
playSound('success');
triggerShearer(`Howay! Cracking landing on ${landedPad.name}! (+${points} pts)`);
} else {
gameState = STATE_CRASHED;
playSound('crash');
triggerShearer("Divvent do that again! Total write-off!");
}
}
}
// Render screens & game objects
function draw() {
ctx.clearRect(0, 0, 600, 800);
drawBackground();
drawLander();
drawShearer();
if (gameState === STATE_START) {
ctx.fillStyle = 'rgba(2, 6, 23, 0.9)';
ctx.fillRect(0, 0, 600, 800);
ctx.fillStyle = '#38bdf8';
ctx.font = 'bold 30px Courier New';
ctx.textAlign = 'center';
ctx.fillText("GEORDIE LANDER", 300, 220);
ctx.fillStyle = '#facc15';
ctx.font = '14px Courier New';
ctx.fillText("Pilot PJ & Duncan with Toon Fuel", 300, 265);
ctx.fillStyle = '#94a3b8';
ctx.font = '12px Courier New';
ctx.fillText("Use bottom buttons or keyboard to play", 300, 330);
ctx.fillText("Land softly on Quayside, Grey St, or SJP!", 300, 360);
ctx.fillStyle = '#22c55e';
ctx.font = 'bold 15px Courier New';
ctx.fillText(">> TAP OR PRESS ENTER TO START HAWAY MAN! <<", 300, 450);
}
else if (gameState === STATE_LANDED) {
ctx.fillStyle = 'rgba(15, 23, 42, 0.88)';
ctx.fillRect(60, 250, 480, 280);
ctx.strokeStyle = '#22c55e';
ctx.lineWidth = 3;
ctx.strokeRect(60, 250, 480, 280);
ctx.fillStyle = '#22c55e';
ctx.font = 'bold 22px Courier New';
ctx.textAlign = 'center';
ctx.fillText("BELTA! SUCCESSFUL LANDING!", 300, 305);
ctx.fillStyle = '#f3f4f6';
ctx.font = '14px Courier New';
ctx.fillText(`Pilot ${pilotNames[currentPilot]} secured the ship!`, 300, 350);
ctx.fillText("Next pilot stepping into cockpit...", 300, 390);
ctx.fillStyle = '#facc15';
ctx.font = 'bold 14px Courier New';
ctx.fillText("TAP OR PRESS ENTER FOR NEXT LAUNCH", 300, 465);
}
else if (gameState === STATE_CRASHED) {
ctx.fillStyle = 'rgba(15, 23, 42, 0.9)';
ctx.fillRect(60, 250, 480, 280);
ctx.strokeStyle = '#ef4444';
ctx.lineWidth = 3;
ctx.strokeRect(60, 250, 480, 280);
ctx.fillStyle = '#ef4444';
ctx.font = 'bold 24px Courier New';
ctx.textAlign = 'center';
ctx.fillText("HOWAY MAN! YOU CRASHED!", 300, 320);
ctx.fillStyle = '#f3f4f6';
ctx.font = '14px Courier New';
ctx.fillText(`Final Score: ${score}`, 300, 380);
ctx.fillStyle = '#facc15';
ctx.font = 'bold 14px Courier New';
ctx.fillText("TAP OR PRESS ENTER TO TRY AGAIN", 300, 460);
}
}
// Game Loop
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
Game Source: Geordie Lander - Newcastle Arcade
Creator: LunarPanda99
Libraries: none
Complexity: complex (675 lines, 19.7 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: geordie-lander-newcastle-arcade-lunarpanda99" to link back to the original. Then publish at arcadelab.ai/publish.