diff --git a/backend/src/controllers/petController.js b/backend/src/controllers/petController.js index 171a416..fd57f03 100644 --- a/backend/src/controllers/petController.js +++ b/backend/src/controllers/petController.js @@ -40,6 +40,10 @@ const BG_SHOP = [ { id:'aurora', name:'Сияние', price:100, desc:'Северное сияние' }, { id:'candy', name:'Леденец', price:100, desc:'Сладкие облака' }, { id:'sakura', name:'Сакура', price:125, desc:'Цветущая вишня' }, + { id:'class', name:'Класс', price:75, desc:'Школьный класс' }, + { id:'lab', name:'Лаборатория', price:100, desc:'Химлаборатория' }, + { id:'winter', name:'Зима', price:100, desc:'Снежная ночь' }, + { id:'rainbow', name:'Радуга', price:125, desc:'Радужное небо' }, ]; function _parseOwned(raw) { @@ -73,16 +77,24 @@ const ACCESSORY_CATALOG = [ // голова { id: 'grad', name: 'Шапочка выпускника', slot: 'head', unlock: { type: 'free' } }, { id: 'party', name: 'Праздничный колпак', slot: 'head', unlock: { type: 'free' } }, + { id: 'beanie', name: 'Шапка-бини', slot: 'head', unlock: { type: 'free' } }, { id: 'hat', name: 'Цилиндр', slot: 'head', unlock: { type: 'streak', value: 7 } }, + { id: 'halo', name: 'Нимб', slot: 'head', unlock: { type: 'achievements', value: 8 } }, { id: 'crown', name: 'Корона', slot: 'head', unlock: { type: 'xp', value: 5000 } }, // лицо { id: 'glasses', name: 'Очки', slot: 'face', unlock: { type: 'level', value: 5 } }, { id: 'sunglasses', name: 'Тёмные очки', slot: 'face', unlock: { type: 'free' } }, + { id: 'monocle', name: 'Монокль', slot: 'face', unlock: { type: 'free' } }, // шея { id: 'bowtie', name: 'Бабочка', slot: 'neck', unlock: { type: 'free' } }, { id: 'scarf', name: 'Шарф', slot: 'neck', unlock: { type: 'free' } }, + { id: 'medal', name: 'Медаль', slot: 'neck', unlock: { type: 'tests', value: 30 } }, // уши { id: 'headphones', name: 'Наушники', slot: 'ears', unlock: { type: 'free' } }, + { id: 'earrings', name: 'Серёжки', slot: 'ears', unlock: { type: 'free' } }, + // в лапах + { id: 'wand', name: 'Волшебная палочка', slot: 'hands', unlock: { type: 'free' } }, + { id: 'balloon', name: 'Шарик', slot: 'hands', unlock: { type: 'free' } }, // акцент { id: 'flower', name: 'Цветок', slot: 'accent', unlock: { type: 'free' } }, { id: 'star', name: 'Звезда', slot: 'accent', unlock: { type: 'level', value: 10 } }, @@ -96,24 +108,38 @@ const PET_PATTERNS = [ { id: 'stripes', name: 'Полоски' }, { id: 'gradient', name: 'Градиент' }, { id: 'galaxy', name: 'Галактика' }, + { id: 'hearts', name: 'Сердечки' }, + { id: 'stars', name: 'Звёздочки' }, + { id: 'checker', name: 'Клетка' }, ]; const PATTERN_IDS = new Set(PET_PATTERNS.map(p => p.id)); -function _accUnlocked(u, a) { +function _accUnlocked(u, a, st) { const k = a.unlock; - if (k.type === 'free') return true; - if (k.type === 'xp') return (u.xp || 0) >= k.value; - if (k.type === 'level') return (u.level || 0) >= k.value; - if (k.type === 'streak') return (u.streak_best || 0) >= k.value; + if (k.type === 'free') return true; + if (k.type === 'xp') return (u.xp || 0) >= k.value; + if (k.type === 'level') return (u.level || 0) >= k.value; + if (k.type === 'streak') return (u.streak_best || 0) >= k.value; + if (k.type === 'tests') return (st && st.tests || 0) >= k.value; + if (k.type === 'achievements') return (st && st.achievements || 0) >= k.value; return false; } function _unlockHint(a) { const k = a.unlock; - if (k.type === 'xp') return `${k.value} XP`; - if (k.type === 'level') return `уровень ${k.value}`; - if (k.type === 'streak') return `серия ${k.value} дн.`; + if (k.type === 'xp') return `${k.value} XP`; + if (k.type === 'level') return `уровень ${k.value}`; + if (k.type === 'streak') return `серия ${k.value} дн.`; + if (k.type === 'tests') return `${k.value} тестов`; + if (k.type === 'achievements') return `${k.value} достижений`; return ''; } +// Статистика для разблокировки косметики за учёбу. +function _petStats(userId) { + let tests = 0, achievements = 0; + try { tests = db.prepare("SELECT COUNT(*) n FROM test_sessions WHERE user_id=? AND status='completed'").get(userId).n; } catch (e) {} + try { achievements = db.prepare("SELECT COUNT(*) n FROM user_achievements WHERE user_id=?").get(userId).n; } catch (e) {} + return { tests, achievements }; +} // Дефолт при первом заходе (pet_equipped == null) — повторяет прежнее авто-поведение. function _defaultEquipped(u) { const eq = []; @@ -179,7 +205,8 @@ function getPet(req, res) { const mood = _mood(user.streak_current, daysSince); // Гардероб: разблокированные + надетые (equipped). При первом заходе — дефолт. - const unlocked = ACCESSORY_CATALOG.filter(a => _accUnlocked(user, a)).map(a => a.id); + const stats = _petStats(req.user.id); + const unlocked = ACCESSORY_CATALOG.filter(a => _accUnlocked(user, a, stats)).map(a => a.id); let equipped; if (user.pet_equipped == null) equipped = _defaultEquipped(user); else { try { equipped = JSON.parse(user.pet_equipped) || []; } catch (e) { equipped = []; } } @@ -296,7 +323,7 @@ function renamePet(req, res) { /* ── PATCH /api/pet/color ─────────────────────────────────────────────── */ function updateColor(req, res) { const { color } = req.body; - const VALID = ['purple', 'cyan', 'gold', 'red', 'green', 'blue']; + const VALID = ['purple', 'cyan', 'gold', 'red', 'green', 'blue', 'pink', 'orange', 'teal', 'lime', 'indigo']; if (!VALID.includes(color)) return res.status(400).json({ error: 'invalid color' }); db.prepare('UPDATE users SET pet_color = ? WHERE id = ?').run(color, req.user.id); res.json({ ok: true, color }); @@ -316,11 +343,12 @@ function equipAccessories(req, res) { if (!Array.isArray(list)) return res.status(400).json({ error: 'equipped[] required' }); const user = db.prepare('SELECT xp, level, streak_best FROM users WHERE id=?').get(req.user.id); if (!user) return res.status(404).json({ error: 'not found' }); + const stats = _petStats(req.user.id); // оставляем только существующие, разблокированные, по одному на слот const seenSlot = new Set(), clean = []; for (const id of list) { const a = ACC_BY_ID.get(id); - if (!a || !_accUnlocked(user, a) || seenSlot.has(a.slot)) continue; + if (!a || !_accUnlocked(user, a, stats) || seenSlot.has(a.slot)) continue; seenSlot.add(a.slot); clean.push(id); } db.prepare('UPDATE users SET pet_equipped=? WHERE id=?').run(JSON.stringify(clean), req.user.id); diff --git a/frontend/js/pet-sprite.js b/frontend/js/pet-sprite.js index 7264c49..359d1f7 100644 --- a/frontend/js/pet-sprite.js +++ b/frontend/js/pet-sprite.js @@ -8,6 +8,8 @@ const PET_PALETTES = { purple:'#9B5DE5', cyan:'#06D6E0', gold:'#F9C74F', red:'#F94144', green:'#38D95A', blue:'#4A90D9', + pink:'#F15BB5', orange:'#FB8B24', teal:'#0CA5B8', + lime:'#7FB800', indigo:'#5E60CE', }; function shadeColor(hex, pct) { @@ -163,6 +165,38 @@ if (accessories.includes('flower')) { accSvg += ``; } + if (accessories.includes('beanie')) { + accSvg += ` + + `; + } + if (accessories.includes('halo')) { + accSvg += ` + `; + } + if (accessories.includes('monocle')) { + accSvg += ` + `; + } + if (accessories.includes('medal')) { + accSvg += ` + + `; + } + if (accessories.includes('earrings')) { + accSvg += ` + `; + } + if (accessories.includes('wand')) { + accSvg += ` + `; + } + if (accessories.includes('balloon')) { + accSvg += ` + + + `; + } if (accessories.includes('headphones')) { accSvg += ` @@ -317,6 +351,9 @@ else if (pattern === 'stripes') pin = `${[-10,8,26,44,62,80,98].map(x => ``).join('')}`; else if (pattern === 'galaxy') pin = ``; else if (pattern === 'gradient') pin = ``; + else if (pattern === 'hearts') pin = [[40,44],[68,40],[57,70],[36,73],[76,63],[52,33]].map(p => ``).join(''); + else if (pattern === 'stars') pin = [[40,44],[68,40],[57,70],[36,73],[76,63],[52,33]].map(p => ``).join(''); + else if (pattern === 'checker') pin = Array.from({ length: 42 }, (_, i) => { const c = i % 6, r = (i / 6) | 0; return ((r + c) % 2) ? '' : ``; }).join(''); patternSvg = `${pin}`; } diff --git a/frontend/pet.html b/frontend/pet.html index 16024e7..bb558c1 100644 --- a/frontend/pet.html +++ b/frontend/pet.html @@ -289,6 +289,10 @@ .pet-scene.bg-aurora { background:radial-gradient(ellipse at 30% 25%,rgba(56,217,140,.6) 0%,transparent 42%),radial-gradient(ellipse at 70% 30%,rgba(120,90,255,.55) 0%,transparent 44%),radial-gradient(ellipse at 50% 92%,rgba(6,214,180,.4) 0%,transparent 52%),linear-gradient(170deg,#02040f,#04122a,#0a0426) !important; } .pet-scene.bg-candy { background:radial-gradient(ellipse at 30% 25%,rgba(255,170,210,.7) 0%,transparent 46%),radial-gradient(ellipse at 75% 70%,rgba(150,200,255,.6) 0%,transparent 46%),linear-gradient(170deg,#3a2540,#52324f,#3a2848) !important; } .pet-scene.bg-sakura { background:radial-gradient(ellipse at 50% 100%,rgba(255,140,190,.7) 0%,transparent 52%),radial-gradient(ellipse at 25% 30%,rgba(255,180,210,.45) 0%,transparent 42%),linear-gradient(170deg,#1a0a14,#2e1226,#241026) !important; } + .pet-scene.bg-class { background:radial-gradient(ellipse at 50% 16%,rgba(255,220,150,.32) 0%,transparent 46%),radial-gradient(ellipse at 50% 100%,rgba(56,140,90,.45) 0%,transparent 52%),linear-gradient(170deg,#16241c,#1e3327,#16261d) !important; } + .pet-scene.bg-lab { background:radial-gradient(ellipse at 30% 28%,rgba(6,214,224,.45) 0%,transparent 44%),radial-gradient(ellipse at 72% 72%,rgba(56,217,90,.35) 0%,transparent 44%),linear-gradient(170deg,#06121e,#0a1c2c,#08202f) !important; } + .pet-scene.bg-winter { background:radial-gradient(ellipse at 50% 100%,rgba(180,220,255,.5) 0%,transparent 55%),radial-gradient(ellipse at 30% 25%,rgba(220,235,255,.35) 0%,transparent 42%),linear-gradient(170deg,#0a1430,#13243f,#1a2c4a) !important; } + .pet-scene.bg-rainbow{ background:linear-gradient(160deg,rgba(249,65,68,.34),rgba(249,199,79,.34) 28%,rgba(56,217,90,.32) 52%,rgba(6,214,224,.34) 74%,rgba(155,93,229,.36)),#0e1020 !important; } /* B2 — BgFX particle container + keyframes */ .pet-bgfx { position:absolute; inset:0; pointer-events:none; overflow:hidden; border-radius:50%; z-index:1; } @@ -368,9 +372,9 @@ .pc-head-ico svg { width:17px; height:17px; } .pc-head-title { font-family:'Unbounded',sans-serif; font-size:.92rem; font-weight:800; line-height:1.1; } .pc-head-sub { font-size:.68rem; color:var(--text-3); margin-top:2px; } - .pc-tabs { display:flex; gap:4px; padding:4px; background:rgba(15,23,42,.06); border-radius:13px; margin-bottom:16px; } - .pc-tab { flex:1; display:inline-flex; align-items:center; justify-content:center; gap:6px; padding:9px 6px; border:none; - border-radius:9px; background:transparent; color:var(--text-2); font:700 .76rem 'Manrope',sans-serif; cursor:pointer; transition:all .18s; } + .pc-tabs { display:flex; flex-wrap:wrap; gap:4px; padding:4px; background:rgba(15,23,42,.06); border-radius:13px; margin-bottom:16px; } + .pc-tab { flex:1 1 84px; display:inline-flex; align-items:center; justify-content:center; gap:5px; padding:9px 5px; border:none; + border-radius:9px; background:transparent; color:var(--text-2); font:700 .74rem 'Manrope',sans-serif; cursor:pointer; transition:all .18s; white-space:nowrap; } .pc-tab svg { width:14px; height:14px; } .pc-tab:hover { color:var(--text); background:rgba(15,23,42,.05); } .pc-tab.active { background:linear-gradient(135deg,#9B5DE5,#7b4fd0); color:#fff; box-shadow:0 5px 16px rgba(155,93,229,.45); } @@ -426,6 +430,9 @@ .pc-swatch-dot.pat-stripes { background:repeating-linear-gradient(45deg,#9B5DE5 0 5px,#5a2da0 5px 9px); } .pc-swatch-dot.pat-gradient{ background:linear-gradient(180deg,#c9a6ec,#4a2398); } .pc-swatch-dot.pat-galaxy { background:radial-gradient(circle at 62% 38%,#1a0a3a 50%,#9B5DE5); } + .pc-swatch-dot.pat-hearts { background:radial-gradient(circle at 35% 40%,#5a2da0 2.5px,transparent 2.7px),radial-gradient(circle at 68% 62%,#5a2da0 2.5px,transparent 2.7px),#9B5DE5; } + .pc-swatch-dot.pat-stars { background:radial-gradient(circle at 38% 38%,#fff 1.8px,transparent 2px),radial-gradient(circle at 66% 66%,#fff 1.8px,transparent 2px),#9B5DE5; } + .pc-swatch-dot.pat-checker { background:repeating-conic-gradient(#9B5DE5 0% 25%,#5a2da0 0% 50%) 50% / 16px 16px; } /* Фон — превью-карточки крупнее */ .pc-bg-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(124px,1fr)); gap:11px; } @@ -434,6 +441,14 @@ .pc-bg-grid .pet-bg-card.active { box-shadow:0 0 0 2px var(--violet), 0 8px 22px rgba(155,93,229,.4); } .pc-bg-grid .pet-bg-preview { height:78px; } + /* Образы (наборы) */ + .pc-sets-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:10px; } + .pc-set { display:flex; flex-direction:column; gap:3px; align-items:flex-start; padding:12px 14px; border-radius:14px; + border:1.5px solid var(--border-h); background:rgba(15,23,42,.03); cursor:pointer; transition:all .16s; text-align:left; } + .pc-set:hover { border-color:var(--violet); transform:translateY(-2px); box-shadow:0 6px 18px rgba(155,93,229,.2); } + .pc-set-name { font:800 .84rem 'Unbounded',sans-serif; color:var(--text); } + .pc-set-sub { font-size:.66rem; color:var(--text-3); } + @media(max-width:768px) { .pet-hero { grid-template-columns:1fr; } .pet-bottom { grid-template-columns:1fr; } @@ -666,6 +681,7 @@ +
Нажми, чтобы надеть или снять. Заблокированные открываются за достижения. По одному предмету на зону.
@@ -683,6 +699,10 @@
Фон сцены.
+ @@ -713,10 +733,13 @@ const EVO_STAGES = ['','Яйцо','Малыш','Подросток','Взрос const PET_PALETTES = { purple:'#9B5DE5', cyan:'#06D6E0', gold:'#F9C74F', red:'#F94144', green:'#38D95A', blue:'#4A90D9', + pink:'#F15BB5', orange:'#FB8B24', teal:'#0CA5B8', + lime:'#7FB800', indigo:'#5E60CE', }; const PALETTE_LABELS = { purple:'Фиолетовый', cyan:'Голубой', gold:'Золотой', red:'Красный', green:'Зелёный', blue:'Синий', + pink:'Розовый', orange:'Оранжевый', teal:'Бирюзовый', lime:'Лаймовый', indigo:'Индиго', }; const MOOD_LABELS = { ecstatic:['Восторг!', 'mood-ecstatic-badge'], @@ -922,6 +945,36 @@ function applyBgFX(bgId) { d.style.cssText = `position:absolute;left:${rnd(2,94)}%;top:-12px;width:${s}px;height:${(s*.8).toFixed(1)}px;border-radius:60% 0 60% 0;background:rgba(255,160,200,.85);--px:${rndI(-18,18)}px;animation:bgPetal ${rnd(3,6)}s ${rnd(0,5)}s linear infinite`; c.appendChild(d); } + } else if (bgId === 'class') { + for (let i = 0; i < 12; i++) { + const d = document.createElement('div'); + const s = rnd(1.5, 3); + d.style.cssText = `position:absolute;left:${rnd(5,92)}%;top:${rnd(5,80)}%;width:${s}px;height:${s}px;border-radius:50%;background:rgba(255,230,170,.8);animation:bgStarTwink ${rnd(2,4)}s ${rnd(0,4)}s ease-in-out infinite`; + c.appendChild(d); + } + } else if (bgId === 'lab') { + for (let i = 0; i < 11; i++) { + const d = document.createElement('div'); + const s = rnd(4, 13); + const col = Math.random() > .5 ? 'rgba(6,214,224,.8)' : 'rgba(56,217,90,.75)'; + d.style.cssText = `position:absolute;left:${rnd(8,88)}%;bottom:-15px;width:${s}px;height:${s}px;border-radius:50%;border:1.5px solid ${col};animation:bgBubble ${rnd(2.5,5)}s ${rnd(0,6)}s ease-out infinite`; + c.appendChild(d); + } + } else if (bgId === 'winter') { + for (let i = 0; i < 18; i++) { + const d = document.createElement('div'); + const s = rnd(2.5, 5); + d.style.cssText = `position:absolute;left:${rnd(2,95)}%;top:-10px;width:${s}px;height:${s}px;border-radius:50%;background:rgba(255,255,255,.9);--px:${rndI(-14,14)}px;animation:bgPetal ${rnd(3.5,7)}s ${rnd(0,5)}s linear infinite`; + c.appendChild(d); + } + } else if (bgId === 'rainbow') { + const cols = ['#F94144','#F9C74F','#38D95A','#06D6E0','#9B5DE5']; + for (let i = 0; i < 16; i++) { + const d = document.createElement('div'); + const s = rnd(2, 4); + d.style.cssText = `position:absolute;left:${rnd(4,93)}%;top:${rnd(4,86)}%;width:${s}px;height:${s}px;border-radius:50%;background:${cols[i % cols.length]};box-shadow:0 0 5px ${cols[i % cols.length]};animation:bgStarTwink ${rnd(1.3,3.2)}s ${rnd(0,4)}s ease-in-out infinite`; + c.appendChild(d); + } } } @@ -990,8 +1043,12 @@ const BG_PREVIEWS = { aurora: 'radial-gradient(ellipse at 35% 25%,rgba(56,217,140,.85) 0%,transparent 50%),radial-gradient(ellipse at 70% 35%,rgba(120,90,255,.7) 0%,transparent 50%),linear-gradient(155deg,#02040f,#0a0426)', candy: 'radial-gradient(ellipse at 30% 25%,rgba(255,170,210,.9) 0%,transparent 55%),radial-gradient(ellipse at 75% 75%,rgba(150,200,255,.8) 0%,transparent 55%),linear-gradient(155deg,#3a2540,#3a2848)', sakura: 'radial-gradient(ellipse at 50% 100%,rgba(255,140,190,.9) 0%,transparent 55%),linear-gradient(155deg,#1a0a14,#241026)', + class: 'radial-gradient(ellipse at 50% 18%,rgba(255,220,150,.5) 0%,transparent 50%),linear-gradient(155deg,#16241c,#16261d)', + lab: 'radial-gradient(ellipse at 32% 30%,rgba(6,214,224,.75) 0%,transparent 50%),radial-gradient(ellipse at 72% 72%,rgba(56,217,90,.6) 0%,transparent 50%),linear-gradient(155deg,#06121e,#08202f)', + winter: 'radial-gradient(ellipse at 50% 100%,rgba(180,220,255,.85) 0%,transparent 55%),linear-gradient(155deg,#0a1430,#1a2c4a)', + rainbow: 'linear-gradient(160deg,#f94144,#f9c74f 30%,#38d95a 55%,#06d6e0 78%,#9b5de5)', }; -const BG_NAMES = { default:'Стандарт', space:'Космос', forest:'Лес', aqua:'Океан', sunset:'Закат', aurora:'Сияние', candy:'Леденец', sakura:'Сакура' }; +const BG_NAMES = { default:'Стандарт', space:'Космос', forest:'Лес', aqua:'Океан', sunset:'Закат', aurora:'Сияние', candy:'Леденец', sakura:'Сакура', class:'Класс', lab:'Лаборатория', winter:'Зима', rainbow:'Радуга' }; async function openPetShop() { const data = await LS.api('/api/pet/shop').catch(() => null); @@ -1077,7 +1134,9 @@ function setupCustomizeTabs() { document.getElementById('pc-color').style.display = which === 'color' ? '' : 'none'; document.getElementById('pc-pattern').style.display = which === 'pattern' ? '' : 'none'; document.getElementById('pc-bg').style.display = which === 'bg' ? '' : 'none'; + document.getElementById('pc-sets').style.display = which === 'sets' ? '' : 'none'; if (which === 'bg') renderBgPicker(); + if (which === 'sets') renderSets(); })); const ov = document.getElementById('wardrobe-modal'); if (ov) ov.addEventListener('click', e => { if (e.target === ov) closeWardrobe(); }); @@ -1378,7 +1437,7 @@ const LOCK_ICO = '${ICO_SHUFFLE}Случайный образ `; - ['head','face','neck','ears','accent'].forEach(z => { + ['head','face','neck','ears','hands','accent'].forEach(z => { const zi = items.filter(i => i.slot === z); if (!zi.length) return; html += `
${ZONE_LABELS[z]||z}
${zi.map(wearChip).join('')}
`; @@ -1482,6 +1541,29 @@ async function applyPattern(id) { renderPatternPicker(_petData.patterns || [], id); } +/* ── Готовые образы (наборы) ── */ +const OUTFIT_SETS = [ + { id:'scientist', name:'Учёный', acc:['grad','glasses','medal'], pattern:'none', color:'cyan' }, + { id:'wizard', name:'Волшебник', acc:['party','wand'], pattern:'galaxy', color:'indigo' }, + { id:'champion', name:'Чемпион', acc:['crown','star','medal'], pattern:'gradient', color:'gold' }, + { id:'cutie', name:'Милашка', acc:['flower','bowtie','balloon'],pattern:'hearts', color:'pink' }, +]; +function renderSets() { + const el = document.getElementById('pc-sets-grid'); if (!el) return; + el.innerHTML = OUTFIT_SETS.map(s => ``).join(''); + el.querySelectorAll('.pc-set').forEach(b => b.addEventListener('click', () => applySet(b.dataset.set))); +} +async function applySet(id) { + const set = OUTFIT_SETS.find(s => s.id === id); if (!set || !_petData) return; + const unlocked = new Set((_petData.wardrobe || []).filter(w => !w.locked).map(w => w.id)); + await setEquipped(set.acc.filter(a => unlocked.has(a))); + if (set.color && _petData.petColor !== set.color) await selectColor(set.color); + if (set.pattern && _petData.petPattern !== set.pattern) await applyPattern(set.pattern); + LS.toast?.('Образ «' + set.name + '» применён', 'success'); +} + /* ── Petting ── */ async function petThePet() { if (_petCooldown) return; @@ -1921,6 +2003,38 @@ function renderPetSVG(level, mood, accessories = [], colorKey = 'purple', streak if (accessories.includes('flower')) { accSvg += ``; } + if (accessories.includes('beanie')) { + accSvg += ` + + `; + } + if (accessories.includes('halo')) { + accSvg += ` + `; + } + if (accessories.includes('monocle')) { + accSvg += ` + `; + } + if (accessories.includes('medal')) { + accSvg += ` + + `; + } + if (accessories.includes('earrings')) { + accSvg += ` + `; + } + if (accessories.includes('wand')) { + accSvg += ` + `; + } + if (accessories.includes('balloon')) { + accSvg += ` + + + `; + } if (accessories.includes('headphones')) { accSvg += ` @@ -2075,6 +2189,9 @@ function renderPetSVG(level, mood, accessories = [], colorKey = 'purple', streak else if (pattern === 'stripes') pin = `${[-10,8,26,44,62,80,98].map(x => ``).join('')}`; else if (pattern === 'galaxy') pin = ``; else if (pattern === 'gradient') pin = ``; + else if (pattern === 'hearts') pin = [[40,44],[68,40],[57,70],[36,73],[76,63],[52,33]].map(p => ``).join(''); + else if (pattern === 'stars') pin = [[40,44],[68,40],[57,70],[36,73],[76,63],[52,33]].map(p => ``).join(''); + else if (pattern === 'checker') pin = Array.from({ length: 42 }, (_, i) => { const c = i % 6, r = (i / 6) | 0; return ((r + c) % 2) ? '' : ``; }).join(''); patternSvg = `${pin}`; }