(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; } }; })();