Files
2026-04-30 02:14:25 +00:00

1876 lines
64 KiB
JavaScript

/**
* CostumeContexts - Themed costume overlays for the avatar system
* Depends on: AvatarRenderer, AvatarAnimations
*/
(function() {
'use strict';
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
function roundRect(ctx, x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
function darken(color, amount) {
var hex = color.replace('#', '');
var r = Math.max(0, Math.floor(parseInt(hex.substr(0, 2), 16) * (1 - amount)));
var g = Math.max(0, Math.floor(parseInt(hex.substr(2, 2), 16) * (1 - amount)));
var b = Math.max(0, Math.floor(parseInt(hex.substr(4, 2), 16) * (1 - amount)));
return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
}
function lighten(color, amount) {
var hex = color.replace('#', '');
var r = Math.min(255, Math.floor(parseInt(hex.substr(0, 2), 16) + (255 - parseInt(hex.substr(0, 2), 16)) * amount));
var g = Math.min(255, Math.floor(parseInt(hex.substr(2, 2), 16) + (255 - parseInt(hex.substr(2, 2), 16)) * amount));
var b = Math.min(255, Math.floor(parseInt(hex.substr(4, 2), 16) + (255 - parseInt(hex.substr(4, 2), 16)) * amount));
return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
}
function getArmTransform(transforms, arm) {
if (!transforms || !transforms[arm]) return { rotation: 0, x: 0, y: 0 };
return transforms[arm];
}
// ============================================================================
// COSTUME: KNIGHT ARMOR
// ============================================================================
var knightArmor = {
id: 'knight-armor',
name: 'Knight Armor',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: false,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['rpg', 'adventure', 'action'],
description: 'Medieval knight in shining armor',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'sword', anchor: 'rightArm', offset: { x: 5, y: 20 } },
{ id: 'shield', anchor: 'leftArm', offset: { x: -10, y: 0 } }
],
contextEffects: {
attack: { type: 'slash', color: '#c0c0c0' },
hurt: { type: 'sparks', color: '#ffa500' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var metalColor = '#a8a9ad';
var metalDark = '#7d7f83';
var metalLight = '#d4d5d9';
// Cape (behind everything)
ctx.fillStyle = '#8b0000';
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 68 * scale);
ctx.quadraticCurveTo(cx - 25 * scale, y + 100 * scale, cx - 20 * scale + Math.sin(time * 2) * 3 * scale, y + 145 * scale);
ctx.lineTo(cx + 20 * scale + Math.sin(time * 2 + 1) * 3 * scale, y + 145 * scale);
ctx.quadraticCurveTo(cx + 25 * scale, y + 100 * scale, cx + 18 * scale, y + 68 * scale);
ctx.closePath();
ctx.fill();
// Cape trim
ctx.fillStyle = '#ffd700';
ctx.fillRect(cx - 20 * scale, y + 140 * scale, 40 * scale, 5 * scale);
// Body armor (chestplate)
ctx.fillStyle = metalColor;
roundRect(ctx, cx - 22 * scale, y + 68 * scale, 44 * scale, 50 * scale, 4 * scale);
ctx.fill();
// Chestplate details
ctx.strokeStyle = metalDark;
ctx.lineWidth = 2 * scale;
ctx.beginPath();
ctx.moveTo(cx, y + 70 * scale);
ctx.lineTo(cx, y + 115 * scale);
ctx.stroke();
// Armor ridges
ctx.strokeStyle = metalLight;
ctx.lineWidth = 1 * scale;
for (var i = 0; i < 3; i++) {
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 78 * scale + i * 12 * scale);
ctx.lineTo(cx + 18 * scale, y + 78 * scale + i * 12 * scale);
ctx.stroke();
}
// Leg armor (greaves)
ctx.fillStyle = metalColor;
roundRect(ctx, cx - 16 * scale, y + 120 * scale, 12 * scale, 35 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 120 * scale, 12 * scale, 35 * scale, 2 * scale);
ctx.fill();
// Knee guards
ctx.fillStyle = metalLight;
ctx.beginPath();
ctx.arc(cx - 10 * scale, y + 125 * scale, 5 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 10 * scale, y + 125 * scale, 5 * scale, 0, Math.PI * 2);
ctx.fill();
// Armored boots
ctx.fillStyle = metalDark;
roundRect(ctx, cx - 18 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
ctx.fill();
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var metalColor = '#a8a9ad';
var metalDark = '#7d7f83';
var metalLight = '#d4d5d9';
// Shoulder pauldrons
ctx.fillStyle = metalColor;
ctx.beginPath();
ctx.ellipse(cx - 26 * scale, y + 70 * scale, 12 * scale, 8 * scale, -0.3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(cx + 26 * scale, y + 70 * scale, 12 * scale, 8 * scale, 0.3, 0, Math.PI * 2);
ctx.fill();
// Pauldron spikes
ctx.fillStyle = metalLight;
ctx.beginPath();
ctx.moveTo(cx - 32 * scale, y + 65 * scale);
ctx.lineTo(cx - 35 * scale, y + 58 * scale);
ctx.lineTo(cx - 28 * scale, y + 65 * scale);
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 32 * scale, y + 65 * scale);
ctx.lineTo(cx + 35 * scale, y + 58 * scale);
ctx.lineTo(cx + 28 * scale, y + 65 * scale);
ctx.fill();
// Helmet
ctx.fillStyle = metalColor;
ctx.beginPath();
ctx.arc(cx, y + 32 * scale, 24 * scale, Math.PI, 0, false);
ctx.fill();
// Helmet face plate
ctx.fillStyle = metalDark;
roundRect(ctx, cx - 18 * scale, y + 20 * scale, 36 * scale, 28 * scale, 4 * scale);
ctx.fill();
// Visor slits (eyes peek through)
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(cx - 14 * scale, y + 28 * scale, 10 * scale, 3 * scale);
ctx.fillRect(cx + 4 * scale, y + 28 * scale, 10 * scale, 3 * scale);
// Helmet crest
ctx.fillStyle = '#8b0000';
ctx.beginPath();
ctx.moveTo(cx - 3 * scale, y + 8 * scale);
ctx.lineTo(cx, y - 5 * scale);
ctx.lineTo(cx + 3 * scale, y + 8 * scale);
ctx.closePath();
ctx.fill();
// Sword (right hand)
var rightArm = getArmTransform(transforms, 'rightArm');
ctx.save();
ctx.translate(cx + 26 * scale, y + 115 * scale);
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
// Sword blade
ctx.fillStyle = metalLight;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-2 * scale, -35 * scale);
ctx.lineTo(0, -45 * scale);
ctx.lineTo(2 * scale, -35 * scale);
ctx.closePath();
ctx.fill();
// Sword guard
ctx.fillStyle = '#ffd700';
ctx.fillRect(-6 * scale, -2 * scale, 12 * scale, 4 * scale);
// Sword handle
ctx.fillStyle = '#4a3728';
ctx.fillRect(-2 * scale, 2 * scale, 4 * scale, 10 * scale);
ctx.restore();
// Shield (left hand)
var leftArm = getArmTransform(transforms, 'leftArm');
ctx.save();
ctx.translate(cx - 30 * scale, y + 95 * scale);
ctx.rotate((leftArm.rotation || 0) * Math.PI / 180);
// Shield body
ctx.fillStyle = metalColor;
ctx.beginPath();
ctx.moveTo(0, -15 * scale);
ctx.lineTo(12 * scale, -10 * scale);
ctx.lineTo(12 * scale, 10 * scale);
ctx.lineTo(0, 20 * scale);
ctx.lineTo(-12 * scale, 10 * scale);
ctx.lineTo(-12 * scale, -10 * scale);
ctx.closePath();
ctx.fill();
// Shield emblem
ctx.fillStyle = '#8b0000';
ctx.beginPath();
ctx.arc(0, 0, 6 * scale, 0, Math.PI * 2);
ctx.fill();
// Shield cross
ctx.fillStyle = '#ffd700';
ctx.fillRect(-1 * scale, -8 * scale, 2 * scale, 16 * scale);
ctx.fillRect(-6 * scale, -1 * scale, 12 * scale, 2 * scale);
ctx.restore();
}
};
// ============================================================================
// COSTUME: SPACE SUIT
// ============================================================================
var spaceSuit = {
id: 'space-suit',
name: 'Space Suit',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['action', 'adventure', 'physics'],
description: 'Astronaut ready for space exploration',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [],
contextEffects: {
hurt: { type: 'sparks', color: '#00ffff' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var suitColor = '#f0f0f0';
var accentColor = '#4a90d9';
// Oxygen pack (on back)
ctx.fillStyle = '#e0e0e0';
roundRect(ctx, cx - 12 * scale, y + 65 * scale, 24 * scale, 35 * scale, 4 * scale);
ctx.fill();
// Pack details
ctx.fillStyle = accentColor;
ctx.fillRect(cx - 8 * scale, y + 70 * scale, 16 * scale, 4 * scale);
ctx.fillRect(cx - 8 * scale, y + 80 * scale, 16 * scale, 4 * scale);
// Pack lights
var blink = Math.sin(time * 3) > 0 ? 1 : 0.3;
ctx.fillStyle = 'rgba(0, 255, 100, ' + blink + ')';
ctx.beginPath();
ctx.arc(cx - 4 * scale, y + 92 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 100, 0, ' + (1 - blink) + ')';
ctx.beginPath();
ctx.arc(cx + 4 * scale, y + 92 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
// Suit body
ctx.fillStyle = suitColor;
roundRect(ctx, cx - 24 * scale, y + 64 * scale, 48 * scale, 55 * scale, 6 * scale);
ctx.fill();
// Blue accents on torso
ctx.fillStyle = accentColor;
ctx.fillRect(cx - 24 * scale, y + 64 * scale, 8 * scale, 55 * scale);
ctx.fillRect(cx + 16 * scale, y + 64 * scale, 8 * scale, 55 * scale);
// Suit legs
ctx.fillStyle = suitColor;
roundRect(ctx, cx - 18 * scale, y + 118 * scale, 14 * scale, 38 * scale, 4 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 118 * scale, 14 * scale, 38 * scale, 4 * scale);
ctx.fill();
// Blue leg stripes
ctx.fillStyle = accentColor;
ctx.fillRect(cx - 18 * scale, y + 130 * scale, 14 * scale, 4 * scale);
ctx.fillRect(cx + 4 * scale, y + 130 * scale, 14 * scale, 4 * scale);
// Boots with magnetic plates
ctx.fillStyle = '#555';
roundRect(ctx, cx - 20 * scale, y + 152 * scale, 16 * scale, 8 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 152 * scale, 16 * scale, 8 * scale, 2 * scale);
ctx.fill();
// Magnetic plate glow
ctx.fillStyle = accentColor;
ctx.fillRect(cx - 18 * scale, y + 158 * scale, 12 * scale, 2 * scale);
ctx.fillRect(cx + 6 * scale, y + 158 * scale, 12 * scale, 2 * scale);
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var suitColor = '#f0f0f0';
var accentColor = '#4a90d9';
// Gloves
ctx.fillStyle = suitColor;
ctx.beginPath();
ctx.arc(cx - 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
ctx.fill();
// Glove accents
ctx.fillStyle = accentColor;
ctx.beginPath();
ctx.arc(cx - 26 * scale, y + 115 * scale, 3 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 26 * scale, y + 115 * scale, 3 * scale, 0, Math.PI * 2);
ctx.fill();
// Helmet bubble
ctx.fillStyle = 'rgba(200, 230, 255, 0.3)';
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 3 * scale;
ctx.beginPath();
ctx.arc(cx, y + 32 * scale, 28 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// Helmet reflection
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
ctx.beginPath();
ctx.ellipse(cx - 8 * scale, y + 22 * scale, 8 * scale, 12 * scale, -0.3, 0, Math.PI * 2);
ctx.fill();
// Helmet ring
ctx.fillStyle = '#ddd';
ctx.beginPath();
ctx.ellipse(cx, y + 55 * scale, 22 * scale, 6 * scale, 0, 0, Math.PI * 2);
ctx.fill();
// Helmet accent
ctx.fillStyle = accentColor;
ctx.beginPath();
ctx.arc(cx, y + 5 * scale, 4 * scale, 0, Math.PI * 2);
ctx.fill();
// Antenna
ctx.strokeStyle = '#888';
ctx.lineWidth = 2 * scale;
ctx.beginPath();
ctx.moveTo(cx + 20 * scale, y + 15 * scale);
ctx.lineTo(cx + 28 * scale, y - 5 * scale);
ctx.stroke();
ctx.fillStyle = '#ff3333';
ctx.beginPath();
ctx.arc(cx + 28 * scale, y - 5 * scale, 3 * scale, 0, Math.PI * 2);
ctx.fill();
}
};
// ============================================================================
// COSTUME: NINJA OUTFIT
// ============================================================================
var ninjaOutfit = {
id: 'ninja-outfit',
name: 'Ninja Outfit',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: false,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['action', 'adventure', 'rpg'],
description: 'Stealthy ninja warrior',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'katana', anchor: 'back', offset: { x: 0, y: -10 } }
],
contextEffects: {
attack: { type: 'slash', color: '#333' },
hurt: { type: 'smoke', color: '#555' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var ninjaBlack = '#1a1a1a';
var ninjaGray = '#333';
// Katana on back
ctx.save();
ctx.translate(cx + 15 * scale, y + 50 * scale);
ctx.rotate(-0.4);
// Scabbard
ctx.fillStyle = '#2c1810';
roundRect(ctx, -3 * scale, 0, 6 * scale, 50 * scale, 2 * scale);
ctx.fill();
// Handle wrap
ctx.fillStyle = '#8b0000';
for (var i = 0; i < 5; i++) {
ctx.fillRect(-3 * scale, -10 * scale + i * 4 * scale, 6 * scale, 2 * scale);
}
// Guard
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.ellipse(0, 0, 5 * scale, 2 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// Body suit
ctx.fillStyle = ninjaBlack;
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 52 * scale, 4 * scale);
ctx.fill();
// Belt with throwing stars
ctx.fillStyle = ninjaGray;
ctx.fillRect(cx - 22 * scale, y + 100 * scale, 44 * scale, 8 * scale);
// Throwing stars on belt
ctx.fillStyle = '#888';
for (var s = 0; s < 3; s++) {
var sx = cx - 12 * scale + s * 12 * scale;
var sy = y + 104 * scale;
ctx.beginPath();
for (var p = 0; p < 4; p++) {
var angle = p * Math.PI / 2;
var px = sx + Math.cos(angle) * 3 * scale;
var py = sy + Math.sin(angle) * 3 * scale;
if (p === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.closePath();
ctx.fill();
}
// Legs
ctx.fillStyle = ninjaBlack;
roundRect(ctx, cx - 16 * scale, y + 116 * scale, 12 * scale, 36 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 116 * scale, 12 * scale, 36 * scale, 3 * scale);
ctx.fill();
// Leg wraps
ctx.strokeStyle = ninjaGray;
ctx.lineWidth = 2 * scale;
for (var w = 0; w < 4; w++) {
ctx.beginPath();
ctx.moveTo(cx - 16 * scale, y + 125 * scale + w * 8 * scale);
ctx.lineTo(cx - 4 * scale, y + 128 * scale + w * 8 * scale);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx + 4 * scale, y + 125 * scale + w * 8 * scale);
ctx.lineTo(cx + 16 * scale, y + 128 * scale + w * 8 * scale);
ctx.stroke();
}
// Tabi boots
ctx.fillStyle = ninjaBlack;
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 152 * scale);
ctx.lineTo(cx - 18 * scale, y + 160 * scale);
ctx.lineTo(cx - 10 * scale, y + 160 * scale);
ctx.lineTo(cx - 8 * scale, y + 155 * scale);
ctx.lineTo(cx - 4 * scale, y + 160 * scale);
ctx.lineTo(cx - 4 * scale, y + 152 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 4 * scale, y + 152 * scale);
ctx.lineTo(cx + 4 * scale, y + 160 * scale);
ctx.lineTo(cx + 10 * scale, y + 160 * scale);
ctx.lineTo(cx + 12 * scale, y + 155 * scale);
ctx.lineTo(cx + 18 * scale, y + 160 * scale);
ctx.lineTo(cx + 18 * scale, y + 152 * scale);
ctx.closePath();
ctx.fill();
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var ninjaBlack = '#1a1a1a';
// Arm wraps
ctx.fillStyle = ninjaBlack;
roundRect(ctx, cx - 32 * scale, y + 70 * scale, 10 * scale, 45 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 22 * scale, y + 70 * scale, 10 * scale, 45 * scale, 3 * scale);
ctx.fill();
// Face mask
ctx.fillStyle = ninjaBlack;
ctx.beginPath();
ctx.moveTo(cx - 22 * scale, y + 35 * scale);
ctx.lineTo(cx - 22 * scale, y + 55 * scale);
ctx.quadraticCurveTo(cx, y + 60 * scale, cx + 22 * scale, y + 55 * scale);
ctx.lineTo(cx + 22 * scale, y + 35 * scale);
ctx.quadraticCurveTo(cx, y + 38 * scale, cx - 22 * scale, y + 35 * scale);
ctx.fill();
// Eye slit
ctx.fillStyle = '#000';
ctx.fillRect(cx - 18 * scale, y + 28 * scale, 36 * scale, 6 * scale);
// Headband
ctx.fillStyle = '#8b0000';
ctx.fillRect(cx - 24 * scale, y + 15 * scale, 48 * scale, 6 * scale);
// Headband tail
ctx.beginPath();
ctx.moveTo(cx + 24 * scale, y + 15 * scale);
ctx.quadraticCurveTo(cx + 35 * scale + Math.sin(time * 3) * 3 * scale, y + 25 * scale, cx + 40 * scale + Math.sin(time * 3) * 5 * scale, y + 35 * scale);
ctx.lineTo(cx + 36 * scale + Math.sin(time * 3) * 5 * scale, y + 38 * scale);
ctx.quadraticCurveTo(cx + 32 * scale + Math.sin(time * 3) * 3 * scale, y + 28 * scale, cx + 24 * scale, y + 21 * scale);
ctx.closePath();
ctx.fill();
}
};
// ============================================================================
// COSTUME: WIZARD ROBES
// ============================================================================
var wizardRobes = {
id: 'wizard-robes',
name: 'Wizard Robes',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['rpg', 'puzzle', 'adventure'],
description: 'Mystical wizard with flowing robes',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'staff', anchor: 'rightArm', offset: { x: 5, y: 0 } }
],
contextEffects: {
attack: { type: 'magic', color: '#9b59b6' },
celebrate: { type: 'sparkles', color: '#ffd700' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var robeColor = '#2c3e50';
var robeLight = '#3d566e';
var accent = '#9b59b6';
// Main robe body
ctx.fillStyle = robeColor;
ctx.beginPath();
ctx.moveTo(cx - 22 * scale, y + 64 * scale);
ctx.lineTo(cx - 30 * scale + Math.sin(time * 1.5) * 2 * scale, y + 158 * scale);
ctx.quadraticCurveTo(cx, y + 162 * scale, cx + 30 * scale + Math.sin(time * 1.5 + 1) * 2 * scale, y + 158 * scale);
ctx.lineTo(cx + 22 * scale, y + 64 * scale);
ctx.closePath();
ctx.fill();
// Robe inner fold
ctx.fillStyle = robeLight;
ctx.beginPath();
ctx.moveTo(cx - 5 * scale, y + 70 * scale);
ctx.lineTo(cx - 8 * scale, y + 155 * scale);
ctx.lineTo(cx + 8 * scale, y + 155 * scale);
ctx.lineTo(cx + 5 * scale, y + 70 * scale);
ctx.closePath();
ctx.fill();
// Belt
ctx.fillStyle = '#8b4513';
ctx.fillRect(cx - 22 * scale, y + 100 * scale, 44 * scale, 8 * scale);
// Belt buckle
ctx.fillStyle = '#ffd700';
roundRect(ctx, cx - 6 * scale, y + 99 * scale, 12 * scale, 10 * scale, 2 * scale);
ctx.fill();
// Belt pouches
ctx.fillStyle = '#6b3e26';
roundRect(ctx, cx - 20 * scale, y + 105 * scale, 8 * scale, 10 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 12 * scale, y + 105 * scale, 8 * scale, 10 * scale, 2 * scale);
ctx.fill();
// Mystical runes on robe
ctx.strokeStyle = accent;
ctx.lineWidth = 1 * scale;
ctx.globalAlpha = 0.5 + Math.sin(time * 2) * 0.3;
// Left rune
ctx.beginPath();
ctx.arc(cx - 15 * scale, y + 130 * scale, 5 * scale, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx - 15 * scale, y + 125 * scale);
ctx.lineTo(cx - 15 * scale, y + 135 * scale);
ctx.moveTo(cx - 20 * scale, y + 130 * scale);
ctx.lineTo(cx - 10 * scale, y + 130 * scale);
ctx.stroke();
// Right rune
ctx.beginPath();
ctx.arc(cx + 15 * scale, y + 130 * scale, 5 * scale, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx + 12 * scale, y + 127 * scale);
ctx.lineTo(cx + 18 * scale, y + 133 * scale);
ctx.moveTo(cx + 18 * scale, y + 127 * scale);
ctx.lineTo(cx + 12 * scale, y + 133 * scale);
ctx.stroke();
ctx.globalAlpha = 1;
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var robeColor = '#2c3e50';
var accent = '#9b59b6';
// Sleeves
ctx.fillStyle = robeColor;
ctx.beginPath();
ctx.moveTo(cx - 22 * scale, y + 68 * scale);
ctx.quadraticCurveTo(cx - 35 * scale, y + 90 * scale, cx - 32 * scale, y + 118 * scale);
ctx.lineTo(cx - 25 * scale, y + 115 * scale);
ctx.quadraticCurveTo(cx - 28 * scale, y + 90 * scale, cx - 18 * scale, y + 72 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 22 * scale, y + 68 * scale);
ctx.quadraticCurveTo(cx + 35 * scale, y + 90 * scale, cx + 32 * scale, y + 118 * scale);
ctx.lineTo(cx + 25 * scale, y + 115 * scale);
ctx.quadraticCurveTo(cx + 28 * scale, y + 90 * scale, cx + 18 * scale, y + 72 * scale);
ctx.closePath();
ctx.fill();
// Staff (right hand)
var rightArm = getArmTransform(transforms, 'rightArm');
ctx.save();
ctx.translate(cx + 28 * scale, y + 90 * scale);
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
// Staff shaft
ctx.fillStyle = '#5d4037';
roundRect(ctx, -3 * scale, -50 * scale, 6 * scale, 80 * scale, 2 * scale);
ctx.fill();
// Staff orb holder
ctx.fillStyle = '#3d2817';
ctx.beginPath();
ctx.moveTo(-6 * scale, -50 * scale);
ctx.quadraticCurveTo(-8 * scale, -58 * scale, 0, -62 * scale);
ctx.quadraticCurveTo(8 * scale, -58 * scale, 6 * scale, -50 * scale);
ctx.closePath();
ctx.fill();
// Staff orb
var orbGlow = ctx.createRadialGradient(0, -68 * scale, 0, 0, -68 * scale, 10 * scale);
orbGlow.addColorStop(0, '#e0b0ff');
orbGlow.addColorStop(0.5, accent);
orbGlow.addColorStop(1, '#4a235a');
ctx.fillStyle = orbGlow;
ctx.beginPath();
ctx.arc(0, -68 * scale, 8 * scale, 0, Math.PI * 2);
ctx.fill();
// Orb glow effect
ctx.globalAlpha = 0.3 + Math.sin(time * 3) * 0.2;
ctx.fillStyle = accent;
ctx.beginPath();
ctx.arc(0, -68 * scale, 12 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
ctx.restore();
// Hood/collar
ctx.fillStyle = robeColor;
ctx.beginPath();
ctx.moveTo(cx - 24 * scale, y + 55 * scale);
ctx.quadraticCurveTo(cx - 28 * scale, y + 45 * scale, cx - 20 * scale, y + 40 * scale);
ctx.lineTo(cx + 20 * scale, y + 40 * scale);
ctx.quadraticCurveTo(cx + 28 * scale, y + 45 * scale, cx + 24 * scale, y + 55 * scale);
ctx.quadraticCurveTo(cx, y + 50 * scale, cx - 24 * scale, y + 55 * scale);
ctx.fill();
}
};
// ============================================================================
// COSTUME: SUPERHERO CAPE
// ============================================================================
var superheroCape = {
id: 'superhero-cape',
name: 'Superhero Cape',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['action', 'adventure'],
description: 'Classic superhero with billowing cape',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [],
contextEffects: {
attack: { type: 'impact', color: '#ffff00' },
celebrate: { type: 'sparkles', color: '#ffd700' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var suitColor = avatar && avatar.base && avatar.base.clothing && avatar.base.clothing.color ? avatar.base.clothing.color : '#c0392b';
var capeColor = darken(suitColor, 0.3);
// Cape (billowing behind)
ctx.fillStyle = capeColor;
ctx.beginPath();
ctx.moveTo(cx - 20 * scale, y + 62 * scale);
var wave1 = Math.sin(time * 2) * 5 * scale;
var wave2 = Math.sin(time * 2 + 1) * 5 * scale;
var wave3 = Math.sin(time * 2 + 2) * 5 * scale;
ctx.quadraticCurveTo(cx - 30 * scale + wave1, y + 100 * scale, cx - 35 * scale + wave2, y + 155 * scale);
ctx.quadraticCurveTo(cx, y + 160 * scale + wave3, cx + 35 * scale + wave2, y + 155 * scale);
ctx.quadraticCurveTo(cx + 30 * scale + wave1, y + 100 * scale, cx + 20 * scale, y + 62 * scale);
ctx.closePath();
ctx.fill();
// Cape inner lining
ctx.fillStyle = lighten(capeColor, 0.2);
ctx.beginPath();
ctx.moveTo(cx - 15 * scale, y + 65 * scale);
ctx.quadraticCurveTo(cx - 22 * scale + wave1 * 0.5, y + 100 * scale, cx - 25 * scale + wave2 * 0.5, y + 145 * scale);
ctx.lineTo(cx + 25 * scale + wave2 * 0.5, y + 145 * scale);
ctx.quadraticCurveTo(cx + 22 * scale + wave1 * 0.5, y + 100 * scale, cx + 15 * scale, y + 65 * scale);
ctx.closePath();
ctx.fill();
// Bodysuit
ctx.fillStyle = suitColor;
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 52 * scale, 4 * scale);
ctx.fill();
// Belt
ctx.fillStyle = '#ffd700';
ctx.fillRect(cx - 22 * scale, y + 105 * scale, 44 * scale, 6 * scale);
// Belt buckle
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(cx, y + 108 * scale, 5 * scale, 0, Math.PI * 2);
ctx.fill();
// Trunks
ctx.fillStyle = darken(suitColor, 0.2);
roundRect(ctx, cx - 18 * scale, y + 108 * scale, 36 * scale, 12 * scale, 2 * scale);
ctx.fill();
// Legs (suit continues)
ctx.fillStyle = suitColor;
roundRect(ctx, cx - 16 * scale, y + 118 * scale, 12 * scale, 36 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 118 * scale, 12 * scale, 36 * scale, 3 * scale);
ctx.fill();
// Boots
ctx.fillStyle = darken(suitColor, 0.4);
roundRect(ctx, cx - 18 * scale, y + 145 * scale, 14 * scale, 15 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 145 * scale, 14 * scale, 15 * scale, 3 * scale);
ctx.fill();
// Boot tops
ctx.fillStyle = '#ffd700';
ctx.fillRect(cx - 18 * scale, y + 145 * scale, 14 * scale, 3 * scale);
ctx.fillRect(cx + 4 * scale, y + 145 * scale, 14 * scale, 3 * scale);
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var suitColor = avatar && avatar.base && avatar.base.clothing && avatar.base.clothing.color ? avatar.base.clothing.color : '#c0392b';
// Arms
ctx.fillStyle = suitColor;
roundRect(ctx, cx - 32 * scale, y + 68 * scale, 12 * scale, 45 * scale, 4 * scale);
ctx.fill();
roundRect(ctx, cx + 20 * scale, y + 68 * scale, 12 * scale, 45 * scale, 4 * scale);
ctx.fill();
// Gloves
ctx.fillStyle = darken(suitColor, 0.4);
ctx.beginPath();
ctx.arc(cx - 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 26 * scale, y + 115 * scale, 6 * scale, 0, Math.PI * 2);
ctx.fill();
// Glove cuffs
ctx.fillStyle = '#ffd700';
ctx.fillRect(cx - 32 * scale, y + 105 * scale, 12 * scale, 4 * scale);
ctx.fillRect(cx + 20 * scale, y + 105 * scale, 12 * scale, 4 * scale);
// Chest emblem
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.moveTo(cx, y + 70 * scale);
ctx.lineTo(cx - 12 * scale, y + 78 * scale);
ctx.lineTo(cx - 8 * scale, y + 95 * scale);
ctx.lineTo(cx, y + 88 * scale);
ctx.lineTo(cx + 8 * scale, y + 95 * scale);
ctx.lineTo(cx + 12 * scale, y + 78 * scale);
ctx.closePath();
ctx.fill();
// Mask
ctx.fillStyle = darken(suitColor, 0.3);
ctx.beginPath();
ctx.moveTo(cx - 22 * scale, y + 25 * scale);
ctx.quadraticCurveTo(cx - 25 * scale, y + 32 * scale, cx - 18 * scale, y + 38 * scale);
ctx.lineTo(cx - 8 * scale, y + 32 * scale);
ctx.lineTo(cx, y + 35 * scale);
ctx.lineTo(cx + 8 * scale, y + 32 * scale);
ctx.lineTo(cx + 18 * scale, y + 38 * scale);
ctx.quadraticCurveTo(cx + 25 * scale, y + 32 * scale, cx + 22 * scale, y + 25 * scale);
ctx.quadraticCurveTo(cx, y + 22 * scale, cx - 22 * scale, y + 25 * scale);
ctx.fill();
// Eye holes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.ellipse(cx - 12 * scale, y + 30 * scale, 6 * scale, 4 * scale, -0.2, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(cx + 12 * scale, y + 30 * scale, 6 * scale, 4 * scale, 0.2, 0, Math.PI * 2);
ctx.fill();
}
};
// ============================================================================
// COSTUME: ATHLETE UNIFORM
// ============================================================================
var athleteUniform = {
id: 'athlete-uniform',
name: 'Athlete Uniform',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['sports', 'action', 'racing'],
description: 'Athletic sports uniform',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'ball', anchor: 'rightArm', offset: { x: 10, y: 5 } }
],
contextEffects: {
celebrate: { type: 'confetti', color: '#ffd700' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var jerseyColor = '#e74c3c';
var accentColor = '#fff';
// Jersey
ctx.fillStyle = jerseyColor;
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 45 * scale, 4 * scale);
ctx.fill();
// Jersey stripes
ctx.fillStyle = accentColor;
ctx.fillRect(cx - 22 * scale, y + 75 * scale, 44 * scale, 4 * scale);
ctx.fillRect(cx - 22 * scale, y + 95 * scale, 44 * scale, 4 * scale);
// Number on back
ctx.fillStyle = accentColor;
ctx.font = 'bold ' + (16 * scale) + 'px Arial';
ctx.textAlign = 'center';
ctx.fillText('23', cx, y + 92 * scale);
// Shorts
ctx.fillStyle = '#2c3e50';
roundRect(ctx, cx - 18 * scale, y + 108 * scale, 36 * scale, 22 * scale, 3 * scale);
ctx.fill();
// Shorts stripe
ctx.fillStyle = jerseyColor;
ctx.fillRect(cx - 18 * scale, y + 108 * scale, 4 * scale, 22 * scale);
ctx.fillRect(cx + 14 * scale, y + 108 * scale, 4 * scale, 22 * scale);
// Legs
ctx.fillStyle = avatar && avatar.base ? avatar.base.skinTone || '#FFD5B8' : '#FFD5B8';
roundRect(ctx, cx - 14 * scale, y + 128 * scale, 10 * scale, 25 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 128 * scale, 10 * scale, 25 * scale, 3 * scale);
ctx.fill();
// Socks
ctx.fillStyle = accentColor;
roundRect(ctx, cx - 16 * scale, y + 140 * scale, 12 * scale, 15 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 140 * scale, 12 * scale, 15 * scale, 2 * scale);
ctx.fill();
// Sock stripes
ctx.fillStyle = jerseyColor;
ctx.fillRect(cx - 16 * scale, y + 145 * scale, 12 * scale, 2 * scale);
ctx.fillRect(cx + 4 * scale, y + 145 * scale, 12 * scale, 2 * scale);
// Athletic shoes
ctx.fillStyle = '#1a1a1a';
roundRect(ctx, cx - 18 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 152 * scale, 14 * scale, 8 * scale, 2 * scale);
ctx.fill();
// Shoe accents
ctx.fillStyle = jerseyColor;
ctx.fillRect(cx - 16 * scale, y + 154 * scale, 10 * scale, 2 * scale);
ctx.fillRect(cx + 6 * scale, y + 154 * scale, 10 * scale, 2 * scale);
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var jerseyColor = '#e74c3c';
var accentColor = '#fff';
// Sleeves
ctx.fillStyle = jerseyColor;
roundRect(ctx, cx - 30 * scale, y + 66 * scale, 12 * scale, 20 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 18 * scale, y + 66 * scale, 12 * scale, 20 * scale, 3 * scale);
ctx.fill();
// Sleeve stripes
ctx.fillStyle = accentColor;
ctx.fillRect(cx - 30 * scale, y + 78 * scale, 12 * scale, 3 * scale);
ctx.fillRect(cx + 18 * scale, y + 78 * scale, 12 * scale, 3 * scale);
// Headband
ctx.fillStyle = jerseyColor;
ctx.fillRect(cx - 22 * scale, y + 12 * scale, 44 * scale, 6 * scale);
// Sweatbands on wrists
ctx.fillStyle = accentColor;
ctx.fillRect(cx - 30 * scale, y + 100 * scale, 10 * scale, 6 * scale);
ctx.fillRect(cx + 20 * scale, y + 100 * scale, 10 * scale, 6 * scale);
// Number on front (smaller)
ctx.fillStyle = accentColor;
ctx.font = 'bold ' + (12 * scale) + 'px Arial';
ctx.textAlign = 'center';
ctx.fillText('23', cx, y + 82 * scale);
}
};
// ============================================================================
// COSTUME: PIRATE OUTFIT
// ============================================================================
var pirateOutfit = {
id: 'pirate-outfit',
name: 'Pirate Outfit',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['adventure', 'action', 'rpg'],
description: 'Swashbuckling pirate captain',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'cutlass', anchor: 'rightArm', offset: { x: 5, y: 15 } }
],
contextEffects: {
attack: { type: 'slash', color: '#c0c0c0' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var coatColor = '#8b0000';
var coatDark = '#5c0000';
// Long coat (back)
ctx.fillStyle = coatColor;
ctx.beginPath();
ctx.moveTo(cx - 24 * scale, y + 64 * scale);
ctx.lineTo(cx - 28 * scale + Math.sin(time * 1.5) * 2 * scale, y + 155 * scale);
ctx.quadraticCurveTo(cx, y + 158 * scale, cx + 28 * scale + Math.sin(time * 1.5 + 1) * 2 * scale, y + 155 * scale);
ctx.lineTo(cx + 24 * scale, y + 64 * scale);
ctx.closePath();
ctx.fill();
// Coat back detail
ctx.fillStyle = coatDark;
ctx.beginPath();
ctx.moveTo(cx - 5 * scale, y + 90 * scale);
ctx.lineTo(cx - 8 * scale, y + 150 * scale);
ctx.lineTo(cx + 8 * scale, y + 150 * scale);
ctx.lineTo(cx + 5 * scale, y + 90 * scale);
ctx.closePath();
ctx.fill();
// White shirt
ctx.fillStyle = '#f5f5dc';
roundRect(ctx, cx - 18 * scale, y + 64 * scale, 36 * scale, 40 * scale, 3 * scale);
ctx.fill();
// Shirt ruffles
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 1 * scale;
for (var r = 0; r < 4; r++) {
ctx.beginPath();
ctx.moveTo(cx - 8 * scale, y + 68 * scale + r * 8 * scale);
ctx.quadraticCurveTo(cx - 4 * scale, y + 72 * scale + r * 8 * scale, cx, y + 68 * scale + r * 8 * scale);
ctx.quadraticCurveTo(cx + 4 * scale, y + 72 * scale + r * 8 * scale, cx + 8 * scale, y + 68 * scale + r * 8 * scale);
ctx.stroke();
}
// Belt with buckle
ctx.fillStyle = '#2c1810';
ctx.fillRect(cx - 22 * scale, y + 102 * scale, 44 * scale, 8 * scale);
ctx.fillStyle = '#ffd700';
roundRect(ctx, cx - 8 * scale, y + 100 * scale, 16 * scale, 12 * scale, 2 * scale);
ctx.fill();
ctx.fillStyle = '#2c1810';
roundRect(ctx, cx - 4 * scale, y + 103 * scale, 8 * scale, 6 * scale, 1 * scale);
ctx.fill();
// Pants
ctx.fillStyle = '#2c3e50';
roundRect(ctx, cx - 16 * scale, y + 108 * scale, 32 * scale, 45 * scale, 3 * scale);
ctx.fill();
// Boots
ctx.fillStyle = '#3d2314';
roundRect(ctx, cx - 18 * scale, y + 140 * scale, 14 * scale, 20 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 140 * scale, 14 * scale, 20 * scale, 3 * scale);
ctx.fill();
// Boot tops (folded)
ctx.fillStyle = '#5d4037';
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 145 * scale);
ctx.lineTo(cx - 20 * scale, y + 140 * scale);
ctx.lineTo(cx - 4 * scale, y + 140 * scale);
ctx.lineTo(cx - 4 * scale, y + 145 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 4 * scale, y + 145 * scale);
ctx.lineTo(cx + 4 * scale, y + 140 * scale);
ctx.lineTo(cx + 20 * scale, y + 140 * scale);
ctx.lineTo(cx + 18 * scale, y + 145 * scale);
ctx.closePath();
ctx.fill();
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var coatColor = '#8b0000';
// Coat front panels
ctx.fillStyle = coatColor;
ctx.beginPath();
ctx.moveTo(cx - 22 * scale, y + 64 * scale);
ctx.lineTo(cx - 26 * scale, y + 110 * scale);
ctx.lineTo(cx - 10 * scale, y + 110 * scale);
ctx.lineTo(cx - 10 * scale, y + 64 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 22 * scale, y + 64 * scale);
ctx.lineTo(cx + 26 * scale, y + 110 * scale);
ctx.lineTo(cx + 10 * scale, y + 110 * scale);
ctx.lineTo(cx + 10 * scale, y + 64 * scale);
ctx.closePath();
ctx.fill();
// Gold trim on coat
ctx.strokeStyle = '#ffd700';
ctx.lineWidth = 2 * scale;
ctx.beginPath();
ctx.moveTo(cx - 10 * scale, y + 64 * scale);
ctx.lineTo(cx - 10 * scale, y + 110 * scale);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx + 10 * scale, y + 64 * scale);
ctx.lineTo(cx + 10 * scale, y + 110 * scale);
ctx.stroke();
// Gold buttons
ctx.fillStyle = '#ffd700';
for (var b = 0; b < 3; b++) {
ctx.beginPath();
ctx.arc(cx - 14 * scale, y + 72 * scale + b * 12 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 14 * scale, y + 72 * scale + b * 12 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
}
// Cutlass (right hand)
var rightArm = getArmTransform(transforms, 'rightArm');
ctx.save();
ctx.translate(cx + 28 * scale, y + 110 * scale);
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
// Blade
ctx.fillStyle = '#c0c0c0';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.quadraticCurveTo(8 * scale, -20 * scale, 5 * scale, -40 * scale);
ctx.lineTo(2 * scale, -40 * scale);
ctx.quadraticCurveTo(5 * scale, -20 * scale, -2 * scale, 0);
ctx.closePath();
ctx.fill();
// Guard
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.ellipse(0, 2 * scale, 8 * scale, 4 * scale, 0.3, 0, Math.PI * 2);
ctx.fill();
// Handle
ctx.fillStyle = '#3d2314';
roundRect(ctx, -2 * scale, 4 * scale, 4 * scale, 12 * scale, 1 * scale);
ctx.fill();
ctx.restore();
// Pirate hat
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.moveTo(cx - 28 * scale, y + 8 * scale);
ctx.quadraticCurveTo(cx - 32 * scale, y - 8 * scale, cx - 18 * scale, y - 18 * scale);
ctx.lineTo(cx, y - 8 * scale);
ctx.lineTo(cx + 18 * scale, y - 18 * scale);
ctx.quadraticCurveTo(cx + 32 * scale, y - 8 * scale, cx + 28 * scale, y + 8 * scale);
ctx.quadraticCurveTo(cx, y + 12 * scale, cx - 28 * scale, y + 8 * scale);
ctx.fill();
// Gold hat trim
ctx.strokeStyle = '#ffd700';
ctx.lineWidth = 2 * scale;
ctx.stroke();
// Skull emblem
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(cx, y - 5 * scale, 5 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(cx - 2 * scale, y - 6 * scale, 1 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 2 * scale, y - 6 * scale, 1 * scale, 0, Math.PI * 2);
ctx.fill();
}
};
// ============================================================================
// COSTUME: SCIENTIST LAB COAT
// ============================================================================
var scientistLabcoat = {
id: 'scientist-labcoat',
name: 'Scientist Lab Coat',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['puzzle', 'physics', 'creative'],
description: 'Mad scientist ready to experiment',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'beaker', anchor: 'rightArm', offset: { x: 5, y: 10 } }
],
contextEffects: {
attack: { type: 'bubbles', color: '#00ff00' }
},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
// Shirt underneath
ctx.fillStyle = '#3498db';
roundRect(ctx, cx - 18 * scale, y + 64 * scale, 36 * scale, 35 * scale, 3 * scale);
ctx.fill();
// Tie
ctx.fillStyle = '#2c3e50';
ctx.beginPath();
ctx.moveTo(cx - 4 * scale, y + 64 * scale);
ctx.lineTo(cx + 4 * scale, y + 64 * scale);
ctx.lineTo(cx + 3 * scale, y + 72 * scale);
ctx.lineTo(cx, y + 100 * scale);
ctx.lineTo(cx - 3 * scale, y + 72 * scale);
ctx.closePath();
ctx.fill();
// Pants
ctx.fillStyle = '#34495e';
roundRect(ctx, cx - 16 * scale, y + 98 * scale, 32 * scale, 55 * scale, 3 * scale);
ctx.fill();
// Sensible shoes
ctx.fillStyle = '#2c1810';
roundRect(ctx, cx - 18 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
ctx.fill();
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
// Lab coat
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.moveTo(cx - 24 * scale, y + 62 * scale);
ctx.lineTo(cx - 26 * scale, y + 140 * scale);
ctx.lineTo(cx - 10 * scale, y + 140 * scale);
ctx.lineTo(cx - 10 * scale, y + 62 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 24 * scale, y + 62 * scale);
ctx.lineTo(cx + 26 * scale, y + 140 * scale);
ctx.lineTo(cx + 10 * scale, y + 140 * scale);
ctx.lineTo(cx + 10 * scale, y + 62 * scale);
ctx.closePath();
ctx.fill();
// Coat collar
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.moveTo(cx - 20 * scale, y + 55 * scale);
ctx.lineTo(cx - 10 * scale, y + 70 * scale);
ctx.lineTo(cx - 5 * scale, y + 60 * scale);
ctx.quadraticCurveTo(cx, y + 58 * scale, cx + 5 * scale, y + 60 * scale);
ctx.lineTo(cx + 10 * scale, y + 70 * scale);
ctx.lineTo(cx + 20 * scale, y + 55 * scale);
ctx.quadraticCurveTo(cx, y + 50 * scale, cx - 20 * scale, y + 55 * scale);
ctx.fill();
// Coat sleeves
ctx.fillStyle = '#fff';
roundRect(ctx, cx - 32 * scale, y + 64 * scale, 12 * scale, 45 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 20 * scale, y + 64 * scale, 12 * scale, 45 * scale, 3 * scale);
ctx.fill();
// Pocket protector
ctx.fillStyle = '#ecf0f1';
roundRect(ctx, cx - 22 * scale, y + 72 * scale, 10 * scale, 15 * scale, 1 * scale);
ctx.fill();
// Pens in pocket
ctx.fillStyle = '#3498db';
ctx.fillRect(cx - 21 * scale, y + 70 * scale, 2 * scale, 12 * scale);
ctx.fillStyle = '#e74c3c';
ctx.fillRect(cx - 18 * scale, y + 71 * scale, 2 * scale, 11 * scale);
ctx.fillStyle = '#2c3e50';
ctx.fillRect(cx - 15 * scale, y + 70 * scale, 2 * scale, 10 * scale);
// ID badge
ctx.fillStyle = '#fff';
roundRect(ctx, cx + 12 * scale, y + 75 * scale, 12 * scale, 18 * scale, 1 * scale);
ctx.fill();
ctx.fillStyle = '#3498db';
ctx.fillRect(cx + 13 * scale, y + 76 * scale, 10 * scale, 6 * scale);
ctx.fillStyle = '#ddd';
ctx.fillRect(cx + 14 * scale, y + 84 * scale, 8 * scale, 2 * scale);
ctx.fillRect(cx + 14 * scale, y + 88 * scale, 6 * scale, 2 * scale);
// Safety goggles (on forehead)
ctx.fillStyle = '#2c3e50';
ctx.fillRect(cx - 24 * scale, y + 8 * scale, 48 * scale, 4 * scale);
ctx.fillStyle = 'rgba(100, 200, 255, 0.6)';
ctx.beginPath();
ctx.ellipse(cx - 12 * scale, y + 10 * scale, 10 * scale, 6 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(cx + 12 * scale, y + 10 * scale, 10 * scale, 6 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#1a1a2e';
ctx.lineWidth = 2 * scale;
ctx.stroke();
// Beaker in hand
var rightArm = getArmTransform(transforms, 'rightArm');
ctx.save();
ctx.translate(cx + 28 * scale, y + 110 * scale);
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
// Beaker glass
ctx.fillStyle = 'rgba(200, 230, 255, 0.4)';
ctx.strokeStyle = '#aaa';
ctx.lineWidth = 1 * scale;
ctx.beginPath();
ctx.moveTo(-5 * scale, -20 * scale);
ctx.lineTo(-6 * scale, 0);
ctx.lineTo(6 * scale, 0);
ctx.lineTo(5 * scale, -20 * scale);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Bubbling liquid
var liquidColor = 'rgba(0, 255, 100, 0.7)';
ctx.fillStyle = liquidColor;
ctx.beginPath();
ctx.moveTo(-5 * scale, -5 * scale);
ctx.lineTo(-6 * scale, 0);
ctx.lineTo(6 * scale, 0);
ctx.lineTo(5 * scale, -5 * scale);
ctx.closePath();
ctx.fill();
// Bubbles
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
for (var bub = 0; bub < 3; bub++) {
var bubY = -8 * scale - ((time * 20 + bub * 30) % 15) * scale;
var bubX = (bub - 1) * 3 * scale;
ctx.beginPath();
ctx.arc(bubX, bubY, 1.5 * scale, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
};
// ============================================================================
// COSTUME: CHEF OUTFIT
// ============================================================================
var chefOutfit = {
id: 'chef-outfit',
name: 'Chef Outfit',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['creative', 'puzzle'],
description: 'Master chef ready to cook',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'spatula', anchor: 'rightArm', offset: { x: 5, y: 15 } }
],
contextEffects: {},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
// Chef jacket
ctx.fillStyle = '#fff';
roundRect(ctx, cx - 22 * scale, y + 64 * scale, 44 * scale, 50 * scale, 4 * scale);
ctx.fill();
// Double-breasted buttons
ctx.fillStyle = '#ddd';
for (var row = 0; row < 2; row++) {
for (var col = 0; col < 3; col++) {
ctx.beginPath();
ctx.arc(cx + (row * 12 - 6) * scale, y + (75 + col * 12) * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
}
}
// Apron
ctx.fillStyle = '#f5f5f5';
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 90 * scale);
ctx.lineTo(cx - 20 * scale, y + 155 * scale);
ctx.lineTo(cx + 20 * scale, y + 155 * scale);
ctx.lineTo(cx + 18 * scale, y + 90 * scale);
ctx.closePath();
ctx.fill();
// Apron strings
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 2 * scale;
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 90 * scale);
ctx.lineTo(cx - 25 * scale, y + 95 * scale);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx + 18 * scale, y + 90 * scale);
ctx.lineTo(cx + 25 * scale, y + 95 * scale);
ctx.stroke();
// Apron pocket
ctx.fillStyle = '#eee';
roundRect(ctx, cx - 10 * scale, y + 115 * scale, 20 * scale, 15 * scale, 2 * scale);
ctx.fill();
// Pants
ctx.fillStyle = '#2c3e50';
roundRect(ctx, cx - 16 * scale, y + 112 * scale, 32 * scale, 42 * scale, 3 * scale);
ctx.fill();
// Shoes
ctx.fillStyle = '#1a1a1a';
roundRect(ctx, cx - 18 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
ctx.fill();
roundRect(ctx, cx + 4 * scale, y + 150 * scale, 14 * scale, 10 * scale, 2 * scale);
ctx.fill();
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
// Chef jacket sleeves
ctx.fillStyle = '#fff';
roundRect(ctx, cx - 30 * scale, y + 66 * scale, 12 * scale, 40 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 18 * scale, y + 66 * scale, 12 * scale, 40 * scale, 3 * scale);
ctx.fill();
// Kitchen towel over shoulder
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.moveTo(cx + 20 * scale, y + 64 * scale);
ctx.quadraticCurveTo(cx + 30 * scale, y + 70 * scale, cx + 25 * scale, y + 90 * scale);
ctx.lineTo(cx + 20 * scale, y + 88 * scale);
ctx.quadraticCurveTo(cx + 24 * scale, y + 72 * scale, cx + 18 * scale, y + 66 * scale);
ctx.closePath();
ctx.fill();
// Towel stripes
ctx.fillStyle = '#fff';
ctx.fillRect(cx + 21 * scale, y + 72 * scale, 2 * scale, 14 * scale);
ctx.fillRect(cx + 24 * scale, y + 74 * scale, 2 * scale, 12 * scale);
// Chef hat
ctx.fillStyle = '#fff';
// Puffy top
ctx.beginPath();
ctx.arc(cx - 10 * scale, y - 25 * scale, 12 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 10 * scale, y - 25 * scale, 12 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx, y - 30 * scale, 14 * scale, 0, Math.PI * 2);
ctx.fill();
// Hat band
roundRect(ctx, cx - 20 * scale, y - 12 * scale, 40 * scale, 20 * scale, 2 * scale);
ctx.fill();
// Hat band line
ctx.fillStyle = '#ecf0f1';
ctx.fillRect(cx - 20 * scale, y + 3 * scale, 40 * scale, 5 * scale);
// Utensils in apron pocket
ctx.fillStyle = '#888';
ctx.fillRect(cx - 6 * scale, y + 110 * scale, 2 * scale, 12 * scale);
ctx.fillRect(cx + 2 * scale, y + 112 * scale, 2 * scale, 10 * scale);
// Spatula in hand
var rightArm = getArmTransform(transforms, 'rightArm');
ctx.save();
ctx.translate(cx + 26 * scale, y + 108 * scale);
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
// Handle
ctx.fillStyle = '#5d4037';
roundRect(ctx, -2 * scale, 0, 4 * scale, 18 * scale, 1 * scale);
ctx.fill();
// Spatula head
ctx.fillStyle = '#888';
roundRect(ctx, -6 * scale, -15 * scale, 12 * scale, 18 * scale, 2 * scale);
ctx.fill();
// Slots
ctx.fillStyle = '#666';
ctx.fillRect(cx - 4 * scale - cx - 26 * scale, -12 * scale, 8 * scale, 2 * scale);
ctx.fillRect(cx - 4 * scale - cx - 26 * scale, -8 * scale, 8 * scale, 2 * scale);
ctx.fillRect(cx - 4 * scale - cx - 26 * scale, -4 * scale, 8 * scale, 2 * scale);
ctx.restore();
}
};
// ============================================================================
// COSTUME: COWBOY GEAR
// ============================================================================
var cowboyGear = {
id: 'cowboy-gear',
name: 'Cowboy Gear',
category: 'costume',
avatarMode: 'FULL_BODY',
animations: ['idle', 'walk', 'attack', 'hurt', 'celebrate'],
defaultAnimation: 'idle',
accessoryVisibility: {
eyewear: true,
headwear: true,
effects: true
},
layerOrder: ['avatar-body', 'costume-base', 'avatar-head', 'costume-overlay', 'earned-accessories'],
recommendedFor: ['action', 'adventure'],
description: 'Wild west cowboy',
avatarPosition: { x: 0.5, y: 0.5 },
avatarScale: 1.0,
props: [
{ id: 'lasso', anchor: 'rightArm', offset: { x: 10, y: 0 } }
],
contextEffects: {},
renderCostumeUnder: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
var vestColor = '#8b4513';
// Shirt
ctx.fillStyle = '#c19a6b';
roundRect(ctx, cx - 20 * scale, y + 64 * scale, 40 * scale, 45 * scale, 3 * scale);
ctx.fill();
// Shirt details (collar points)
ctx.fillStyle = '#a08060';
ctx.beginPath();
ctx.moveTo(cx - 8 * scale, y + 62 * scale);
ctx.lineTo(cx - 15 * scale, y + 75 * scale);
ctx.lineTo(cx - 5 * scale, y + 70 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 8 * scale, y + 62 * scale);
ctx.lineTo(cx + 15 * scale, y + 75 * scale);
ctx.lineTo(cx + 5 * scale, y + 70 * scale);
ctx.closePath();
ctx.fill();
// Vest
ctx.fillStyle = vestColor;
ctx.beginPath();
ctx.moveTo(cx - 20 * scale, y + 65 * scale);
ctx.lineTo(cx - 22 * scale, y + 108 * scale);
ctx.lineTo(cx - 8 * scale, y + 108 * scale);
ctx.lineTo(cx - 8 * scale, y + 75 * scale);
ctx.lineTo(cx - 12 * scale, y + 65 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 20 * scale, y + 65 * scale);
ctx.lineTo(cx + 22 * scale, y + 108 * scale);
ctx.lineTo(cx + 8 * scale, y + 108 * scale);
ctx.lineTo(cx + 8 * scale, y + 75 * scale);
ctx.lineTo(cx + 12 * scale, y + 65 * scale);
ctx.closePath();
ctx.fill();
// Belt
ctx.fillStyle = '#3d2314';
ctx.fillRect(cx - 20 * scale, y + 105 * scale, 40 * scale, 8 * scale);
// Belt buckle (large oval)
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.ellipse(cx, y + 109 * scale, 8 * scale, 5 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#b8860b';
ctx.beginPath();
ctx.ellipse(cx, y + 109 * scale, 5 * scale, 3 * scale, 0, 0, Math.PI * 2);
ctx.fill();
// Jeans
ctx.fillStyle = '#4a6fa5';
roundRect(ctx, cx - 16 * scale, y + 112 * scale, 32 * scale, 42 * scale, 3 * scale);
ctx.fill();
// Jean seams
ctx.strokeStyle = '#3d5a80';
ctx.lineWidth = 1 * scale;
ctx.beginPath();
ctx.moveTo(cx - 10 * scale, y + 115 * scale);
ctx.lineTo(cx - 10 * scale, y + 152 * scale);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx + 10 * scale, y + 115 * scale);
ctx.lineTo(cx + 10 * scale, y + 152 * scale);
ctx.stroke();
// Cowboy boots
ctx.fillStyle = '#5d4037';
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 150 * scale);
ctx.lineTo(cx - 20 * scale, y + 160 * scale);
ctx.lineTo(cx - 4 * scale, y + 160 * scale);
ctx.lineTo(cx - 4 * scale, y + 150 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 4 * scale, y + 150 * scale);
ctx.lineTo(cx + 4 * scale, y + 160 * scale);
ctx.lineTo(cx + 20 * scale, y + 160 * scale);
ctx.lineTo(cx + 18 * scale, y + 150 * scale);
ctx.closePath();
ctx.fill();
// Boot heel
ctx.fillStyle = '#3d2314';
ctx.fillRect(cx - 18 * scale, y + 158 * scale, 4 * scale, 4 * scale);
ctx.fillRect(cx + 14 * scale, y + 158 * scale, 4 * scale, 4 * scale);
// Boot top decoration
ctx.fillStyle = '#8b7355';
ctx.fillRect(cx - 17 * scale, y + 150 * scale, 12 * scale, 3 * scale);
ctx.fillRect(cx + 5 * scale, y + 150 * scale, 12 * scale, 3 * scale);
// Spurs
ctx.fillStyle = '#c0c0c0';
ctx.beginPath();
ctx.arc(cx - 19 * scale, y + 158 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + 19 * scale, y + 158 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
},
renderCostumeOver: function(ctx, avatar, x, y, scale, transforms, time) {
var cx = x + 32 * scale;
// Shirt sleeves
ctx.fillStyle = '#c19a6b';
roundRect(ctx, cx - 28 * scale, y + 66 * scale, 10 * scale, 40 * scale, 3 * scale);
ctx.fill();
roundRect(ctx, cx + 18 * scale, y + 66 * scale, 10 * scale, 40 * scale, 3 * scale);
ctx.fill();
// Bandana around neck
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.moveTo(cx - 15 * scale, y + 58 * scale);
ctx.quadraticCurveTo(cx, y + 65 * scale, cx + 15 * scale, y + 58 * scale);
ctx.lineTo(cx + 12 * scale, y + 62 * scale);
ctx.quadraticCurveTo(cx, y + 68 * scale, cx - 12 * scale, y + 62 * scale);
ctx.closePath();
ctx.fill();
// Bandana knot
ctx.beginPath();
ctx.arc(cx, y + 68 * scale, 4 * scale, 0, Math.PI * 2);
ctx.fill();
// Bandana tails
ctx.beginPath();
ctx.moveTo(cx - 2 * scale, y + 72 * scale);
ctx.quadraticCurveTo(cx - 5 * scale, y + 85 * scale, cx - 8 * scale + Math.sin(time * 2) * 2 * scale, y + 92 * scale);
ctx.lineTo(cx - 5 * scale + Math.sin(time * 2) * 2 * scale, y + 90 * scale);
ctx.quadraticCurveTo(cx - 3 * scale, y + 82 * scale, cx, y + 72 * scale);
ctx.fill();
ctx.beginPath();
ctx.moveTo(cx + 2 * scale, y + 72 * scale);
ctx.quadraticCurveTo(cx + 5 * scale, y + 85 * scale, cx + 8 * scale + Math.sin(time * 2 + 0.5) * 2 * scale, y + 92 * scale);
ctx.lineTo(cx + 5 * scale + Math.sin(time * 2 + 0.5) * 2 * scale, y + 90 * scale);
ctx.quadraticCurveTo(cx + 3 * scale, y + 82 * scale, cx, y + 72 * scale);
ctx.fill();
// Cowboy hat
var hatColor = '#8b4513';
// Brim
ctx.fillStyle = hatColor;
ctx.beginPath();
ctx.ellipse(cx, y + 8 * scale, 32 * scale, 8 * scale, 0, 0, Math.PI * 2);
ctx.fill();
// Crown
ctx.beginPath();
ctx.moveTo(cx - 18 * scale, y + 6 * scale);
ctx.quadraticCurveTo(cx - 20 * scale, y - 18 * scale, cx - 10 * scale, y - 22 * scale);
ctx.quadraticCurveTo(cx, y - 16 * scale, cx + 10 * scale, y - 22 * scale);
ctx.quadraticCurveTo(cx + 20 * scale, y - 18 * scale, cx + 18 * scale, y + 6 * scale);
ctx.closePath();
ctx.fill();
// Hat band
ctx.fillStyle = darken(hatColor, 0.3);
ctx.fillRect(cx - 18 * scale, y + 1 * scale, 36 * scale, 5 * scale);
// Hat band buckle
ctx.fillStyle = '#ffd700';
ctx.fillRect(cx - 4 * scale, y, 8 * scale, 7 * scale);
// Lasso in hand
var rightArm = getArmTransform(transforms, 'rightArm');
ctx.save();
ctx.translate(cx + 26 * scale, y + 108 * scale);
ctx.rotate((rightArm.rotation || 0) * Math.PI / 180);
// Coiled rope
ctx.strokeStyle = '#c19a6b';
ctx.lineWidth = 3 * scale;
ctx.beginPath();
for (var coil = 0; coil < 3; coil++) {
ctx.arc(0, -5 * scale + coil * 6 * scale, 8 * scale, 0, Math.PI * 1.8);
}
ctx.stroke();
// Rope end
ctx.beginPath();
ctx.moveTo(8 * scale, 10 * scale);
ctx.lineTo(15 * scale, 20 * scale);
ctx.stroke();
ctx.restore();
}
};
// ============================================================================
// CONTEXTS ARRAY
// ============================================================================
var CONTEXTS = [
knightArmor,
spaceSuit,
ninjaOutfit,
wizardRobes,
superheroCape,
athleteUniform,
pirateOutfit,
scientistLabcoat,
chefOutfit,
cowboyGear
];
// ============================================================================
// PUBLIC API
// ============================================================================
window.CostumeContexts = {
CONTEXTS: CONTEXTS,
getById: function(id) {
for (var i = 0; i < CONTEXTS.length; i++) {
if (CONTEXTS[i].id === id) {
return CONTEXTS[i];
}
}
return null;
},
getAll: function() {
return CONTEXTS.slice();
},
getRecommendedFor: function(gameType) {
var recommended = [];
for (var i = 0; i < CONTEXTS.length; i++) {
if (CONTEXTS[i].recommendedFor && CONTEXTS[i].recommendedFor.indexOf(gameType) !== -1) {
recommended.push(CONTEXTS[i]);
}
}
return recommended;
},
isAccessoryVisible: function(costumeId, accessoryType) {
var costume = this.getById(costumeId);
if (!costume || !costume.accessoryVisibility) {
return true;
}
return costume.accessoryVisibility[accessoryType] !== false;
}
};
})();