тест
магазин
Сообщений 1 страница 2 из 2
Поделиться22025-12-21 19:14:43
[html]<div id="shop" class="shop">
<div class="shop__top">
<div class="shop__filters">
<input id="shopSearch" type="search" placeholder="Найти предмет..." />
<select id="shopCategory">
<option value="all">Все категории</option>
</select>
</div>
<details class="shop__stats" open>
<summary>Скиллы персонажа (для проверки требований)</summary>
<div class="shop__statsGrid">
<label>Сила <input id="st_sila" type="number" min="0" value="0"></label>
<label>Скорость <input id="st_skor" type="number" min="0" value="0"></label>
<label>Здоровье <input id="st_zdr" type="number" min="0" value="0"></label>
<label>Тайдзюцу <input id="st_tai" type="number" min="0" value="0"></label>
<label>Ниндзюцу <input id="st_nin" type="number" min="0" value="0"></label>
<label>Фуиндзюцу <input id="st_fuin" type="number" min="0" value="0"></label>
<label>Тактика <input id="st_takt" type="number" min="0" value="0"></label>
<label>Знания <input id="st_znan" type="number" min="0" value="0"></label>
<label>Метательные навыки <input id="st_met" type="number" min="0" value="0"></label>
</div>
</details>
</div>
<div class="shop__layout">
<section class="shop__list">
<div id="shopItems" class="shop__items"></div>
</section>
<aside class="shop__cart">
<div class="shop__cartHeader">
<strong>Корзина</strong>
<button id="cartClear" type="button">Очистить</button>
</div>
<div id="cartEmpty" class="shop__muted">Пока пусто. Как твой холодильник, когда “просто заглянула”.</div>
<div id="cartWrap" hidden>
<table class="shop__cartTable">
<thead>
<tr>
<th>Предмет</th>
<th>Кол-во</th>
<th>Цена</th>
<th></th>
</tr>
</thead>
<tbody id="cartRows"></tbody>
</table>
<div class="shop__cartTotal">
<span>Итого:</span>
<strong><span id="cartTotal">0</span> рё</strong>
</div>
<div class="shop__postTools">
<input id="episodeLine" type="text" placeholder="Строка эпизода (необязательно), напр.: В эпизоде 24.01.999 - Волчий след!" />
<button id="copyPost" type="button">Скопировать текст покупки</button>
<textarea id="postOut" rows="10" readonly></textarea>
</div>
</div>
</aside>
</div>
</div>
<script>
(() => {
// ====== ДАННЫЕ МАГАЗИНА (добавляй свои позиции по образцу) ======
// Взято из твоего списка "Магазин предметов" (оружие/броня/щиты/снаряжение). :contentReference[oaicite:1]{index=1}
const ITEMS = [
{
id: "sai",
name: "Саи",
category: "Колющее",
classRank: "D-ранг",
price: 3500,
image: "https://upforme.ru/uploads/001a/12/f3/6/t744066.jpg",
stats: [
"Урон: 30 ед. + Скорость*2",
"Крит: 50 ед. (25%)",
"Бонус: +1 к блокированию клинков"
],
req: { tai: 4, skor: 4 }
},
{
id: "pika",
name: "Пика",
category: "Колющее",
classRank: "D-ранг",
price: 3500,
image: "https://upforme.ru/uploads/001a/12/f3/6/t191823.webp",
stats: [
"Урон: 30 ед. + Скорость*2",
"Крит: 60 ед. (15%)"
],
req: { skor: 5, tai: 4 }
},
{
id: "trident",
name: "Трезубец",
category: "Колющее",
classRank: "D-ранг",
price: 4000,
image: "",
stats: [
"Урон: 35 ед. + Скорость*2",
"Крит: 70 ед. (20%)",
"Бонус: возможность метания"
],
req: { tai: 5, skor: 4 }
},
{
id: "kunai_pack_10",
name: "Комплект кунаев (10 шт.)",
category: "Метательное",
classRank: "E-ранг",
price: 1200,
image: "",
stats: [
"Урон: 15 ед. + Скорость*2",
"Крит: 30 ед. (15%)",
"Бонус: возможность метания"
],
req: { tai: 1, skor: 1 }
},
{
id: "smoke_bomb",
name: "Дымовая шашка",
category: "Вспомогательное снаряжение",
classRank: "E-ранг",
price: 600,
image: "",
stats: [
"Эффект: туман на 2 хода",
"Если добавить яд — урон как у яда"
],
req: { tai: 4, skor: 4 }
},
{
id: "flash_bomb",
name: "Световая бомба",
category: "Вспомогательное снаряжение",
classRank: "E-ранг",
price: 800,
image: "",
stats: [
"Урон: 0",
"Бонус: ослепление на 2 хода"
],
req: { tai: 3 }
},
{
id: "blank_scroll",
name: "Чистый свиток",
category: "Вспомогательное снаряжение",
classRank: "E-ранг",
price: 100,
image: "",
stats: [
"Бонус: запечатывание техник/оружия/предметов"
],
req: { fuin: 3, nin: 3 }
},
{
id: "light_armor",
name: "Лёгкая броня",
category: "Защита",
classRank: "Уровень 4",
price: 3500,
image: "",
stats: [
"Защита от физ. урона: 10",
"Комплект: жилет, щитки на голени и предплечьях"
],
req: { sila: 4, zdr: 4 }
},
{
id: "medium_armor",
name: "Средняя броня",
category: "Защита",
classRank: "Уровень 6",
price: 6000,
image: "",
stats: [
"Защита от физ. урона: 20",
"Комплект: жилет, щитки на голени и предплечьях"
],
req: { sila: 6, zdr: 6 }
},
{
id: "heavy_armor",
name: "Тяжёлая броня",
category: "Защита",
classRank: "Уровень 8",
price: 7500,
image: "",
stats: [
"Защита от физ. урона: 30"
],
req: { sila: 7, zdr: 7 }
},
{
id: "buckler",
name: "Баклер",
category: "Защита",
classRank: "Уровень 3",
price: 2500,
image: "",
stats: [
"Защита: 25",
"Урон щитом: 5 + Сила*2",
"Проверка блока: Тайдзюцу+Сила"
],
req: { sila: 3 }
},
{
id: "mokibishi",
name: "Мокибиши",
category: "Вспомогательное снаряжение",
classRank: "E-ранг",
price: 500,
image: "https://upforme.ru/uploads/001a/12/f3/6/t514347.webp",
stats: [
"Урон при наступлении: 10",
"Крит: 20 (10%)"
],
req: { met: 2 }
},
{
id: "paper_bombs_3",
name: "Комплект взрывных печатей (3 шт.)",
category: "Вспомогательное снаряжение",
classRank: "E-ранг",
price: 900,
image: "",
stats: [
"Урон: 40 (прямое) / 20 (близкая дистанция)"
],
req: {} // требований в твоём списке рядом не видно, оставила пусто
}
];
const CATEGORIES_ORDER = [
"Колющее",
"Режущее",
"Рубящее",
"Колюще-режущее",
"Рубяще-колющее",
"Ударно-раздробляющее",
"Дробяще-колющее",
"Метательное",
"Защита",
"Вспомогательное снаряжение"
];
// ====== DOM ======
const elItems = document.getElementById("shopItems");
const elSearch = document.getElementById("shopSearch");
const elCat = document.getElementById("shopCategory");
const elCartEmpty = document.getElementById("cartEmpty");
const elCartWrap = document.getElementById("cartWrap");
const elCartRows = document.getElementById("cartRows");
const elCartTotal = document.getElementById("cartTotal");
const elClear = document.getElementById("cartClear");
const elEpisode = document.getElementById("episodeLine");
const elCopy = document.getElementById("copyPost");
const elOut = document.getElementById("postOut");
const statIds = ["sila","skor","zdr","tai","nin","fuin","takt","znan","met"];
const statInputs = Object.fromEntries(statIds.map(k => [k, document.getElementById("st_" + k)]));
// ====== STATE ======
const cart = new Map(); // id -> {qty}
// ====== HELPERS ======
const fmt = (n) => (Number(n)||0).toLocaleString("ru-RU");
const getStats = () => Object.fromEntries(statIds.map(k => [k, Number(statInputs[k].value||0)]));
function unmetReq(item, stats) {
const req = item.req || {};
const miss = [];
for (const [k, v] of Object.entries(req)) {
if ((stats[k] || 0) < v) miss.push(`${labelStat(k)} ≥ ${v}`);
}
return miss;
}
function labelStat(k) {
return ({
sila: "Сила",
skor: "Скорость",
zdr: "Здоровье",
tai: "Тайдзюцу",
nin: "Ниндзюцу",
fuin: "Фуиндзюцу",
takt: "Тактика",
znan: "Знания",
met: "Метательные навыки"
})[k] || k;
}
function categories() {
const set = new Set(ITEMS.map(i => i.category));
const sorted = [...set].sort((a,b) => {
const ia = CATEGORIES_ORDER.indexOf(a);
const ib = CATEGORIES_ORDER.indexOf(b);
if (ia === -1 && ib === -1) return a.localeCompare(b, "ru");
if (ia === -1) return 1;
if (ib === -1) return -1;
return ia - ib;
});
return sorted;
}
// ====== RENDER ======
function renderCategoryOptions() {
const cats = categories();
for (const c of cats) {
const opt = document.createElement("option");
opt.value = c;
opt.textContent = c;
elCat.appendChild(opt);
}
}
function renderItems() {
const q = (elSearch.value || "").trim().toLowerCase();
const cat = elCat.value;
const stats = getStats();
const filtered = ITEMS.filter(i => {
const okCat = (cat === "all") || (i.category === cat);
const okQ = !q || (i.name.toLowerCase().includes(q));
return okCat && okQ;
});
elItems.innerHTML = "";
for (const item of filtered) {
const miss = unmetReq(item, stats);
const canBuy = miss.length === 0;
const card = document.createElement("article");
card.className = "shop__card";
card.dataset.id = item.id;
const imgHtml = item.image
? `<div class="shop__img"><img src="${item.image}" alt=""></div>`
: `<div class="shop__img shop__img--ph">нет картинки</div>`;
card.innerHTML = `
${imgHtml}
<div class="shop__body">
<div class="shop__titleRow">
<div class="shop__title">
<strong>${item.name}</strong>
<div class="shop__meta">${item.classRank} • ${item.category}</div>
</div>
<div class="shop__price">${fmt(item.price)} рё</div>
</div>
<ul class="shop__statsList">
${(item.stats || []).map(s => `<li>${s}</li>`).join("")}
</ul>
<div class="shop__req">
${
Object.keys(item.req || {}).length
? `<div class="shop__reqLine"><span class="shop__muted">Требования:</span> ${
Object.entries(item.req).map(([k,v]) => `${labelStat(k)} ≥ ${v}`).join(", ")
}</div>`
: `<div class="shop__reqLine shop__muted">Требования: —</div>`
}
${
canBuy
? `<div class="shop__ok">Можно купить</div>`
: `<div class="shop__bad">Не хватает: ${miss.join(", ")}</div>`
}
</div>
<div class="shop__actions">
<button type="button" class="shop__btnAdd" ${canBuy ? "" : "disabled"}>
Добавить
</button>
</div>
</div>
`;
card.querySelector(".shop__btnAdd").addEventListener("click", () => addToCart(item.id, 1));
elItems.appendChild(card);
}
}
function renderCart() {
if (cart.size === 0) {
elCartEmpty.hidden = false;
elCartWrap.hidden = true;
elOut.value = "";
return;
}
elCartEmpty.hidden = true;
elCartWrap.hidden = false;
elCartRows.innerHTML = "";
let total = 0;
for (const [id, row] of cart.entries()) {
const item = ITEMS.find(i => i.id === id);
if (!item) continue;
const line = item.price * row.qty;
total += line;
const tr = document.createElement("tr");
tr.innerHTML = `
<td>
<div><strong>${item.name}</strong></div>
<div class="shop__muted">${item.classRank} • ${item.category}</div>
</td>
<td>
<div class="shop__qty">
<button type="button" data-act="dec">−</button>
<input type="number" min="1" value="${row.qty}">
<button type="button" data-act="inc">+</button>
</div>
</td>
<td>${fmt(line)} рё</td>
<td><button type="button" data-act="del">✕</button></td>
`;
const qtyInput = tr.querySelector('input[type="number"]');
tr.querySelector('[data-act="dec"]').addEventListener("click", () => setQty(id, row.qty - 1));
tr.querySelector('[data-act="inc"]').addEventListener("click", () => setQty(id, row.qty + 1));
qtyInput.addEventListener("change", () => setQty(id, Number(qtyInput.value || 1)));
tr.querySelector('[data-act="del"]').addEventListener("click", () => removeFromCart(id));
elCartRows.appendChild(tr);
}
elCartTotal.textContent = fmt(total);
elOut.value = buildPostText(total);
}
function buildPostText(total) {
const lines = [];
const ep = (elEpisode.value || "").trim();
if (ep) lines.push(ep);
lines.push("Покупка:");
for (const [id, row] of cart.entries()) {
const item = ITEMS.find(i => i.id === id);
if (!item) continue;
const sum = item.price * row.qty;
// если в названии уже есть комплект/шт — не выдумываем единицы
lines.push(`${row.qty} x ${item.name} = ${fmt(sum)} рё`);
}
lines.push(`Итог - ${fmt(total)}`);
return lines.join("\n");
}
// ====== CART OPS ======
function addToCart(id, qty) {
const cur = cart.get(id)?.qty || 0;
cart.set(id, { qty: cur + qty });
renderCart();
}
function setQty(id, qty) {
if (qty <= 0) cart.delete(id);
else cart.set(id, { qty: Math.max(1, Math.floor(qty)) });
renderCart();
}
function removeFromCart(id) {
cart.delete(id);
renderCart();
}
// ====== EVENTS ======
function onFilterChange() { renderItems(); }
elSearch.addEventListener("input", onFilterChange);
elCat.addEventListener("change", onFilterChange);
for (const k of statIds) statInputs[k].addEventListener("input", () => { renderItems(); });
elClear.addEventListener("click", () => {
cart.clear();
renderCart();
});
elEpisode.addEventListener("input", () => {
// обновлять текст внизу, если корзина не пуста
if (cart.size) elOut.value = buildPostText(Number(elCartTotal.textContent.replace(/\s/g,"")) || 0);
});
elCopy.addEventListener("click", async () => {
const txt = elOut.value || "";
try {
await navigator.clipboard.writeText(txt);
elCopy.textContent = "Скопировано";
setTimeout(() => (elCopy.textContent = "Скопировать текст покупки"), 900);
} catch (e) {
// fallback
elOut.focus();
elOut.select();
document.execCommand("copy");
}
});
// ====== INIT ======
renderCategoryOptions();
renderItems();
renderCart();
})();
</script>
[/html]











































































