feat(pet): единый UI кастомизации (аксессуары/цвет/фон) + пояснение эволюции
Вынес кастомизацию из тесного блока сцены в отдельную карточку с вкладками «Аксессуары · Цвет · Фон». Фон теперь выбирается инлайн (сетка превью с ценой/статусом, покупка/выбор за монеты) вместо незаметной кнопки-модалки. Добавил легенду эволюции: облик (уши/антенны/крылья/аура…) растёт с XP по уровням — объясняет «откуда крылья». Рендереры цвета/гардероба не тронуты (те же id), бэкенд без изменений. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+92
-12
@@ -315,6 +315,20 @@
|
||||
/* B3 — Rainbow collar keyframe */
|
||||
@keyframes rbRot { to { transform:rotate(360deg); } }
|
||||
|
||||
/* ── Customize panel (аксессуары / цвет / фон) ── */
|
||||
.pet-customize { margin-bottom:18px; }
|
||||
.pc-tabs { display:flex; gap:6px; margin:4px 0 14px; flex-wrap:wrap; }
|
||||
.pc-tab { padding:7px 16px; border-radius:99px; border:1.5px solid var(--border-h); background:transparent;
|
||||
color:var(--text-2); font:700 .78rem 'Manrope',sans-serif; cursor:pointer; transition:all .15s; }
|
||||
.pc-tab:hover { border-color:var(--violet); color:var(--text); }
|
||||
.pc-tab.active { background:var(--violet); border-color:var(--violet); color:#fff; }
|
||||
.pc-hint { font-size:.72rem; color:var(--text-3); margin-bottom:11px; line-height:1.5; }
|
||||
.pc-panel .pet-color-picker { display:flex; gap:9px; flex-wrap:wrap; }
|
||||
.pc-panel .pet-color-dot { width:30px; height:30px; }
|
||||
.pc-bg-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(118px,1fr)); gap:10px; }
|
||||
.pet-evo-legend { font-size:.6rem; color:var(--text-3); margin-top:7px; line-height:1.45; text-align:center;
|
||||
cursor:help; max-width:230px; }
|
||||
|
||||
@media(max-width:768px) {
|
||||
.pet-hero { grid-template-columns:1fr; }
|
||||
.pet-bottom { grid-template-columns:1fr; }
|
||||
@@ -385,20 +399,9 @@
|
||||
<div class="pet-evo-lbl">Эволюция</div>
|
||||
<div class="pet-evo-name" id="pet-evo-name">—</div>
|
||||
<div class="pet-lvl-dots" id="pet-lvl-dots"></div>
|
||||
<div class="pet-evo-legend" title="Облик растёт сам с уровнем (за XP): Ур.2 — уши, Ур.3 — антенны, Ур.4 — крылья и аура, Ур.5 — корона-сияние и орбиты, Ур.6 — вторая аура, Ур.7 — нимб и вторые крылья, Ур.8 — золотое сияние.">облик растёт с XP — наведи, чтобы узнать, что добавляется на каждом уровне</div>
|
||||
</div>
|
||||
|
||||
<!-- Color picker + shop -->
|
||||
<div class="pet-color-row">
|
||||
<div class="pet-color-lbl">Цвет</div>
|
||||
<div class="pet-color-picker" id="color-picker"></div>
|
||||
<button class="pet-btn" onclick="openPetShop()" style="font-size:.62rem;padding:3px 9px;margin-left:2px;display:inline-flex;align-items:center;gap:4px" title="Фоны сцены">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"/><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"/><path d="M2 7h20"/><path d="M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7"/></svg>
|
||||
Фоны
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pet-accessories" id="pet-accessories"></div>
|
||||
|
||||
<button class="pet-action-btn" id="btn-pet" onclick="petThePet()">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 11V6a2 2 0 0 0-4 0v0"/><path d="M14 10V4a2 2 0 0 0-4 0v2"/><path d="M10 10.5V6a2 2 0 0 0-4 0v8"/><path d="M6 14v0a2 2 0 0 0-2-2H2v5l6.5 6.5a2 2 0 0 0 2.8 0l3.7-3.7a2 2 0 0 0 0-2.8L18 18"/></svg>
|
||||
Погладить
|
||||
@@ -476,6 +479,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Кастомизация ── -->
|
||||
<div class="pet-card pet-customize">
|
||||
<div class="pet-card-title"><i data-lucide="palette" width="12" height="12"></i> Кастомизация</div>
|
||||
<div class="pc-tabs">
|
||||
<button class="pc-tab active" data-tab="acc" type="button">Аксессуары</button>
|
||||
<button class="pc-tab" data-tab="color" type="button">Цвет</button>
|
||||
<button class="pc-tab" data-tab="bg" type="button">Фон</button>
|
||||
</div>
|
||||
<div class="pc-panel" id="pc-acc">
|
||||
<div class="pc-hint">Нажми, чтобы надеть или снять. Заблокированные открываются за достижения. По одному предмету на зону (голова / лицо / шея / уши / акцент).</div>
|
||||
<div class="pet-accessories" id="pet-accessories"></div>
|
||||
</div>
|
||||
<div class="pc-panel" id="pc-color" style="display:none">
|
||||
<div class="pc-hint">Основной цвет питомца.</div>
|
||||
<div class="pet-color-picker" id="color-picker"></div>
|
||||
</div>
|
||||
<div class="pc-panel" id="pc-bg" style="display:none">
|
||||
<div class="pc-hint">Фон сцены. <span id="pc-bg-coins"></span></div>
|
||||
<div class="pc-bg-grid" id="pc-bg-grid"><div style="color:var(--text-2);font-size:.8rem">Загрузка…</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Bottom ── -->
|
||||
<div class="pet-bottom">
|
||||
|
||||
@@ -632,6 +657,7 @@ let _petCooldownTimer = null;
|
||||
|
||||
applyTimeOfDay();
|
||||
scheduleNextStar();
|
||||
setupCustomizeTabs();
|
||||
loadPet();
|
||||
})();
|
||||
|
||||
@@ -877,6 +903,60 @@ async function selectBg(id, price, owned, card) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Кастомизация: вкладки ── */
|
||||
function setupCustomizeTabs() {
|
||||
const tabs = document.querySelectorAll('.pc-tab');
|
||||
tabs.forEach(t => t.addEventListener('click', () => {
|
||||
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';
|
||||
if (which === 'bg') renderBgPicker();
|
||||
}));
|
||||
}
|
||||
|
||||
/* ── Фоны (инлайн-выбор во вкладке) ── */
|
||||
async function renderBgPicker() {
|
||||
const grid = document.getElementById('pc-bg-grid');
|
||||
if (!grid) return;
|
||||
const data = await LS.api('/api/pet/shop').catch(() => null);
|
||||
if (!data) { grid.innerHTML = '<div style="color:var(--text-2);font-size:.8rem">Не удалось загрузить</div>'; return; }
|
||||
const coinsEl = document.getElementById('pc-bg-coins');
|
||||
if (coinsEl) coinsEl.innerHTML = `Монет: <b style="color:#F9C74F">${data.coins}</b>`;
|
||||
const items = [{ id: 'default', name: 'Стандарт', price: 0, owned: true }, ...data.items];
|
||||
grid.innerHTML = items.map(item => {
|
||||
const isActive = data.currentBg === item.id;
|
||||
const isOwned = item.owned || item.price === 0;
|
||||
const status = isActive ? 'Активен' : isOwned ? 'Выбрать' : item.price + ' монет';
|
||||
return `<div class="pet-bg-card${isActive ? ' active' : ''}" data-id="${item.id}" data-price="${item.price || 0}" data-owned="${isOwned ? 1 : 0}">
|
||||
<div class="pet-bg-preview" style="background:${BG_PREVIEWS[item.id] || BG_PREVIEWS.default}"></div>
|
||||
<div class="pet-bg-info"><div class="pet-bg-name">${escHtml(item.name)}</div>
|
||||
<div class="pet-bg-status">${status}</div></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
grid.querySelectorAll('.pet-bg-card').forEach(c =>
|
||||
c.addEventListener('click', () => selectBgInline(c.dataset.id, +c.dataset.price, c.dataset.owned === '1')));
|
||||
}
|
||||
async function selectBgInline(id, price, owned) {
|
||||
const endpoint = owned ? '/api/pet/bg' : '/api/pet/shop/buy';
|
||||
const res = await LS.api(endpoint, { method: owned ? 'PATCH' : 'POST', body: JSON.stringify({ id }) }).catch(e => {
|
||||
if (e?.data?.error === 'insufficient_coins') LS.toast?.('Недостаточно монет', 'error');
|
||||
return null;
|
||||
});
|
||||
if (!res?.ok) return;
|
||||
const scene = document.getElementById('pet-scene');
|
||||
scene.className = scene.className.replace(/\bbg-\S+/g, '') + (id !== 'default' ? ` bg-${id}` : '');
|
||||
if (_petData) _petData.petBg = id;
|
||||
applyBgFX(id);
|
||||
if (res.coins !== undefined) {
|
||||
document.getElementById('stat-coins').textContent = res.coins;
|
||||
if (_petData) _petData.coins = res.coins;
|
||||
}
|
||||
renderBgPicker();
|
||||
}
|
||||
|
||||
/* ── Eye tracking + A2 Parallax ── */
|
||||
function onStageMouse(e) {
|
||||
const wrap = document.getElementById('pet-svg-wrap');
|
||||
|
||||
Reference in New Issue
Block a user