diff --git a/CHANGELOG.md b/CHANGELOG.md index e9c9271..d51a7db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Clean up corrupted Unicode Mojibake artifacts in media/sidebar.html (Fixes #28). - Update .vscodeignore to exclude internal scripts/tests/CI and add Content-Security-Policy to sidebar webview (Fixes #29). - Fix pet mood duration and unlock typing combo meter in Feed/Play commands (Fixes #23). +- Fix accessory positioning and eye-blink transform-origins for Cat and Robot pet species (Fixes #27). --- diff --git a/src/dashboard.js b/src/dashboard.js index 22f5868..8a3f294 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -88,7 +88,7 @@ function _updatePanel(statsEngine, petName, currentMood) { const petSpecies = config.get('petSpecies', 'alien'); const { getPetSvg, getAccessoriesSvg } = require('./pets'); - const petSvgRaw = getPetSvg(petSpecies).replace('__ACCESSORIES__', getAccessoriesSvg(stats.level)); + const petSvgRaw = getPetSvg(petSpecies).replace('__ACCESSORIES__', getAccessoriesSvg(stats.level, petSpecies)); const milestoneData = MILESTONES.map(m => ({ id: m.id, diff --git a/src/pets.js b/src/pets.js index 3eb294c..3ee3940 100644 --- a/src/pets.js +++ b/src/pets.js @@ -34,13 +34,13 @@ function getPetSvg(species) { - + - + @@ -99,11 +99,11 @@ function getPetSvg(species) { - + - + @@ -151,10 +151,10 @@ function getPetSvg(species) { - + - + @@ -222,11 +222,39 @@ function getPetSvg(species) { /** * Returns accessory SVGs based on level. */ -function getAccessoriesSvg(level) { +/** + * Returns accessory SVGs based on level and pet species. + * + * @param {number} level + * @param {string} [species='alien'] + * @returns {string} + */ +function getAccessoriesSvg(level, species = 'alien') { let svg = ''; // Level 5: Sunglasses if (level >= 5) { - svg += \` + if (species === 'robot') { + svg += ` + + + + + + + + `; + } else if (species === 'cat') { + svg += ` + + + + + + + + `; + } else { + svg += ` @@ -234,21 +262,61 @@ function getAccessoriesSvg(level) { - \`; + `; + } } // Level 10: Top Hat if (level >= 10 && level < 15) { - svg += \` + if (species === 'cat') { + svg += ` + + + + + + `; + } else if (species === 'robot') { + svg += ` + + + + + + `; + } else { + svg += ` - \`; + `; + } } // Level 15: Party Hat if (level >= 15 && level < 20) { - svg += \` + if (species === 'cat') { + svg += ` + + + + + + + + `; + } else if (species === 'robot') { + svg += ` + + + + + + + + `; + } else { + svg += ` @@ -256,11 +324,33 @@ function getAccessoriesSvg(level) { - \`; + `; + } } // Level 20: Crown if (level >= 20) { - svg += \` + if (species === 'cat') { + svg += ` + + + + + + + + `; + } else if (species === 'robot') { + svg += ` + + + + + + + + `; + } else { + svg += ` @@ -268,9 +358,9 @@ function getAccessoriesSvg(level) { - \`; + `; + } } - return svg; } diff --git a/src/sidebar.js b/src/sidebar.js index e13d768..5c543e8 100644 --- a/src/sidebar.js +++ b/src/sidebar.js @@ -74,7 +74,7 @@ class SidebarProvider { const currentMood = this.getCurrentMood(); const petName = this.getPetName(); - const petSvgRaw = getPetSvg(petSpecies).replace('__ACCESSORIES__', getAccessoriesSvg(stats.level)); + const petSvgRaw = getPetSvg(petSpecies).replace('__ACCESSORIES__', getAccessoriesSvg(stats.level, petSpecies)); const htmlPath = path.join(__dirname, '..', 'media', 'sidebar.html'); let html = 'Sidebar HTML not found.'; diff --git a/tests/pets.test.js b/tests/pets.test.js new file mode 100644 index 0000000..b565b5a --- /dev/null +++ b/tests/pets.test.js @@ -0,0 +1,50 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const test = require('node:test'); +const { getPetSvg, getAccessoriesSvg } = require('../src/pets'); + +test('Issue #27: getPetSvg applies species-specific eye-blink transform-origins', () => { + const alienSvg = getPetSvg('alien'); + assert.ok(alienSvg.includes('class="eye-l" style="transform-origin: 57px 55px"'), 'alien left eye origin'); + assert.ok(alienSvg.includes('class="eye-r" style="transform-origin: 83px 55px"'), 'alien right eye origin'); + + const catSvg = getPetSvg('cat'); + assert.ok(catSvg.includes('class="eye-l" style="transform-origin: 58px 60px"'), 'cat left eye origin'); + assert.ok(catSvg.includes('class="eye-r" style="transform-origin: 82px 60px"'), 'cat right eye origin'); + + const robotSvg = getPetSvg('robot'); + assert.ok(robotSvg.includes('class="eye-l" style="transform-origin: 58px 48px"'), 'robot left eye origin'); + assert.ok(robotSvg.includes('class="eye-r" style="transform-origin: 82px 48px"'), 'robot right eye origin'); +}); + +test('Issue #27: getAccessoriesSvg adapts coordinates based on pet species', () => { + // Level 5: Sunglasses + const robotSunglasses = getAccessoriesSvg(5, 'robot'); + assert.ok(robotSunglasses.includes('transform="translate(0, -10)"'), 'robot sunglasses shifted up to eye level'); + + const catSunglasses = getAccessoriesSvg(5, 'cat'); + assert.ok(catSunglasses.includes('transform="translate(0, 3)"'), 'cat sunglasses positioned to cat eye level'); + + const defaultSunglasses = getAccessoriesSvg(5); + assert.ok(defaultSunglasses.includes('acc-sunglasses'), 'default sunglasses rendered'); + assert.equal(defaultSunglasses.includes('transform='), false, 'default alien sunglasses do not need translate transform'); + + // Level 10: Top Hat + const catTopHat = getAccessoriesSvg(10, 'cat'); + assert.ok(catTopHat.includes('rect x="50" y="36" width="40"'), 'cat top hat sits between ears'); + + const robotTopHat = getAccessoriesSvg(10, 'robot'); + assert.ok(robotTopHat.includes('transform="translate(0, -5)"'), 'robot top hat positioned on head'); + + // Level 15: Party Hat + const catPartyHat = getAccessoriesSvg(15, 'cat'); + assert.ok(catPartyHat.includes('points="70,12 54,38 86,38"'), 'cat party hat base fitted between ears'); + + // Level 20: Crown + const catCrown = getAccessoriesSvg(20, 'cat'); + assert.ok(catCrown.includes('M 52 38 L 55 18'), 'cat crown fits between ears'); + + const robotCrown = getAccessoriesSvg(20, 'robot'); + assert.ok(robotCrown.includes('transform="translate(0, -8)"'), 'robot crown shifted for head antenna'); +});