feat(pet): кастомизация вынесена в модалку-«гардеробную» с живым превью
Кнопка «Нарядить» на сцене открывает модалку: слева крупное живое превью питомца (обновляется мгновенно при любой смене — аксессуар/цвет/узор/фон), справа вкладки Аксессуары/Цвет/Узор/Фон. Превью-сцена отражает выбранный фон. Закрытие крестиком, кликом по фону, Esc. Инлайн-карточка убрана со страницы; все рендереры идут через общий paintPet (сцена + превью). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+111
-40
@@ -319,7 +319,31 @@
|
||||
/* B3 — Rainbow collar keyframe */
|
||||
@keyframes rbRot { to { transform:rotate(360deg); } }
|
||||
|
||||
/* ── Customize panel (полностью переработанный вид) ── */
|
||||
/* ── Гардеробная (модалка) ── */
|
||||
.wr-modal-overlay { position:fixed; inset:0; z-index:1100; display:flex; align-items:center; justify-content:center;
|
||||
background:rgba(0,0,0,.66); backdrop-filter:blur(6px); padding:20px; animation:pcFade .2s ease; }
|
||||
.wr-modal { width:780px; max-width:96vw; max-height:90vh; overflow:auto; background:var(--surface);
|
||||
border:1.5px solid rgba(155,93,229,.22); border-radius:22px; box-shadow:0 30px 90px rgba(0,0,0,.55); padding:20px 22px 24px; }
|
||||
.wr-modal-head { display:flex; align-items:center; gap:11px; margin-bottom:18px; }
|
||||
.wr-modal-titles { flex:1; min-width:0; }
|
||||
.wr-modal-close { width:32px; height:32px; border:none; background:rgba(255,255,255,.06); border-radius:9px; color:var(--text-2);
|
||||
cursor:pointer; display:flex; align-items:center; justify-content:center; flex-shrink:0; transition:all .15s; }
|
||||
.wr-modal-close svg { width:17px; height:17px; }
|
||||
.wr-modal-close:hover { background:rgba(255,255,255,.12); color:var(--text); }
|
||||
.wr-modal-body { display:grid; grid-template-columns:236px 1fr; gap:22px; align-items:start; }
|
||||
.wr-modal-preview { display:flex; flex-direction:column; align-items:center; gap:8px; position:sticky; top:0; }
|
||||
.wr-prev-scene { width:100%; aspect-ratio:1; border-radius:20px; display:flex; align-items:center; justify-content:center;
|
||||
overflow:hidden; border:1.5px solid rgba(255,255,255,.08); background:linear-gradient(155deg,#0d0d1f,#1a1040); }
|
||||
.wr-prev-scene svg { width:74%; height:auto; }
|
||||
.wr-prev-name { font-family:'Unbounded',sans-serif; font-weight:800; font-size:.95rem; }
|
||||
.wr-prev-evo { font-size:.7rem; color:var(--text-3); }
|
||||
@media (max-width:640px) {
|
||||
.wr-modal-body { grid-template-columns:1fr; gap:16px; }
|
||||
.wr-modal-preview { position:static; }
|
||||
.wr-prev-scene { max-width:190px; margin:0 auto; }
|
||||
}
|
||||
|
||||
/* ── Customize controls (внутри модалки) ── */
|
||||
.pet-customize { background:linear-gradient(165deg,rgba(155,93,229,.10),rgba(6,214,224,.045)),var(--surface);
|
||||
border:1.5px solid rgba(155,93,229,.2); border-radius:20px; padding:16px 18px 20px; margin-bottom:18px; }
|
||||
.pc-head { display:flex; align-items:center; gap:11px; margin-bottom:15px; }
|
||||
@@ -463,6 +487,10 @@
|
||||
<div class="pet-evo-legend" title="Облик растёт сам с уровнем (за XP): Ур.2 — уши, Ур.3 — антенны, Ур.4 — крылья и аура, Ур.5 — корона-сияние и орбиты, Ур.6 — вторая аура, Ур.7 — нимб и вторые крылья, Ур.8 — золотое сияние.">облик растёт с XP — наведи, чтобы узнать, что добавляется на каждом уровне</div>
|
||||
</div>
|
||||
|
||||
<button class="pet-action-btn" id="btn-customize" onclick="openWardrobe()" style="background:linear-gradient(135deg,rgba(155,93,229,.2),rgba(6,214,224,.12));border-color:rgba(155,93,229,.4);color:#c9a6ff">
|
||||
<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="M20.38 3.46 16 2a4 4 0 0 1-8 0L3.62 3.46a2 2 0 0 0-1.34 2.23l.58 3.47a1 1 0 0 0 .99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V10h2.15a1 1 0 0 0 .99-.84l.58-3.47a2 2 0 0 0-1.34-2.23z"/></svg>
|
||||
Нарядить
|
||||
</button>
|
||||
<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>
|
||||
Погладить
|
||||
@@ -540,39 +568,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Кастомизация ── -->
|
||||
<div class="pet-customize">
|
||||
<div class="pc-head">
|
||||
<div class="pc-head-ico"><i data-lucide="palette"></i></div>
|
||||
<div>
|
||||
<div class="pc-head-title">Кастомизация</div>
|
||||
<div class="pc-head-sub">Наряди питомца: аксессуары, цвет, узор и фон</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pc-tabs">
|
||||
<button class="pc-tab active" data-tab="acc" type="button"><i data-lucide="shirt"></i> Аксессуары</button>
|
||||
<button class="pc-tab" data-tab="color" type="button"><i data-lucide="droplet"></i> Цвет</button>
|
||||
<button class="pc-tab" data-tab="pattern" type="button"><i data-lucide="grid-2x2"></i> Узор</button>
|
||||
<button class="pc-tab" data-tab="bg" type="button"><i data-lucide="image"></i> Фон</button>
|
||||
</div>
|
||||
<div class="pc-panel" id="pc-acc">
|
||||
<div class="pc-hint">Нажми, чтобы надеть или снять. Заблокированные открываются за достижения. По одному предмету на зону.</div>
|
||||
<div 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-pattern" style="display:none">
|
||||
<div class="pc-hint">Узор на теле питомца.</div>
|
||||
<div class="pc-pattern-grid" id="pc-pattern-grid"></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">
|
||||
|
||||
@@ -622,6 +617,53 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Гардеробная (модалка) ── -->
|
||||
<div id="wardrobe-modal" class="wr-modal-overlay" style="display:none">
|
||||
<div class="wr-modal">
|
||||
<div class="wr-modal-head">
|
||||
<div class="pc-head-ico"><i data-lucide="shirt"></i></div>
|
||||
<div class="wr-modal-titles">
|
||||
<div class="pc-head-title">Гардеробная</div>
|
||||
<div class="pc-head-sub">Наряди питомца — изменения видно сразу</div>
|
||||
</div>
|
||||
<button class="wr-modal-close" onclick="closeWardrobe()" title="Закрыть">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="wr-modal-body">
|
||||
<div class="wr-modal-preview">
|
||||
<div class="wr-prev-scene" id="wr-preview-scene"><div id="wr-preview-svg"></div></div>
|
||||
<div class="wr-prev-name" id="wr-preview-name">Квантик</div>
|
||||
<div class="wr-prev-evo" id="wr-preview-evo"></div>
|
||||
</div>
|
||||
<div class="wr-modal-controls">
|
||||
<div class="pc-tabs">
|
||||
<button class="pc-tab active" data-tab="acc" type="button"><i data-lucide="shirt"></i> Аксессуары</button>
|
||||
<button class="pc-tab" data-tab="color" type="button"><i data-lucide="droplet"></i> Цвет</button>
|
||||
<button class="pc-tab" data-tab="pattern" type="button"><i data-lucide="grid-2x2"></i> Узор</button>
|
||||
<button class="pc-tab" data-tab="bg" type="button"><i data-lucide="image"></i> Фон</button>
|
||||
</div>
|
||||
<div class="pc-panel" id="pc-acc">
|
||||
<div class="pc-hint">Нажми, чтобы надеть или снять. Заблокированные открываются за достижения. По одному предмету на зону.</div>
|
||||
<div 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-pattern" style="display:none">
|
||||
<div class="pc-hint">Узор на теле питомца.</div>
|
||||
<div class="pc-pattern-grid" id="pc-pattern-grid"></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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Feed mini-game overlay ── -->
|
||||
<div id="feed-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:1000;align-items:center;justify-content:center;backdrop-filter:blur(6px)">
|
||||
<div style="background:var(--surface);border:1.5px solid rgba(255,255,255,.1);border-radius:22px;padding:28px 26px;width:380px;max-width:94vw;box-shadow:0 24px 80px rgba(0,0,0,.5)">
|
||||
@@ -1013,6 +1055,9 @@ function setupCustomizeTabs() {
|
||||
document.getElementById('pc-bg').style.display = which === 'bg' ? '' : 'none';
|
||||
if (which === 'bg') renderBgPicker();
|
||||
}));
|
||||
const ov = document.getElementById('wardrobe-modal');
|
||||
if (ov) ov.addEventListener('click', e => { if (e.target === ov) closeWardrobe(); });
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeWardrobe(); });
|
||||
}
|
||||
|
||||
/* ── Фоны (инлайн-выбор во вкладке) ── */
|
||||
@@ -1048,6 +1093,7 @@ async function selectBgInline(id, price, owned) {
|
||||
scene.className = scene.className.replace(/\bbg-\S+/g, '') + (id !== 'default' ? ` bg-${id}` : '');
|
||||
if (_petData) _petData.petBg = id;
|
||||
applyBgFX(id);
|
||||
updatePreviewScene();
|
||||
if (res.coins !== undefined) {
|
||||
document.getElementById('stat-coins').textContent = res.coins;
|
||||
if (_petData) _petData.coins = res.coins;
|
||||
@@ -1288,8 +1334,7 @@ async function selectColor(colorKey) {
|
||||
const res = await LS.api('/api/pet/color', {method:'PATCH', body: JSON.stringify({color:colorKey})}).catch(()=>null);
|
||||
if (!res?.ok) return;
|
||||
_petData.petColor = colorKey;
|
||||
document.getElementById('pet-svg-wrap').innerHTML =
|
||||
renderPetSVG(_petData.petLevel, _petData.mood, _petData.accessories, colorKey, _petData.streakCurrent || 0, _petData.petPattern || 'none');
|
||||
paintPet();
|
||||
document.querySelectorAll('.pet-color-dot').forEach(d =>
|
||||
d.classList.toggle('active', d.dataset.color === colorKey)
|
||||
);
|
||||
@@ -1301,6 +1346,34 @@ const CHECK_ICO = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" st
|
||||
const ICO_SHUFFLE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 3h5v5"/><path d="M4 20 21 3"/><path d="M21 16v5h-5"/><path d="m15 15 6 6"/><path d="M4 4l5 5"/></svg>';
|
||||
const ICO_ERASE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 21-4.3-4.3a1 1 0 0 1 0-1.4L13 5l6 6-9 9"/><path d="M22 21H7"/></svg>';
|
||||
const ZONE_LABELS = { head:'Голова', face:'Лицо', neck:'Шея', ears:'Уши', accent:'Акцент' };
|
||||
|
||||
/* ── Гардеробная: модалка + живое превью ── */
|
||||
function petSvgHTML() {
|
||||
return renderPetSVG(_petData.petLevel, _petData.mood, _petData.accessories, _petData.petColor || 'purple', _petData.streakCurrent || 0, _petData.petPattern || 'none');
|
||||
}
|
||||
function paintPet() {
|
||||
if (!_petData) return;
|
||||
const svg = petSvgHTML();
|
||||
const m = document.getElementById('pet-svg-wrap'); if (m) m.innerHTML = svg;
|
||||
const p = document.getElementById('wr-preview-svg'); if (p) p.innerHTML = svg;
|
||||
}
|
||||
function updatePreviewScene() {
|
||||
const sc = document.getElementById('wr-preview-scene');
|
||||
if (sc && _petData) sc.style.background = BG_PREVIEWS[_petData.petBg] || BG_PREVIEWS.default;
|
||||
}
|
||||
function openWardrobe() {
|
||||
if (!_petData) return;
|
||||
document.getElementById('wr-preview-name').textContent = _petData.petName || 'Квантик';
|
||||
const evoEl = document.getElementById('wr-preview-evo');
|
||||
if (evoEl) evoEl.textContent = (EVO_STAGES[_petData.petLevel] || '') + ' · ур. ' + _petData.petLevel;
|
||||
renderWardrobe(_petData.wardrobe || []);
|
||||
renderColorPicker(_petData.petColor || 'purple');
|
||||
renderPatternPicker(_petData.patterns || [], _petData.petPattern || 'none');
|
||||
paintPet(); updatePreviewScene();
|
||||
document.getElementById('wardrobe-modal').style.display = 'flex';
|
||||
}
|
||||
function closeWardrobe() { const m = document.getElementById('wardrobe-modal'); if (m) m.style.display = 'none'; }
|
||||
|
||||
function wearChip(it) {
|
||||
if (it.locked)
|
||||
return `<span class="wr-tile locked" title="Откроется: ${escHtml(it.hint)}">${LOCK_ICO}${escHtml(it.name)}${it.hint ? ` <span class="wr-hint">${escHtml(it.hint)}</span>` : ''}</span>`;
|
||||
@@ -1334,8 +1407,7 @@ async function setEquipped(list) {
|
||||
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');
|
||||
paintPet();
|
||||
renderWardrobe(_petData.wardrobe);
|
||||
}
|
||||
function toggleEquip(id) {
|
||||
@@ -1372,8 +1444,7 @@ async function applyPattern(id) {
|
||||
const res = await LS.api('/api/pet/pattern', { method:'PATCH', body: JSON.stringify({ pattern: id }) }).catch(() => null);
|
||||
if (!res || !res.ok) return;
|
||||
_petData.petPattern = id;
|
||||
document.getElementById('pet-svg-wrap').innerHTML =
|
||||
renderPetSVG(_petData.petLevel, _petData.mood, _petData.accessories, _petData.petColor || 'purple', _petData.streakCurrent || 0, id);
|
||||
paintPet();
|
||||
renderPatternPicker(_petData.patterns || [], id);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user