Files
gamercomp/frontend/public/assets/avatar/contexts/vehicles.js
T
2026-04-30 02:14:25 +00:00

1800 lines
63 KiB
JavaScript

(function() {
'use strict';
// ============================================================
// VEHICLE CONTEXTS - Avatar placement in vehicle scenarios
// ============================================================
const CONTEXTS = [
// ============================================================
// 1. SPACESHIP COCKPIT
// ============================================================
{
id: 'spaceship-cockpit',
name: 'Spaceship Cockpit',
category: 'vehicle',
avatarMode: 'HEAD_ONLY',
animations: ['idle', 'bob', 'celebrate', 'hurt'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['space', 'shooter', 'arcade'],
description: 'Pilot a spacecraft through space',
avatarPosition: { x: 0.5, y: 0.4 },
avatarScale: 1.0,
renderBackground: function(ctx, width, height, time, options) {
// Dark space background
ctx.fillStyle = '#0a0a1a';
ctx.fillRect(0, 0, width, height);
// Starfield
var starCount = 50;
for (var i = 0; i < starCount; i++) {
var seed = i * 7919;
var x = ((seed * 13) % 1000) / 1000 * width;
var baseY = ((seed * 17) % 1000) / 1000 * height;
var speed = 0.5 + ((seed * 23) % 100) / 100;
var y = (baseY + time * speed * 100) % height;
var brightness = 0.3 + ((seed * 31) % 70) / 100;
var size = 1 + ((seed * 37) % 3);
ctx.fillStyle = 'rgba(255, 255, 255, ' + brightness + ')';
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
// Cockpit interior frame
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, width * 0.15, height);
ctx.fillRect(width * 0.85, 0, width * 0.15, height);
ctx.fillRect(0, height * 0.75, width, height * 0.25);
// Interior panel details
ctx.fillStyle = '#2a2a4e';
for (var j = 0; j < 3; j++) {
ctx.fillRect(width * 0.02, height * 0.1 + j * height * 0.2, width * 0.08, height * 0.12);
ctx.fillRect(width * 0.9, height * 0.1 + j * height * 0.2, width * 0.08, height * 0.12);
}
},
renderForeground: function(ctx, width, height, time, options) {
// Cockpit glass with slight blue tint
ctx.strokeStyle = 'rgba(100, 150, 255, 0.3)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.4, width * 0.35, 0, Math.PI * 2);
ctx.stroke();
// HUD overlay elements
ctx.strokeStyle = 'rgba(0, 255, 200, 0.5)';
ctx.lineWidth = 2;
// Targeting reticle
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.35, width * 0.08, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(width * 0.5, height * 0.25);
ctx.lineTo(width * 0.5, height * 0.3);
ctx.moveTo(width * 0.5, height * 0.4);
ctx.lineTo(width * 0.5, height * 0.45);
ctx.moveTo(width * 0.4, height * 0.35);
ctx.lineTo(width * 0.45, height * 0.35);
ctx.moveTo(width * 0.55, height * 0.35);
ctx.lineTo(width * 0.6, height * 0.35);
ctx.stroke();
// Control panel at bottom
var gradient = ctx.createLinearGradient(0, height * 0.8, 0, height);
gradient.addColorStop(0, '#2a2a4e');
gradient.addColorStop(1, '#1a1a2e');
ctx.fillStyle = gradient;
ctx.fillRect(0, height * 0.8, width, height * 0.2);
// Control buttons
var colors = ['#ff3333', '#33ff33', '#3333ff', '#ffff33'];
for (var i = 0; i < 4; i++) {
var blinkPhase = Math.sin(time * 3 + i) * 0.3 + 0.7;
ctx.fillStyle = colors[i];
ctx.globalAlpha = blinkPhase;
ctx.beginPath();
ctx.arc(width * 0.25 + i * width * 0.15, height * 0.9, width * 0.02, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// Speed indicator
ctx.fillStyle = 'rgba(0, 255, 200, 0.8)';
ctx.font = (width * 0.04) + 'px monospace';
ctx.fillText('SPD: ' + Math.floor(500 + Math.sin(time) * 50), width * 0.05, height * 0.88);
},
contextEffects: {
idle: null,
boost: { type: 'particles', direction: 'back' },
damage: { type: 'shake', intensity: 5 }
}
},
// ============================================================
// 2. RACE CAR
// ============================================================
{
id: 'race-car',
name: 'Race Car',
category: 'vehicle',
avatarMode: 'HEAD_AND_SHOULDERS',
animations: ['idle', 'bob', 'celebrate', 'hurt', 'turn-left', 'turn-right'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['racing', 'driving', 'arcade'],
description: 'Race at high speed on the track',
avatarPosition: { x: 0.5, y: 0.35 },
avatarScale: 0.9,
renderBackground: function(ctx, width, height, time, options) {
// Sky gradient
var skyGradient = ctx.createLinearGradient(0, 0, 0, height * 0.4);
skyGradient.addColorStop(0, '#87ceeb');
skyGradient.addColorStop(1, '#e0f0ff');
ctx.fillStyle = skyGradient;
ctx.fillRect(0, 0, width, height * 0.4);
// Road rushing past
var roadGradient = ctx.createLinearGradient(0, height * 0.4, 0, height);
roadGradient.addColorStop(0, '#555555');
roadGradient.addColorStop(1, '#333333');
ctx.fillStyle = roadGradient;
ctx.fillRect(0, height * 0.4, width, height * 0.6);
// Road lines (animated)
ctx.fillStyle = '#ffffff';
var lineOffset = (time * 300) % 80;
for (var i = -1; i < 8; i++) {
var y = height * 0.5 + i * 80 - lineOffset;
var lineWidth = 5 + (y - height * 0.4) * 0.05;
ctx.fillRect(width * 0.48, y, width * 0.04, 40);
}
// Speed lines on sides
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 2;
for (var j = 0; j < 10; j++) {
var lineY = height * 0.5 + (j * 30 + time * 200) % (height * 0.5);
ctx.beginPath();
ctx.moveTo(width * 0.1, lineY);
ctx.lineTo(width * 0.2, lineY + 20);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(width * 0.9, lineY);
ctx.lineTo(width * 0.8, lineY + 20);
ctx.stroke();
}
},
renderForeground: function(ctx, width, height, time, options) {
// Dashboard
var dashGradient = ctx.createLinearGradient(0, height * 0.7, 0, height);
dashGradient.addColorStop(0, '#1a1a1a');
dashGradient.addColorStop(1, '#0a0a0a');
ctx.fillStyle = dashGradient;
ctx.beginPath();
ctx.moveTo(0, height * 0.75);
ctx.quadraticCurveTo(width * 0.5, height * 0.65, width, height * 0.75);
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.closePath();
ctx.fill();
// Steering wheel
ctx.strokeStyle = '#333333';
ctx.lineWidth = width * 0.03;
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.95, width * 0.15, Math.PI * 0.8, Math.PI * 2.2);
ctx.stroke();
ctx.fillStyle = '#222222';
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.95, width * 0.06, 0, Math.PI * 2);
ctx.fill();
// Side mirrors
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, height * 0.3, width * 0.12, height * 0.15);
ctx.fillRect(width * 0.88, height * 0.3, width * 0.12, height * 0.15);
// Mirror reflections (road)
ctx.fillStyle = '#444444';
ctx.fillRect(width * 0.01, height * 0.32, width * 0.1, height * 0.11);
ctx.fillRect(width * 0.89, height * 0.32, width * 0.1, height * 0.11);
// Speedometer
ctx.fillStyle = '#111111';
ctx.beginPath();
ctx.arc(width * 0.25, height * 0.85, width * 0.08, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#ff3300';
ctx.lineWidth = 2;
var speedAngle = Math.PI * 0.75 + (0.5 + Math.sin(time * 0.5) * 0.2) * Math.PI;
ctx.beginPath();
ctx.moveTo(width * 0.25, height * 0.85);
ctx.lineTo(
width * 0.25 + Math.cos(speedAngle) * width * 0.06,
height * 0.85 + Math.sin(speedAngle) * width * 0.06
);
ctx.stroke();
// RPM gauge
ctx.fillStyle = '#111111';
ctx.beginPath();
ctx.arc(width * 0.75, height * 0.85, width * 0.08, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 2;
var rpmAngle = Math.PI * 0.75 + (0.3 + Math.sin(time * 2) * 0.3) * Math.PI;
ctx.beginPath();
ctx.moveTo(width * 0.75, height * 0.85);
ctx.lineTo(
width * 0.75 + Math.cos(rpmAngle) * width * 0.06,
height * 0.85 + Math.sin(rpmAngle) * width * 0.06
);
ctx.stroke();
},
contextEffects: {
idle: null,
accelerate: { type: 'blur', direction: 'vertical' },
drift: { type: 'particles', direction: 'side' },
damage: { type: 'shake', intensity: 8 }
}
},
// ============================================================
// 3. AIRPLANE
// ============================================================
{
id: 'airplane',
name: 'Airplane',
category: 'vehicle',
avatarMode: 'HEAD_AND_SHOULDERS',
animations: ['idle', 'bob', 'celebrate', 'hurt'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['flight', 'simulation', 'arcade'],
description: 'Fly through the clouds as a pilot',
avatarPosition: { x: 0.5, y: 0.4 },
avatarScale: 0.85,
renderBackground: function(ctx, width, height, time, options) {
// Sky gradient
var skyGradient = ctx.createLinearGradient(0, 0, 0, height);
skyGradient.addColorStop(0, '#1e90ff');
skyGradient.addColorStop(0.5, '#87ceeb');
skyGradient.addColorStop(1, '#b0e0e6');
ctx.fillStyle = skyGradient;
ctx.fillRect(0, 0, width, height);
// Clouds streaming past
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
for (var i = 0; i < 8; i++) {
var seed = i * 3571;
var baseX = ((seed * 13) % 1000) / 1000 * width * 1.5;
var x = (baseX - time * 150) % (width * 1.5) - width * 0.25;
var y = ((seed * 17) % 1000) / 1000 * height * 0.6;
var cloudWidth = 60 + ((seed * 23) % 80);
var cloudHeight = 30 + ((seed * 29) % 30);
// Draw cloud puffs
for (var j = 0; j < 4; j++) {
var puffX = x + j * cloudWidth * 0.25;
var puffY = y + Math.sin(j * 1.5) * cloudHeight * 0.3;
var puffR = cloudHeight * 0.5 + ((seed * j) % 10);
ctx.beginPath();
ctx.arc(puffX, puffY, puffR, 0, Math.PI * 2);
ctx.fill();
}
}
// Cockpit interior sides
ctx.fillStyle = '#2a3a4a';
ctx.fillRect(0, 0, width * 0.1, height);
ctx.fillRect(width * 0.9, 0, width * 0.1, height);
},
renderForeground: function(ctx, width, height, time, options) {
// Windshield frame
ctx.strokeStyle = '#4a5a6a';
ctx.lineWidth = width * 0.04;
ctx.beginPath();
ctx.moveTo(width * 0.1, 0);
ctx.lineTo(width * 0.2, height * 0.7);
ctx.lineTo(width * 0.8, height * 0.7);
ctx.lineTo(width * 0.9, 0);
ctx.stroke();
// Center divider
ctx.beginPath();
ctx.moveTo(width * 0.5, 0);
ctx.lineTo(width * 0.5, height * 0.15);
ctx.stroke();
// Instrument panel
var panelGradient = ctx.createLinearGradient(0, height * 0.7, 0, height);
panelGradient.addColorStop(0, '#3a4a5a');
panelGradient.addColorStop(1, '#2a3a4a');
ctx.fillStyle = panelGradient;
ctx.fillRect(0, height * 0.7, width, height * 0.3);
// Instruments
var instrumentPositions = [0.2, 0.35, 0.5, 0.65, 0.8];
for (var i = 0; i < instrumentPositions.length; i++) {
ctx.fillStyle = '#1a2a3a';
ctx.beginPath();
ctx.arc(width * instrumentPositions[i], height * 0.82, width * 0.055, 0, Math.PI * 2);
ctx.fill();
// Instrument needle
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
var angle = Math.PI * (0.75 + Math.sin(time + i) * 0.3);
ctx.beginPath();
ctx.moveTo(width * instrumentPositions[i], height * 0.82);
ctx.lineTo(
width * instrumentPositions[i] + Math.cos(angle) * width * 0.04,
height * 0.82 + Math.sin(angle) * width * 0.04
);
ctx.stroke();
}
// Yoke/control column
ctx.fillStyle = '#2a2a2a';
ctx.fillRect(width * 0.45, height * 0.85, width * 0.1, height * 0.15);
ctx.fillStyle = '#3a3a3a';
ctx.beginPath();
ctx.ellipse(width * 0.5, height * 0.88, width * 0.12, width * 0.04, 0, 0, Math.PI * 2);
ctx.fill();
// Altitude display
ctx.fillStyle = '#00ff00';
ctx.font = (width * 0.03) + 'px monospace';
ctx.fillText('ALT: ' + Math.floor(35000 + Math.sin(time * 0.5) * 500), width * 0.05, height * 0.95);
ctx.fillText('HDG: ' + Math.floor(270 + Math.sin(time * 0.3) * 10), width * 0.75, height * 0.95);
},
contextEffects: {
idle: null,
turbulence: { type: 'shake', intensity: 3 },
dive: { type: 'blur', direction: 'vertical' },
damage: { type: 'shake', intensity: 10 }
}
},
// ============================================================
// 4. FLAPPY STYLE
// ============================================================
{
id: 'flappy-style',
name: 'Flappy Bird Style',
category: 'vehicle',
avatarMode: 'HEAD_ONLY',
animations: ['idle', 'flap', 'fall', 'hurt'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: false, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['flappy', 'casual', 'arcade', 'mobile'],
description: 'Flap through obstacles as a flying character',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.2,
renderBackground: function(ctx, width, height, time, options) {
// Transparent background - game provides its own
ctx.clearRect(0, 0, width, height);
},
renderForeground: function(ctx, width, height, time, options) {
// Optional wing sprites
var flapOffset = Math.sin(time * 15) * 0.15;
// Left wing
ctx.fillStyle = 'rgba(255, 200, 100, 0.9)';
ctx.save();
ctx.translate(width * 0.25, height * 0.5);
ctx.rotate(-0.3 + flapOffset);
ctx.beginPath();
ctx.ellipse(0, 0, width * 0.15, width * 0.06, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Right wing
ctx.save();
ctx.translate(width * 0.75, height * 0.5);
ctx.rotate(0.3 - flapOffset);
ctx.beginPath();
ctx.ellipse(0, 0, width * 0.15, width * 0.06, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Wing details
ctx.strokeStyle = 'rgba(200, 150, 50, 0.7)';
ctx.lineWidth = 2;
ctx.save();
ctx.translate(width * 0.25, height * 0.5);
ctx.rotate(-0.3 + flapOffset);
ctx.beginPath();
ctx.moveTo(-width * 0.1, 0);
ctx.lineTo(width * 0.1, 0);
ctx.stroke();
ctx.restore();
ctx.save();
ctx.translate(width * 0.75, height * 0.5);
ctx.rotate(0.3 - flapOffset);
ctx.beginPath();
ctx.moveTo(-width * 0.1, 0);
ctx.lineTo(width * 0.1, 0);
ctx.stroke();
ctx.restore();
},
contextEffects: {
idle: null,
flap: { type: 'blur', direction: 'vertical', intensity: 2 },
fall: { type: 'stretch', direction: 'down' },
damage: { type: 'flash', color: '#ff0000' }
}
},
// ============================================================
// 5. TANK
// ============================================================
{
id: 'tank',
name: 'Tank',
category: 'vehicle',
avatarMode: 'HEAD_ONLY',
animations: ['idle', 'bob', 'celebrate', 'hurt', 'aim'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['military', 'shooter', 'strategy', 'arcade'],
description: 'Command a battle tank',
avatarPosition: { x: 0.5, y: 0.35 },
avatarScale: 0.8,
renderBackground: function(ctx, width, height, time, options) {
// Military green interior
var interiorGradient = ctx.createRadialGradient(
width * 0.5, height * 0.5, 0,
width * 0.5, height * 0.5, width * 0.6
);
interiorGradient.addColorStop(0, '#3a4a2a');
interiorGradient.addColorStop(1, '#2a3a1a');
ctx.fillStyle = interiorGradient;
ctx.fillRect(0, 0, width, height);
// Rivets and panels
ctx.fillStyle = '#4a5a3a';
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
ctx.beginPath();
ctx.arc(
width * 0.1 + i * width * 0.05,
height * 0.1 + j * height * 0.25,
4, 0, Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
width * 0.9 - i * width * 0.05,
height * 0.1 + j * height * 0.25,
4, 0, Math.PI * 2
);
ctx.fill();
}
}
// Interior screens
ctx.fillStyle = '#1a2a1a';
ctx.fillRect(width * 0.05, height * 0.6, width * 0.2, height * 0.15);
ctx.fillRect(width * 0.75, height * 0.6, width * 0.2, height * 0.15);
// Screen static
ctx.fillStyle = '#2a4a2a';
for (var k = 0; k < 20; k++) {
var sx = width * 0.07 + Math.random() * width * 0.16;
var sy = height * 0.62 + Math.random() * height * 0.11;
ctx.fillRect(sx, sy, 2, 2);
}
},
renderForeground: function(ctx, width, height, time, options) {
// Tank hatch frame (circular opening)
ctx.strokeStyle = '#5a6a4a';
ctx.lineWidth = width * 0.08;
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.35, width * 0.28, 0, Math.PI * 2);
ctx.stroke();
// Hatch lip detail
ctx.strokeStyle = '#4a5a3a';
ctx.lineWidth = width * 0.02;
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.35, width * 0.32, 0, Math.PI * 2);
ctx.stroke();
// Hatch bolts
ctx.fillStyle = '#3a4a2a';
for (var i = 0; i < 8; i++) {
var angle = (i / 8) * Math.PI * 2;
var bx = width * 0.5 + Math.cos(angle) * width * 0.3;
var by = height * 0.35 + Math.sin(angle) * width * 0.3;
ctx.beginPath();
ctx.arc(bx, by, 6, 0, Math.PI * 2);
ctx.fill();
}
// Periscope overlay (top)
ctx.fillStyle = '#2a3a1a';
ctx.fillRect(width * 0.35, 0, width * 0.3, height * 0.1);
ctx.fillStyle = '#1a2a0a';
ctx.fillRect(width * 0.38, height * 0.02, width * 0.24, height * 0.06);
// Periscope view
ctx.fillStyle = '#87ceeb';
ctx.fillRect(width * 0.4, height * 0.03, width * 0.2, height * 0.04);
// Crosshair in periscope
ctx.strokeStyle = '#ff0000';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(width * 0.5, height * 0.03);
ctx.lineTo(width * 0.5, height * 0.07);
ctx.moveTo(width * 0.4, height * 0.05);
ctx.lineTo(width * 0.6, height * 0.05);
ctx.stroke();
// Control panel at bottom
ctx.fillStyle = '#3a4a2a';
ctx.fillRect(0, height * 0.8, width, height * 0.2);
// Warning lights
var warningOn = Math.sin(time * 5) > 0;
ctx.fillStyle = warningOn ? '#ff3333' : '#331111';
ctx.beginPath();
ctx.arc(width * 0.2, height * 0.9, width * 0.025, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#33ff33';
ctx.beginPath();
ctx.arc(width * 0.35, height * 0.9, width * 0.025, 0, Math.PI * 2);
ctx.fill();
// Ammo counter
ctx.fillStyle = '#ffcc00';
ctx.font = (width * 0.05) + 'px monospace';
ctx.fillText('AMMO: 24', width * 0.6, height * 0.92);
},
contextEffects: {
idle: null,
fire: { type: 'flash', color: '#ffff00', intensity: 10 },
rumble: { type: 'shake', intensity: 4 },
damage: { type: 'shake', intensity: 12 }
}
},
// ============================================================
// 6. PACMAN STYLE
// ============================================================
{
id: 'pacman-style',
name: 'Pac-Man Style',
category: 'vehicle',
avatarMode: 'HEAD_ONLY',
animations: ['idle', 'chomp', 'hurt', 'power'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: false, headwear: false, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['maze', 'arcade', 'retro', 'casual'],
description: 'Navigate mazes and eat pellets',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
renderBackground: function(ctx, width, height, time, options) {
// Fully transparent - game provides maze
ctx.clearRect(0, 0, width, height);
},
renderForeground: function(ctx, width, height, time, options) {
// No foreground elements - avatar IS the character
// The chomp animation is handled by avatar animations
// Optional: subtle mouth effect overlay during chomp
var chompPhase = Math.abs(Math.sin(time * 12));
if (chompPhase < 0.3) {
// Mouth "bite" effect - darkens bottom portion slightly
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.beginPath();
ctx.moveTo(width * 0.5, height * 0.5);
ctx.lineTo(width * 0.8, height * 0.6);
ctx.lineTo(width * 0.8, height * 0.7);
ctx.lineTo(width * 0.5, height * 0.6);
ctx.closePath();
ctx.fill();
}
},
contextEffects: {
idle: null,
chomp: { type: 'scale', pulseRate: 12 },
power: { type: 'glow', color: '#00ffff' },
damage: { type: 'flash', color: '#ff0000' }
}
},
// ============================================================
// 7. HOVERBOARD
// ============================================================
{
id: 'hoverboard',
name: 'Hoverboard',
category: 'vehicle',
avatarMode: 'UPPER_BODY',
animations: ['idle', 'bob', 'celebrate', 'hurt', 'lean-left', 'lean-right', 'jump'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'foreground', 'avatar'],
recommendedFor: ['runner', 'racing', 'arcade', 'endless'],
description: 'Ride a futuristic hoverboard',
avatarPosition: { x: 0.5, y: 0.35 },
avatarScale: 0.75,
renderBackground: function(ctx, width, height, time, options) {
// Ground/environment rushing beneath
var groundGradient = ctx.createLinearGradient(0, height * 0.6, 0, height);
groundGradient.addColorStop(0, '#4a4a5a');
groundGradient.addColorStop(1, '#2a2a3a');
ctx.fillStyle = groundGradient;
ctx.fillRect(0, height * 0.6, width, height * 0.4);
// Speed lines on ground
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.lineWidth = 3;
for (var i = 0; i < 12; i++) {
var seed = i * 1847;
var lx = ((seed * 13) % 1000) / 1000 * width;
var ly = height * 0.7 + ((seed * 17) % 1000) / 1000 * height * 0.25;
var lineLen = 30 + ((seed * 23) % 50);
var offset = (time * 400 + seed) % (width * 1.5);
var drawX = lx - offset;
if (drawX < -lineLen) drawX += width * 1.5;
ctx.beginPath();
ctx.moveTo(drawX, ly);
ctx.lineTo(drawX + lineLen, ly);
ctx.stroke();
}
// Shadow beneath board
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.beginPath();
ctx.ellipse(width * 0.5, height * 0.85, width * 0.25, height * 0.03, 0, 0, Math.PI * 2);
ctx.fill();
},
renderForeground: function(ctx, width, height, time, options) {
// Hoverboard
var hoverOffset = Math.sin(time * 4) * 3;
var boardY = height * 0.72 + hoverOffset;
// Board shadow/glow
var glowGradient = ctx.createRadialGradient(
width * 0.5, boardY + 15, 0,
width * 0.5, boardY + 15, width * 0.3
);
glowGradient.addColorStop(0, 'rgba(0, 200, 255, 0.6)');
glowGradient.addColorStop(0.5, 'rgba(0, 200, 255, 0.2)');
glowGradient.addColorStop(1, 'rgba(0, 200, 255, 0)');
ctx.fillStyle = glowGradient;
ctx.beginPath();
ctx.ellipse(width * 0.5, boardY + 15, width * 0.3, height * 0.06, 0, 0, Math.PI * 2);
ctx.fill();
// Board body
var boardGradient = ctx.createLinearGradient(0, boardY - 10, 0, boardY + 10);
boardGradient.addColorStop(0, '#3a3a4a');
boardGradient.addColorStop(0.5, '#5a5a6a');
boardGradient.addColorStop(1, '#2a2a3a');
ctx.fillStyle = boardGradient;
ctx.beginPath();
ctx.ellipse(width * 0.5, boardY, width * 0.28, height * 0.04, 0, 0, Math.PI * 2);
ctx.fill();
// Board top surface
ctx.fillStyle = '#6a6a7a';
ctx.beginPath();
ctx.ellipse(width * 0.5, boardY - 3, width * 0.25, height * 0.025, 0, 0, Math.PI * 2);
ctx.fill();
// Board lights
var lightPhase = (time * 3) % 1;
ctx.fillStyle = 'rgba(0, 255, 255, ' + (0.5 + lightPhase * 0.5) + ')';
ctx.beginPath();
ctx.arc(width * 0.3, boardY, 4, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(width * 0.7, boardY, 4, 0, Math.PI * 2);
ctx.fill();
// Hover jets
ctx.fillStyle = 'rgba(0, 200, 255, 0.8)';
var jetFlicker = 0.7 + Math.random() * 0.3;
ctx.globalAlpha = jetFlicker;
ctx.beginPath();
ctx.moveTo(width * 0.35, boardY + 8);
ctx.lineTo(width * 0.32, boardY + 20 + hoverOffset);
ctx.lineTo(width * 0.38, boardY + 20 + hoverOffset);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(width * 0.65, boardY + 8);
ctx.lineTo(width * 0.62, boardY + 20 + hoverOffset);
ctx.lineTo(width * 0.68, boardY + 20 + hoverOffset);
ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1;
},
contextEffects: {
idle: { type: 'bob', amplitude: 3, rate: 4 },
boost: { type: 'particles', direction: 'back', color: '#00ffff' },
jump: { type: 'stretch', direction: 'up' },
damage: { type: 'shake', intensity: 6 }
}
},
// ============================================================
// 8. JETPACK
// ============================================================
{
id: 'jetpack',
name: 'Jetpack',
category: 'vehicle',
avatarMode: 'UPPER_BODY',
animations: ['idle', 'bob', 'celebrate', 'hurt', 'boost'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'jetpack', 'avatar', 'foreground'],
recommendedFor: ['flying', 'endless', 'arcade', 'shooter'],
description: 'Fly with a jetpack strapped to your back',
avatarPosition: { x: 0.5, y: 0.4 },
avatarScale: 0.7,
renderBackground: function(ctx, width, height, time, options) {
// Sky gradient
var skyGradient = ctx.createLinearGradient(0, 0, 0, height);
skyGradient.addColorStop(0, '#1e90ff');
skyGradient.addColorStop(1, '#87ceeb');
ctx.fillStyle = skyGradient;
ctx.fillRect(0, 0, width, height);
// Clouds below
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
for (var i = 0; i < 5; i++) {
var seed = i * 2347;
var cx = ((seed * 13) % 1000) / 1000 * width * 1.5 - width * 0.25;
var cy = height * 0.7 + ((seed * 17) % 1000) / 1000 * height * 0.2;
var cw = 80 + ((seed * 23) % 60);
// Animate clouds moving down
cy = (cy + time * 30) % (height * 0.5) + height * 0.6;
for (var j = 0; j < 4; j++) {
ctx.beginPath();
ctx.arc(cx + j * cw * 0.3, cy + Math.sin(j) * 10, cw * 0.3, 0, Math.PI * 2);
ctx.fill();
}
}
// JETPACK (rendered behind avatar)
var packX = width * 0.5;
var packY = height * 0.45;
// Main pack body
var packGradient = ctx.createLinearGradient(packX - 30, 0, packX + 30, 0);
packGradient.addColorStop(0, '#4a4a5a');
packGradient.addColorStop(0.5, '#6a6a7a');
packGradient.addColorStop(1, '#4a4a5a');
ctx.fillStyle = packGradient;
// Pack shape
ctx.beginPath();
ctx.roundRect(packX - width * 0.12, packY - height * 0.05, width * 0.24, height * 0.25, 8);
ctx.fill();
// Pack details
ctx.fillStyle = '#3a3a4a';
ctx.fillRect(packX - width * 0.08, packY, width * 0.16, height * 0.02);
ctx.fillRect(packX - width * 0.08, packY + height * 0.08, width * 0.16, height * 0.02);
// Fuel gauge
ctx.fillStyle = '#2a2a3a';
ctx.fillRect(packX - width * 0.06, packY + height * 0.12, width * 0.12, height * 0.04);
ctx.fillStyle = '#00ff00';
var fuelLevel = 0.7 + Math.sin(time * 0.5) * 0.1;
ctx.fillRect(packX - width * 0.055, packY + height * 0.125, width * 0.11 * fuelLevel, height * 0.03);
// Exhaust nozzles
ctx.fillStyle = '#5a5a6a';
ctx.beginPath();
ctx.moveTo(packX - width * 0.08, packY + height * 0.2);
ctx.lineTo(packX - width * 0.1, packY + height * 0.28);
ctx.lineTo(packX - width * 0.04, packY + height * 0.28);
ctx.lineTo(packX - width * 0.04, packY + height * 0.2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(packX + width * 0.04, packY + height * 0.2);
ctx.lineTo(packX + width * 0.04, packY + height * 0.28);
ctx.lineTo(packX + width * 0.1, packY + height * 0.28);
ctx.lineTo(packX + width * 0.08, packY + height * 0.2);
ctx.closePath();
ctx.fill();
},
renderForeground: function(ctx, width, height, time, options) {
// Jet flames (rendered in front layer for visibility)
var packX = width * 0.5;
var packY = height * 0.45;
var flameHeight = height * 0.15 + Math.random() * height * 0.05;
// Left flame
var flameGradient1 = ctx.createLinearGradient(0, packY + height * 0.28, 0, packY + height * 0.28 + flameHeight);
flameGradient1.addColorStop(0, 'rgba(255, 200, 50, 0.9)');
flameGradient1.addColorStop(0.3, 'rgba(255, 100, 0, 0.8)');
flameGradient1.addColorStop(1, 'rgba(255, 50, 0, 0)');
ctx.fillStyle = flameGradient1;
ctx.beginPath();
ctx.moveTo(packX - width * 0.09, packY + height * 0.28);
ctx.quadraticCurveTo(
packX - width * 0.07 + Math.sin(time * 20) * 5,
packY + height * 0.35,
packX - width * 0.07,
packY + height * 0.28 + flameHeight
);
ctx.quadraticCurveTo(
packX - width * 0.07 + Math.sin(time * 25) * 5,
packY + height * 0.35,
packX - width * 0.05,
packY + height * 0.28
);
ctx.closePath();
ctx.fill();
// Right flame
ctx.beginPath();
ctx.moveTo(packX + width * 0.05, packY + height * 0.28);
ctx.quadraticCurveTo(
packX + width * 0.07 + Math.sin(time * 22) * 5,
packY + height * 0.35,
packX + width * 0.07,
packY + height * 0.28 + flameHeight
);
ctx.quadraticCurveTo(
packX + width * 0.07 + Math.sin(time * 27) * 5,
packY + height * 0.35,
packX + width * 0.09,
packY + height * 0.28
);
ctx.closePath();
ctx.fill();
// Smoke trail
ctx.fillStyle = 'rgba(100, 100, 100, 0.3)';
for (var i = 0; i < 6; i++) {
var smokeY = packY + height * 0.35 + i * 15 + (time * 50) % 20;
var smokeX = packX + Math.sin(time * 3 + i) * 10;
var smokeSize = 8 + i * 3;
ctx.globalAlpha = 0.3 - i * 0.04;
ctx.beginPath();
ctx.arc(smokeX, smokeY, smokeSize, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
},
contextEffects: {
idle: { type: 'bob', amplitude: 2, rate: 2 },
boost: { type: 'particles', direction: 'down', color: '#ff6600' },
damage: { type: 'shake', intensity: 8 }
}
},
// ============================================================
// 9. MECH SUIT
// ============================================================
{
id: 'mech-suit',
name: 'Mech Suit',
category: 'vehicle',
avatarMode: 'HEAD_ONLY',
animations: ['idle', 'bob', 'celebrate', 'hurt'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: false, headwear: false, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['mech', 'shooter', 'action', 'combat'],
description: 'Pilot a powerful mech suit',
avatarPosition: { x: 0.5, y: 0.35 },
avatarScale: 0.7,
renderBackground: function(ctx, width, height, time, options) {
// Mech interior with screens
var interiorGradient = ctx.createRadialGradient(
width * 0.5, height * 0.4, 0,
width * 0.5, height * 0.4, width * 0.6
);
interiorGradient.addColorStop(0, '#2a2a3a');
interiorGradient.addColorStop(1, '#1a1a2a');
ctx.fillStyle = interiorGradient;
ctx.fillRect(0, 0, width, height);
// Side screens
ctx.fillStyle = '#0a1a2a';
ctx.fillRect(width * 0.02, height * 0.15, width * 0.15, height * 0.25);
ctx.fillRect(width * 0.83, height * 0.15, width * 0.15, height * 0.25);
// Screen content - radar
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(width * 0.095, height * 0.275, width * 0.05, 0, Math.PI * 2);
ctx.stroke();
// Radar sweep
var sweepAngle = (time * 2) % (Math.PI * 2);
ctx.beginPath();
ctx.moveTo(width * 0.095, height * 0.275);
ctx.lineTo(
width * 0.095 + Math.cos(sweepAngle) * width * 0.05,
height * 0.275 + Math.sin(sweepAngle) * width * 0.05
);
ctx.stroke();
// Right screen - systems
ctx.fillStyle = '#00ff00';
ctx.font = (width * 0.02) + 'px monospace';
ctx.fillText('PWR: 87%', width * 0.84, height * 0.2);
ctx.fillText('ARM: OK', width * 0.84, height * 0.25);
ctx.fillText('SHD: 65%', width * 0.84, height * 0.3);
ctx.fillText('FUEL: 72%', width * 0.84, height * 0.35);
// Interior frame elements
ctx.fillStyle = '#3a3a4a';
ctx.fillRect(0, 0, width * 0.08, height);
ctx.fillRect(width * 0.92, 0, width * 0.08, height);
},
renderForeground: function(ctx, width, height, time, options) {
// Mech chest window frame (hexagonal)
ctx.strokeStyle = '#5a5a6a';
ctx.lineWidth = width * 0.06;
ctx.beginPath();
var centerX = width * 0.5;
var centerY = height * 0.35;
var radius = width * 0.28;
for (var i = 0; i < 6; i++) {
var angle = (i / 6) * Math.PI * 2 - Math.PI / 2;
var x = centerX + Math.cos(angle) * radius;
var y = centerY + Math.sin(angle) * radius * 0.8;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.stroke();
// Inner frame
ctx.strokeStyle = '#4a4a5a';
ctx.lineWidth = width * 0.02;
ctx.beginPath();
for (var j = 0; j < 6; j++) {
var angle2 = (j / 6) * Math.PI * 2 - Math.PI / 2;
var x2 = centerX + Math.cos(angle2) * radius * 1.1;
var y2 = centerY + Math.sin(angle2) * radius * 0.88;
if (j === 0) {
ctx.moveTo(x2, y2);
} else {
ctx.lineTo(x2, y2);
}
}
ctx.closePath();
ctx.stroke();
// HUD elements
ctx.strokeStyle = 'rgba(0, 200, 255, 0.6)';
ctx.lineWidth = 2;
// Targeting brackets
ctx.beginPath();
ctx.moveTo(width * 0.35, height * 0.2);
ctx.lineTo(width * 0.35, height * 0.15);
ctx.lineTo(width * 0.4, height * 0.15);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(width * 0.65, height * 0.2);
ctx.lineTo(width * 0.65, height * 0.15);
ctx.lineTo(width * 0.6, height * 0.15);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(width * 0.35, height * 0.5);
ctx.lineTo(width * 0.35, height * 0.55);
ctx.lineTo(width * 0.4, height * 0.55);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(width * 0.65, height * 0.5);
ctx.lineTo(width * 0.65, height * 0.55);
ctx.lineTo(width * 0.6, height * 0.55);
ctx.stroke();
// Control panel
ctx.fillStyle = '#3a3a4a';
ctx.fillRect(0, height * 0.7, width, height * 0.3);
// Control sticks
ctx.fillStyle = '#2a2a3a';
ctx.beginPath();
ctx.arc(width * 0.25, height * 0.85, width * 0.06, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(width * 0.75, height * 0.85, width * 0.06, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#5a5a6a';
ctx.beginPath();
ctx.arc(width * 0.25, height * 0.85, width * 0.025, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(width * 0.75, height * 0.85, width * 0.025, 0, Math.PI * 2);
ctx.fill();
// Warning lights (blinking)
var warningPhase = Math.sin(time * 4);
if (warningPhase > 0.5) {
ctx.fillStyle = '#ff3333';
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.75, width * 0.015, 0, Math.PI * 2);
ctx.fill();
}
// Status text
ctx.fillStyle = '#00ffff';
ctx.font = (width * 0.035) + 'px monospace';
ctx.fillText('MECH SYSTEMS ONLINE', width * 0.32, height * 0.78);
},
contextEffects: {
idle: null,
walk: { type: 'shake', intensity: 2 },
fire: { type: 'flash', color: '#ffff00' },
damage: { type: 'shake', intensity: 10 }
}
},
// ============================================================
// 10. SUBMARINE
// ============================================================
{
id: 'submarine',
name: 'Submarine',
category: 'vehicle',
avatarMode: 'HEAD_AND_SHOULDERS',
animations: ['idle', 'bob', 'celebrate', 'hurt'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['underwater', 'exploration', 'arcade'],
description: 'Explore the depths in a submarine',
avatarPosition: { x: 0.5, y: 0.4 },
avatarScale: 0.8,
renderBackground: function(ctx, width, height, time, options) {
// Underwater gradient
var waterGradient = ctx.createLinearGradient(0, 0, 0, height);
waterGradient.addColorStop(0, '#0a3a5a');
waterGradient.addColorStop(0.5, '#0a2a4a');
waterGradient.addColorStop(1, '#0a1a3a');
ctx.fillStyle = waterGradient;
ctx.fillRect(0, 0, width, height);
// Bubbles rising
ctx.fillStyle = 'rgba(150, 200, 255, 0.4)';
for (var i = 0; i < 15; i++) {
var seed = i * 1373;
var bx = ((seed * 13) % 1000) / 1000 * width;
var by = height - ((time * 40 + seed) % height);
var bSize = 3 + ((seed * 17) % 8);
var wobble = Math.sin(time * 2 + i) * 5;
ctx.beginPath();
ctx.arc(bx + wobble, by, bSize, 0, Math.PI * 2);
ctx.fill();
}
// Submarine interior
ctx.fillStyle = '#3a4a5a';
ctx.fillRect(0, 0, width * 0.12, height);
ctx.fillRect(width * 0.88, 0, width * 0.12, height);
// Interior pipes
ctx.strokeStyle = '#5a6a7a';
ctx.lineWidth = 8;
ctx.beginPath();
ctx.moveTo(width * 0.06, 0);
ctx.lineTo(width * 0.06, height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(width * 0.94, 0);
ctx.lineTo(width * 0.94, height);
ctx.stroke();
// Pipe joints
ctx.fillStyle = '#4a5a6a';
for (var j = 0; j < 4; j++) {
ctx.beginPath();
ctx.arc(width * 0.06, height * 0.2 + j * height * 0.25, 12, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(width * 0.94, height * 0.2 + j * height * 0.25, 12, 0, Math.PI * 2);
ctx.fill();
}
},
renderForeground: function(ctx, width, height, time, options) {
// Porthole frame (circular submarine window)
ctx.strokeStyle = '#6a7a8a';
ctx.lineWidth = width * 0.08;
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.4, width * 0.3, 0, Math.PI * 2);
ctx.stroke();
// Porthole rivets
ctx.fillStyle = '#5a6a7a';
for (var i = 0; i < 12; i++) {
var angle = (i / 12) * Math.PI * 2;
var rx = width * 0.5 + Math.cos(angle) * width * 0.34;
var ry = height * 0.4 + Math.sin(angle) * width * 0.34;
ctx.beginPath();
ctx.arc(rx, ry, 5, 0, Math.PI * 2);
ctx.fill();
}
// Glass reflection
ctx.strokeStyle = 'rgba(150, 200, 255, 0.3)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(width * 0.4, height * 0.3, width * 0.15, Math.PI * 1.2, Math.PI * 1.7);
ctx.stroke();
// Water distortion overlay
var distortionAlpha = 0.05 + Math.sin(time) * 0.02;
ctx.fillStyle = 'rgba(100, 150, 200, ' + distortionAlpha + ')';
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.4, width * 0.26, 0, Math.PI * 2);
ctx.fill();
// Control panel
ctx.fillStyle = '#4a5a6a';
ctx.fillRect(0, height * 0.75, width, height * 0.25);
// Depth gauge
ctx.fillStyle = '#2a3a4a';
ctx.beginPath();
ctx.arc(width * 0.2, height * 0.87, width * 0.07, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(width * 0.2, height * 0.87, width * 0.05, 0, Math.PI * 2);
ctx.stroke();
// Depth needle
var depthAngle = Math.PI * 0.75 + (0.4 + Math.sin(time * 0.3) * 0.2) * Math.PI;
ctx.beginPath();
ctx.moveTo(width * 0.2, height * 0.87);
ctx.lineTo(
width * 0.2 + Math.cos(depthAngle) * width * 0.045,
height * 0.87 + Math.sin(depthAngle) * width * 0.045
);
ctx.stroke();
// Depth reading
ctx.fillStyle = '#00ff00';
ctx.font = (width * 0.03) + 'px monospace';
ctx.fillText('DEPTH', width * 0.155, height * 0.95);
ctx.fillText(Math.floor(500 + Math.sin(time * 0.3) * 50) + 'm', width * 0.16, height * 0.98);
// Pressure gauge
ctx.fillStyle = '#2a3a4a';
ctx.beginPath();
ctx.arc(width * 0.8, height * 0.87, width * 0.07, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#ffaa00';
ctx.beginPath();
ctx.arc(width * 0.8, height * 0.87, width * 0.05, 0, Math.PI * 2);
ctx.stroke();
// Sonar ping effect
var pingPhase = (time * 0.5) % 1;
ctx.strokeStyle = 'rgba(0, 255, 100, ' + (1 - pingPhase) * 0.5 + ')';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.87, pingPhase * width * 0.15, 0, Math.PI * 2);
ctx.stroke();
// Periscope controls
ctx.fillStyle = '#5a6a7a';
ctx.fillRect(width * 0.45, height * 0.78, width * 0.1, height * 0.08);
ctx.fillStyle = '#3a4a5a';
ctx.beginPath();
ctx.arc(width * 0.5, height * 0.82, width * 0.03, 0, Math.PI * 2);
ctx.fill();
},
contextEffects: {
idle: { type: 'bob', amplitude: 2, rate: 1 },
pressure: { type: 'distort', intensity: 3 },
damage: { type: 'shake', intensity: 6 }
}
},
// ============================================================
// 11. DRAGON RIDER
// ============================================================
{
id: 'dragon-rider',
name: 'Dragon Rider',
category: 'vehicle',
avatarMode: 'FULL_BODY',
animations: ['idle', 'bob', 'celebrate', 'hurt', 'lean'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['fantasy', 'adventure', 'flying', 'rpg'],
description: 'Soar through the skies on a dragon',
avatarPosition: { x: 0.5, y: 0.3 },
avatarScale: 0.5,
renderBackground: function(ctx, width, height, time, options) {
// Sky gradient
var skyGradient = ctx.createLinearGradient(0, 0, 0, height);
skyGradient.addColorStop(0, '#ff7700');
skyGradient.addColorStop(0.3, '#ff9944');
skyGradient.addColorStop(0.6, '#87ceeb');
skyGradient.addColorStop(1, '#5588bb');
ctx.fillStyle = skyGradient;
ctx.fillRect(0, 0, width, height);
// Clouds
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
for (var i = 0; i < 6; i++) {
var seed = i * 2741;
var cx = (((seed * 13) % 1000) / 1000 * width * 2 - time * 30) % (width * 1.5) - width * 0.25;
var cy = ((seed * 17) % 1000) / 1000 * height * 0.4;
var cw = 60 + ((seed * 23) % 40);
for (var j = 0; j < 3; j++) {
ctx.beginPath();
ctx.arc(cx + j * cw * 0.4, cy, cw * 0.35, 0, Math.PI * 2);
ctx.fill();
}
}
// Dragon body/neck beneath rider
var wingFlap = Math.sin(time * 3) * 0.15;
// Dragon neck
var neckGradient = ctx.createLinearGradient(width * 0.3, height * 0.4, width * 0.7, height * 0.6);
neckGradient.addColorStop(0, '#2a5a3a');
neckGradient.addColorStop(0.5, '#3a7a4a');
neckGradient.addColorStop(1, '#2a5a3a');
ctx.fillStyle = neckGradient;
ctx.beginPath();
ctx.moveTo(width * 0.35, height * 0.5);
ctx.quadraticCurveTo(width * 0.5, height * 0.45, width * 0.65, height * 0.5);
ctx.lineTo(width * 0.6, height * 0.65);
ctx.quadraticCurveTo(width * 0.5, height * 0.6, width * 0.4, height * 0.65);
ctx.closePath();
ctx.fill();
// Dragon scales on neck
ctx.fillStyle = '#1a4a2a';
for (var k = 0; k < 5; k++) {
var scaleX = width * 0.42 + k * width * 0.04;
var scaleY = height * 0.52 + Math.sin(k * 0.5) * 5;
ctx.beginPath();
ctx.arc(scaleX, scaleY, 6, 0, Math.PI, true);
ctx.fill();
}
// Dragon wings (behind rider)
ctx.fillStyle = '#2a6a3a';
// Left wing
ctx.save();
ctx.translate(width * 0.3, height * 0.5);
ctx.rotate(-0.5 + wingFlap);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.quadraticCurveTo(-width * 0.3, -height * 0.1, -width * 0.4, height * 0.1);
ctx.quadraticCurveTo(-width * 0.2, height * 0.05, 0, height * 0.1);
ctx.closePath();
ctx.fill();
// Wing membrane lines
ctx.strokeStyle = '#1a5a2a';
ctx.lineWidth = 2;
for (var l = 0; l < 4; l++) {
ctx.beginPath();
ctx.moveTo(0, height * 0.02 * l);
ctx.quadraticCurveTo(-width * 0.2, 0, -width * 0.3 + l * 20, height * 0.08);
ctx.stroke();
}
ctx.restore();
// Right wing
ctx.save();
ctx.translate(width * 0.7, height * 0.5);
ctx.rotate(0.5 - wingFlap);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.quadraticCurveTo(width * 0.3, -height * 0.1, width * 0.4, height * 0.1);
ctx.quadraticCurveTo(width * 0.2, height * 0.05, 0, height * 0.1);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#1a5a2a';
for (var m = 0; m < 4; m++) {
ctx.beginPath();
ctx.moveTo(0, height * 0.02 * m);
ctx.quadraticCurveTo(width * 0.2, 0, width * 0.3 - m * 20, height * 0.08);
ctx.stroke();
}
ctx.restore();
// Saddle
ctx.fillStyle = '#5a3a2a';
ctx.beginPath();
ctx.ellipse(width * 0.5, height * 0.48, width * 0.1, height * 0.04, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#7a4a3a';
ctx.beginPath();
ctx.ellipse(width * 0.5, height * 0.46, width * 0.08, height * 0.025, 0, 0, Math.PI * 2);
ctx.fill();
},
renderForeground: function(ctx, width, height, time, options) {
// Dragon head/horns framing view (top)
var headBob = Math.sin(time * 2) * 3;
// Dragon horns
ctx.fillStyle = '#4a3a2a';
// Left horn
ctx.beginPath();
ctx.moveTo(width * 0.15, height * 0.15 + headBob);
ctx.quadraticCurveTo(width * 0.1, 0, width * 0.2, -height * 0.05);
ctx.quadraticCurveTo(width * 0.18, height * 0.05, width * 0.22, height * 0.12 + headBob);
ctx.closePath();
ctx.fill();
// Right horn
ctx.beginPath();
ctx.moveTo(width * 0.85, height * 0.15 + headBob);
ctx.quadraticCurveTo(width * 0.9, 0, width * 0.8, -height * 0.05);
ctx.quadraticCurveTo(width * 0.82, height * 0.05, width * 0.78, height * 0.12 + headBob);
ctx.closePath();
ctx.fill();
// Dragon ears/spines
ctx.fillStyle = '#3a6a4a';
ctx.beginPath();
ctx.moveTo(width * 0.05, height * 0.3 + headBob);
ctx.lineTo(0, height * 0.15);
ctx.lineTo(width * 0.12, height * 0.25 + headBob);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(width * 0.95, height * 0.3 + headBob);
ctx.lineTo(width, height * 0.15);
ctx.lineTo(width * 0.88, height * 0.25 + headBob);
ctx.closePath();
ctx.fill();
// Dragon body below (tail hint)
ctx.fillStyle = '#2a5a3a';
ctx.beginPath();
ctx.moveTo(width * 0.3, height * 0.85);
ctx.quadraticCurveTo(width * 0.5, height * 0.75, width * 0.7, height * 0.85);
ctx.lineTo(width * 0.65, height);
ctx.lineTo(width * 0.35, height);
ctx.closePath();
ctx.fill();
// Dragon legs/claws
ctx.fillStyle = '#2a5a3a';
ctx.beginPath();
ctx.moveTo(width * 0.2, height * 0.7);
ctx.quadraticCurveTo(width * 0.15, height * 0.85, width * 0.1, height);
ctx.lineTo(width * 0.25, height);
ctx.quadraticCurveTo(width * 0.25, height * 0.8, width * 0.28, height * 0.7);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(width * 0.8, height * 0.7);
ctx.quadraticCurveTo(width * 0.85, height * 0.85, width * 0.9, height);
ctx.lineTo(width * 0.75, height);
ctx.quadraticCurveTo(width * 0.75, height * 0.8, width * 0.72, height * 0.7);
ctx.closePath();
ctx.fill();
// Claws
ctx.fillStyle = '#3a3a3a';
for (var i = 0; i < 3; i++) {
ctx.beginPath();
ctx.moveTo(width * 0.08 + i * 8, height);
ctx.lineTo(width * 0.1 + i * 8, height * 0.95);
ctx.lineTo(width * 0.12 + i * 8, height);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(width * 0.88 - i * 8, height);
ctx.lineTo(width * 0.9 - i * 8, height * 0.95);
ctx.lineTo(width * 0.92 - i * 8, height);
ctx.closePath();
ctx.fill();
}
},
contextEffects: {
idle: { type: 'bob', amplitude: 5, rate: 2 },
fireBreath: { type: 'glow', color: '#ff6600' },
dive: { type: 'blur', direction: 'vertical' },
damage: { type: 'shake', intensity: 8 }
}
},
// ============================================================
// 12. MOTORCYCLE
// ============================================================
{
id: 'motorcycle',
name: 'Motorcycle',
category: 'vehicle',
avatarMode: 'FULL_BODY',
animations: ['idle', 'bob', 'celebrate', 'hurt', 'lean-left', 'lean-right'],
defaultAnimation: 'idle',
accessoryVisibility: { eyewear: true, headwear: true, effects: true },
layerOrder: ['background', 'avatar', 'foreground'],
recommendedFor: ['racing', 'endless', 'arcade', 'action'],
description: 'Race on a high-speed motorcycle',
avatarPosition: { x: 0.5, y: 0.3 },
avatarScale: 0.55,
renderBackground: function(ctx, width, height, time, options) {
// Sky
var skyGradient = ctx.createLinearGradient(0, 0, 0, height * 0.4);
skyGradient.addColorStop(0, '#ff7700');
skyGradient.addColorStop(1, '#ffaa44');
ctx.fillStyle = skyGradient;
ctx.fillRect(0, 0, width, height * 0.4);
// Road
var roadGradient = ctx.createLinearGradient(0, height * 0.4, 0, height);
roadGradient.addColorStop(0, '#444444');
roadGradient.addColorStop(1, '#222222');
ctx.fillStyle = roadGradient;
ctx.fillRect(0, height * 0.4, width, height * 0.6);
// Road lines
ctx.fillStyle = '#ffffff';
var lineOffset = (time * 400) % 100;
for (var i = -1; i < 6; i++) {
var y = height * 0.5 + i * 100 - lineOffset;
var perspectiveScale = (y - height * 0.4) / (height * 0.6);
var lineWidth = 4 + perspectiveScale * 10;
ctx.fillRect(width * 0.48, y, width * 0.04, 50);
}
// Environment blur (side)
ctx.fillStyle = 'rgba(100, 150, 100, 0.3)';
for (var j = 0; j < 8; j++) {
var blurY = height * 0.3 + j * 40;
var blurOffset = (time * 300 + j * 50) % 200;
ctx.fillRect(0, blurY, width * 0.1 - blurOffset * 0.05, 30);
ctx.fillRect(width * 0.9 + blurOffset * 0.05, blurY, width * 0.1, 30);
}
// Motion blur lines
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
ctx.lineWidth = 2;
for (var k = 0; k < 15; k++) {
var seed = k * 1931;
var lx = ((seed * 13) % 1000) / 1000 * width;
var ly = height * 0.45 + ((seed * 17) % 1000) / 1000 * height * 0.5;
ctx.beginPath();
ctx.moveTo(lx, ly);
ctx.lineTo(lx, ly + 40);
ctx.stroke();
}
},
renderForeground: function(ctx, width, height, time, options) {
// Motorcycle
var bikeY = height * 0.55;
var wheelSpin = time * 20;
// Bike body
var bodyGradient = ctx.createLinearGradient(width * 0.3, bikeY, width * 0.7, bikeY);
bodyGradient.addColorStop(0, '#cc0000');
bodyGradient.addColorStop(0.5, '#ff3333');
bodyGradient.addColorStop(1, '#cc0000');
ctx.fillStyle = bodyGradient;
// Main body shape
ctx.beginPath();
ctx.moveTo(width * 0.25, bikeY);
ctx.quadraticCurveTo(width * 0.35, bikeY - height * 0.08, width * 0.5, bikeY - height * 0.1);
ctx.quadraticCurveTo(width * 0.65, bikeY - height * 0.08, width * 0.75, bikeY);
ctx.lineTo(width * 0.7, bikeY + height * 0.05);
ctx.lineTo(width * 0.3, bikeY + height * 0.05);
ctx.closePath();
ctx.fill();
// Fuel tank
ctx.fillStyle = '#aa0000';
ctx.beginPath();
ctx.ellipse(width * 0.5, bikeY - height * 0.05, width * 0.12, height * 0.04, 0, 0, Math.PI * 2);
ctx.fill();
// Tank stripe
ctx.fillStyle = '#ffffff';
ctx.fillRect(width * 0.48, bikeY - height * 0.08, width * 0.04, height * 0.06);
// Engine
ctx.fillStyle = '#333333';
ctx.beginPath();
ctx.rect(width * 0.4, bikeY + height * 0.02, width * 0.2, height * 0.1);
ctx.fill();
// Engine details
ctx.fillStyle = '#555555';
for (var i = 0; i < 4; i++) {
ctx.fillRect(width * 0.42 + i * width * 0.04, bikeY + height * 0.03, width * 0.02, height * 0.08);
}
// Exhaust pipes
ctx.fillStyle = '#666666';
ctx.beginPath();
ctx.moveTo(width * 0.6, bikeY + height * 0.08);
ctx.quadraticCurveTo(width * 0.75, bikeY + height * 0.1, width * 0.85, bikeY + height * 0.15);
ctx.lineTo(width * 0.85, bikeY + height * 0.18);
ctx.quadraticCurveTo(width * 0.75, bikeY + height * 0.13, width * 0.6, bikeY + height * 0.11);
ctx.closePath();
ctx.fill();
// Front wheel
var frontWheelX = width * 0.25;
var wheelY = bikeY + height * 0.15;
var wheelRadius = width * 0.1;
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.arc(frontWheelX, wheelY, wheelRadius, 0, Math.PI * 2);
ctx.fill();
// Wheel spokes
ctx.strokeStyle = '#444444';
ctx.lineWidth = 2;
for (var j = 0; j < 8; j++) {
var spokeAngle = wheelSpin + (j / 8) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(frontWheelX, wheelY);
ctx.lineTo(
frontWheelX + Math.cos(spokeAngle) * wheelRadius * 0.8,
wheelY + Math.sin(spokeAngle) * wheelRadius * 0.8
);
ctx.stroke();
}
// Wheel hub
ctx.fillStyle = '#888888';
ctx.beginPath();
ctx.arc(frontWheelX, wheelY, wheelRadius * 0.25, 0, Math.PI * 2);
ctx.fill();
// Rear wheel
var rearWheelX = width * 0.75;
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.arc(rearWheelX, wheelY, wheelRadius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#444444';
for (var k = 0; k < 8; k++) {
var rearSpokeAngle = wheelSpin + (k / 8) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(rearWheelX, wheelY);
ctx.lineTo(
rearWheelX + Math.cos(rearSpokeAngle) * wheelRadius * 0.8,
wheelY + Math.sin(rearSpokeAngle) * wheelRadius * 0.8
);
ctx.stroke();
}
ctx.fillStyle = '#888888';
ctx.beginPath();
ctx.arc(rearWheelX, wheelY, wheelRadius * 0.25, 0, Math.PI * 2);
ctx.fill();
// Front fork
ctx.strokeStyle = '#666666';
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(width * 0.32, bikeY - height * 0.05);
ctx.lineTo(frontWheelX, wheelY);
ctx.stroke();
// Handlebars
ctx.strokeStyle = '#444444';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(width * 0.25, bikeY - height * 0.12);
ctx.lineTo(width * 0.4, bikeY - height * 0.1);
ctx.stroke();
// Mirrors
ctx.fillStyle = '#333333';
ctx.beginPath();
ctx.ellipse(width * 0.22, bikeY - height * 0.14, width * 0.02, height * 0.015, -0.3, 0, Math.PI * 2);
ctx.fill();
// Headlight
ctx.fillStyle = '#ffff88';
ctx.beginPath();
ctx.arc(width * 0.2, bikeY - height * 0.02, width * 0.025, 0, Math.PI * 2);
ctx.fill();
// Seat
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.moveTo(width * 0.42, bikeY - height * 0.1);
ctx.quadraticCurveTo(width * 0.55, bikeY - height * 0.14, width * 0.68, bikeY - height * 0.08);
ctx.lineTo(width * 0.65, bikeY - height * 0.06);
ctx.quadraticCurveTo(width * 0.55, bikeY - height * 0.1, width * 0.45, bikeY - height * 0.08);
ctx.closePath();
ctx.fill();
// Speed blur from wheels
ctx.fillStyle = 'rgba(150, 150, 150, 0.3)';
ctx.beginPath();
ctx.ellipse(frontWheelX, wheelY + wheelRadius * 0.8, wheelRadius * 0.8, wheelRadius * 0.15, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(rearWheelX, wheelY + wheelRadius * 0.8, wheelRadius * 0.8, wheelRadius * 0.15, 0, 0, Math.PI * 2);
ctx.fill();
},
contextEffects: {
idle: null,
accelerate: { type: 'blur', direction: 'vertical' },
wheelie: { type: 'rotate', angle: -15 },
lean: { type: 'rotate', angle: 20 },
damage: { type: 'shake', intensity: 8 }
}
}
];
// ============================================================
// PUBLIC API
// ============================================================
window.VehicleContexts = {
CONTEXTS: CONTEXTS,
/**
* Get a vehicle context by its ID
* @param {string} id - The context ID
* @returns {Object|null} The context object or null if not found
*/
getById: function(id) {
for (var i = 0; i < CONTEXTS.length; i++) {
if (CONTEXTS[i].id === id) {
return CONTEXTS[i];
}
}
return null;
},
/**
* Get all vehicle contexts
* @returns {Array} Array of all context objects
*/
getAll: function() {
return CONTEXTS.slice();
},
/**
* Get contexts filtered by avatar mode
* @param {string} mode - The avatar mode (HEAD_ONLY, HEAD_AND_SHOULDERS, UPPER_BODY, FULL_BODY)
* @returns {Array} Array of matching context objects
*/
getByMode: function(mode) {
var results = [];
for (var i = 0; i < CONTEXTS.length; i++) {
if (CONTEXTS[i].avatarMode === mode) {
results.push(CONTEXTS[i]);
}
}
return results;
},
/**
* Get contexts recommended for a specific game type
* @param {string} gameType - The game type to match
* @returns {Array} Array of matching context objects
*/
getRecommendedFor: function(gameType) {
var results = [];
var lowerType = gameType.toLowerCase();
for (var i = 0; i < CONTEXTS.length; i++) {
var recommended = CONTEXTS[i].recommendedFor;
for (var j = 0; j < recommended.length; j++) {
if (recommended[j].toLowerCase() === lowerType) {
results.push(CONTEXTS[i]);
break;
}
}
}
return results;
}
};
})();