Фон сцены.
@@ -762,6 +795,28 @@ function applyBgFX(bgId) {
d.style.cssText = `position:absolute;left:${rnd(8,88)}%;bottom:4%;width:${s}px;height:${s}px;border-radius:50%;background:${col};box-shadow:0 0 6px ${col};--ex:${ex}px;animation:bgEmber ${rnd(1.5,3)}s ${rnd(0,5)}s ease-out infinite`;
c.appendChild(d);
}
+ } else if (bgId === 'aurora') {
+ for (let i = 0; i < 20; i++) {
+ const d = document.createElement('div');
+ const s = rnd(0.8, 2.6);
+ d.style.cssText = `position:absolute;left:${rnd(3,95)}%;top:${rnd(3,72)}%;width:${s}px;height:${s}px;border-radius:50%;background:#aef7d8;box-shadow:0 0 4px #6ee7c0;animation:bgStarTwink ${rnd(1.2,3.5)}s ${rnd(0,4)}s ease-in-out infinite`;
+ c.appendChild(d);
+ }
+ } else if (bgId === 'candy') {
+ for (let i = 0; i < 11; i++) {
+ const d = document.createElement('div');
+ const s = rnd(5, 15);
+ const col = Math.random() > .5 ? 'rgba(255,150,200,.85)' : 'rgba(150,200,255,.85)';
+ d.style.cssText = `position:absolute;left:${rnd(8,88)}%;bottom:-16px;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 === 'sakura') {
+ for (let i = 0; i < 14; i++) {
+ const d = document.createElement('div');
+ const s = rnd(4, 8);
+ 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);
+ }
}
}
@@ -827,8 +882,11 @@ const BG_PREVIEWS = {
forest: 'radial-gradient(ellipse at 50% 100%,rgba(56,217,90,.8) 0%,transparent 55%),linear-gradient(155deg,#010701,#082007)',
aqua: 'radial-gradient(ellipse at 50% 100%,rgba(6,214,224,.8) 0%,transparent 55%),linear-gradient(155deg,#010810,#03203a)',
sunset: 'radial-gradient(ellipse at 50% 100%,rgba(249,100,20,.9) 0%,transparent 55%),linear-gradient(155deg,#100201,#1e0404)',
+ 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)',
};
-const BG_NAMES = { default:'Стандарт', space:'Космос', forest:'Лес', aqua:'Океан', sunset:'Закат' };
+const BG_NAMES = { default:'Стандарт', space:'Космос', forest:'Лес', aqua:'Океан', sunset:'Закат', aurora:'Сияние', candy:'Леденец', sakura:'Сакура' };
async function openPetShop() {
const data = await LS.api('/api/pet/shop').catch(() => null);
@@ -910,9 +968,10 @@ function setupCustomizeTabs() {
tabs.forEach(x => x.classList.remove('active'));
t.classList.add('active');
const which = t.dataset.tab;
- document.getElementById('pc-acc').style.display = which === 'acc' ? '' : 'none';
- document.getElementById('pc-color').style.display = which === 'color' ? '' : 'none';
- document.getElementById('pc-bg').style.display = which === 'bg' ? '' : 'none';
+ document.getElementById('pc-acc').style.display = which === 'acc' ? '' : 'none';
+ 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';
if (which === 'bg') renderBgPicker();
}));
}
@@ -998,7 +1057,7 @@ function renderPet(d) {
// Pet SVG (B3: pass streak for rainbow collar)
const wrap = document.getElementById('pet-svg-wrap');
wrap.className = `pet-svg-wrap pet-float mood-${d.mood}`;
- wrap.innerHTML = renderPetSVG(d.petLevel, d.mood, d.accessories, d.petColor || 'purple', d.streakCurrent || 0);
+ wrap.innerHTML = renderPetSVG(d.petLevel, d.mood, d.accessories, d.petColor || 'purple', d.streakCurrent || 0, d.petPattern || 'none');
// Scene mood class + weather + tod + B2 background
const scene = document.getElementById('pet-scene');
@@ -1048,8 +1107,9 @@ function renderPet(d) {
// Color picker
renderColorPicker(d.petColor || 'purple');
- // Гардероб (интерактивный выбор аксессуаров)
+ // Гардероб (интерактивный выбор аксессуаров) + узор
renderWardrobe(d.wardrobe || []);
+ renderPatternPicker(d.patterns || [], d.petPattern || 'none');
// Stats
document.getElementById('stat-streak').textContent = d.streakCurrent + ' дн.';
@@ -1190,53 +1250,95 @@ async function selectColor(colorKey) {
if (!res?.ok) return;
_petData.petColor = colorKey;
document.getElementById('pet-svg-wrap').innerHTML =
- renderPetSVG(_petData.petLevel, _petData.mood, _petData.accessories, colorKey);
+ renderPetSVG(_petData.petLevel, _petData.mood, _petData.accessories, colorKey, _petData.streakCurrent || 0, _petData.petPattern || 'none');
document.querySelectorAll('.pet-color-dot').forEach(d =>
d.classList.toggle('active', d.dataset.color === colorKey)
);
}
-/* ── Гардероб (выбор аксессуаров) ── */
+/* ── Гардероб (выбор аксессуаров, по зонам) ── */
const LOCK_ICO = '
';
+const ZONE_LABELS = { head:'Голова', face:'Лицо', neck:'Шея', ears:'Уши', accent:'Акцент' };
+function wearChip(it) {
+ const base = 'display:inline-flex;align-items:center;gap:5px;padding:6px 11px;border-radius:99px;font:600 .74rem Manrope,sans-serif;border:1.5px solid;transition:all .15s;user-select:none;';
+ const style = it.locked
+ ? base + 'border-color:rgba(255,255,255,.1);color:var(--text-3);background:transparent;cursor:not-allowed;opacity:.6'
+ : it.equipped
+ ? base + 'border-color:var(--violet);color:#fff;background:rgba(155,93,229,.22);cursor:pointer'
+ : base + 'border-color:var(--border-h);color:var(--text-2);background:transparent;cursor:pointer';
+ const title = it.locked ? `Откроется: ${it.hint}` : (it.equipped ? 'Снять' : 'Надеть');
+ const tail = it.locked && it.hint ? ` · ${it.hint}` : '';
+ return `
${it.locked?LOCK_ICO:''}${escHtml(it.name)}${tail}`;
+}
function renderWardrobe(items) {
const el = document.getElementById('pet-accessories');
if (!el) return;
- el.style.cssText = 'display:flex;flex-wrap:wrap;gap:7px';
+ el.style.cssText = '';
if (!items.length) { el.innerHTML = ''; return; }
- const base = 'display:inline-flex;align-items:center;gap:5px;padding:6px 11px;border-radius:99px;font:600 .74rem Manrope,sans-serif;border:1.5px solid;transition:all .15s;user-select:none;';
- el.innerHTML = items.map(it => {
- const style = it.locked
- ? base + 'border-color:rgba(255,255,255,.1);color:var(--text-3);background:transparent;cursor:not-allowed;opacity:.6'
- : it.equipped
- ? base + 'border-color:var(--violet);color:#fff;background:rgba(155,93,229,.22);cursor:pointer'
- : base + 'border-color:var(--border-h);color:var(--text-2);background:transparent;cursor:pointer';
- const title = it.locked ? `Откроется: ${it.hint}` : (it.equipped ? 'Снять' : 'Надеть');
- const tail = it.locked && it.hint ? ` · ${it.hint}` : '';
- return `
${it.locked?LOCK_ICO:''}${escHtml(it.name)}${tail}`;
- }).join('');
- el.querySelectorAll('.pet-wear.tgl').forEach(ch =>
- ch.addEventListener('click', () => toggleEquip(ch.dataset.id)));
+ const onCount = items.filter(i => i.equipped).length;
+ let html = `
Надето: ${onCount}
+
+
+
+
`;
+ ['head','face','neck','ears','accent'].forEach(z => {
+ const zi = items.filter(i => i.slot === z);
+ if (!zi.length) return;
+ html += `
${ZONE_LABELS[z]||z}
${zi.map(wearChip).join('')}
`;
+ });
+ el.innerHTML = html;
+ el.querySelectorAll('.pet-wear.tgl').forEach(ch => ch.addEventListener('click', () => toggleEquip(ch.dataset.id)));
+ document.getElementById('wr-clear')?.addEventListener('click', () => setEquipped([]));
+ document.getElementById('wr-random')?.addEventListener('click', randomLook);
}
-async function toggleEquip(id) {
+async function setEquipped(list) {
+ if (!_petData || !_petData.wardrobe) return;
+ const res = await LS.api('/api/pet/equip', { method:'PATCH', body: JSON.stringify({ equipped: list }) }).catch(() => null);
+ if (!res || !res.ok) return;
+ const final = res.equipped || list;
+ _petData.accessories = final;
+ _petData.wardrobe.forEach(w => { w.equipped = final.includes(w.id); });
+ document.getElementById('pet-svg-wrap').innerHTML =
+ renderPetSVG(_petData.petLevel, _petData.mood, final, _petData.petColor || 'purple', _petData.streakCurrent || 0, _petData.petPattern || 'none');
+ renderWardrobe(_petData.wardrobe);
+}
+function toggleEquip(id) {
if (!_petData || !_petData.wardrobe) return;
const item = _petData.wardrobe.find(w => w.id === id);
if (!item || item.locked) return;
const slotOf = {}; _petData.wardrobe.forEach(w => { slotOf[w.id] = w.slot; });
let eq = _petData.wardrobe.filter(w => w.equipped).map(w => w.id);
- if (item.equipped) {
- eq = eq.filter(x => x !== id); // снять
- } else {
- eq = eq.filter(x => slotOf[x] !== item.slot); // освободить слот
- eq.push(id); // надеть
- }
- const res = await LS.api('/api/pet/equip', { method:'PATCH', body: JSON.stringify({ equipped: eq }) }).catch(() => null);
+ if (item.equipped) eq = eq.filter(x => x !== id);
+ else { eq = eq.filter(x => slotOf[x] !== item.slot); eq.push(id); }
+ setEquipped(eq);
+}
+function randomLook() {
+ if (!_petData || !_petData.wardrobe) return;
+ const bySlot = {};
+ _petData.wardrobe.filter(w => !w.locked).forEach(w => { (bySlot[w.slot] = bySlot[w.slot] || []).push(w); });
+ const pick = [];
+ Object.values(bySlot).forEach(arr => { if (Math.random() < 0.7) pick.push(arr[Math.floor(Math.random()*arr.length)].id); });
+ setEquipped(pick);
+}
+
+/* ── Узор тела ── */
+function renderPatternPicker(list, current) {
+ const el = document.getElementById('pc-pattern-grid');
+ if (!el) return;
+ el.innerHTML = (list || []).map(p => {
+ const on = p.id === current;
+ return `
`;
+ }).join('');
+ el.querySelectorAll('.pc-swatch').forEach(b => b.addEventListener('click', () => applyPattern(b.dataset.pat)));
+}
+async function applyPattern(id) {
+ if (!_petData) return;
+ const res = await LS.api('/api/pet/pattern', { method:'PATCH', body: JSON.stringify({ pattern: id }) }).catch(() => null);
if (!res || !res.ok) return;
- const final = res.equipped || eq;
- _petData.accessories = final;
- _petData.wardrobe.forEach(w => { w.equipped = final.includes(w.id); });
+ _petData.petPattern = id;
document.getElementById('pet-svg-wrap').innerHTML =
- renderPetSVG(_petData.petLevel, _petData.mood, final, _petData.petColor || 'purple', _petData.streakCurrent || 0);
- renderWardrobe(_petData.wardrobe);
+ renderPetSVG(_petData.petLevel, _petData.mood, _petData.accessories, _petData.petColor || 'purple', _petData.streakCurrent || 0, id);
+ renderPatternPicker(_petData.patterns || [], id);
}
/* ── Petting ── */
@@ -1533,7 +1635,7 @@ function shadeColor(hex, pct) {
}
/* ── Pet SVG renderer ── */
-function renderPetSVG(level, mood, accessories = [], colorKey = 'purple', streak = 0) {
+function renderPetSVG(level, mood, accessories = [], colorKey = 'purple', streak = 0, pattern = 'none') {
const col = PET_PALETTES[colorKey] || '#9B5DE5';
const dark = shadeColor(col, -45);
const light = shadeColor(col, 52);
@@ -1658,6 +1760,26 @@ function renderPetSVG(level, mood, accessories = [], colorKey = 'purple', streak
/* ── Accessories (гардероб: строго по equipped-списку, без авто по уровню) ── */
let accSvg = '';
+ if (accessories.includes('party')) {
+ accSvg += `
+
+
+
`;
+ }
+ if (accessories.includes('sunglasses')) {
+ accSvg += `
+
+
+
+
`;
+ }
+ if (accessories.includes('scarf')) {
+ accSvg += `
+
`;
+ }
+ if (accessories.includes('flower')) {
+ accSvg += `
`;
+ }
if (accessories.includes('headphones')) {
accSvg += `
@@ -1804,6 +1926,17 @@ function renderPetSVG(level, mood, accessories = [], colorKey = 'purple', streak
shimmer = `
`;
}
+ // Узор тела (клипуется по силуэту)
+ let patternSvg = '';
+ if (pattern && pattern !== 'none') {
+ let pin = '';
+ if (pattern === 'spots') pin = `
`;
+ 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 = `
`;
+ patternSvg = `
${pin}`;
+ }
+
return `