Upload files to "html"
This commit is contained in:
parent
8b1c54a95d
commit
964a6fc764
165
html/shop.css
Normal file
165
html/shop.css
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/* html/shop.css */
|
||||
|
||||
/* Hide helpers */
|
||||
#shop-wrap.hidden { display: none; }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
/* Kill any “black frame” / overlay behind the card */
|
||||
html, body {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
#shop-wrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: auto;
|
||||
background: transparent !important;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* If shop is open, hide ATM vignette even if wrap accidentally exists */
|
||||
#shop-wrap:not(.hidden) ~ #wrap::before { display: none !important; }
|
||||
#wrap::before { display: none !important; } /* temporary test */
|
||||
|
||||
/* Card (gang-tinted glass) */
|
||||
.shop-card {
|
||||
width: 520px;
|
||||
border-radius: 16px;
|
||||
padding: 18px;
|
||||
|
||||
/* Gang-tinted glass */
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(var(--gang-rgb, 10, 10, 10), 0.45),
|
||||
rgba(10,10,10,0.60)
|
||||
);
|
||||
|
||||
/* ✅ remove the frame */
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: 0 10px 26px rgba(var(--gang-rgb, 10, 10, 10), 0.18);
|
||||
|
||||
color: #fff;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
|
||||
/* Header */
|
||||
.shop-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.shop-title {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.shop-sub {
|
||||
font-size: 12px;
|
||||
opacity: 0.85;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.shop-balance {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.shop-balance .num {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.shop-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 12px 0 10px;
|
||||
}
|
||||
|
||||
.shop-tab {
|
||||
border: 1px solid rgba(255,255,255,0.14);
|
||||
background: rgba(255,255,255,0.06);
|
||||
color: #fff;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.shop-tab.active {
|
||||
background: rgba(var(--gang-rgb, 10, 10, 10), 0.28);
|
||||
border-color: rgba(var(--gang-rgb, 10, 10, 10), 0.55);
|
||||
}
|
||||
|
||||
/* List */
|
||||
.shop-list {
|
||||
max-height: 360px;
|
||||
overflow: auto;
|
||||
border-top: 1px solid rgba(255,255,255,0.10);
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.shop-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
|
||||
.shop-item-name { font-weight: 700; }
|
||||
|
||||
.shop-item-sub {
|
||||
font-size: 12px;
|
||||
opacity: 0.85;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.shop-price {
|
||||
font-weight: 800;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.shop-buy {
|
||||
border: 1px solid rgba(255,255,255,0.14);
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: #fff;
|
||||
padding: 8px 12px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
.shop-buy:hover {
|
||||
background: rgba(255,255,255,0.12);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.shop-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.shop-close {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
249
html/shop.js
Normal file
249
html/shop.js
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
// html/shop.js
|
||||
(() => {
|
||||
const wrap = document.getElementById("shop-wrap");
|
||||
const title = document.getElementById("shop-title");
|
||||
const balanceNum = document.getElementById("shop-balance-num");
|
||||
|
||||
const tabBtns = {
|
||||
weapons: document.getElementById("shop-tab-weapons"),
|
||||
ammo: document.getElementById("shop-tab-ammo"),
|
||||
vehicles: document.getElementById("shop-tab-vehicles"),
|
||||
};
|
||||
|
||||
const list = document.getElementById("shop-list");
|
||||
const closeBtn = document.getElementById("shop-close");
|
||||
|
||||
console.log("[turfwar] shop.js loaded");
|
||||
|
||||
// Guard against missing markup (prevents silent failures)
|
||||
if (
|
||||
!wrap || !title || !balanceNum || !list || !closeBtn ||
|
||||
!tabBtns.weapons || !tabBtns.ammo || !tabBtns.vehicles
|
||||
) {
|
||||
console.error("[turfwar] shop.js missing DOM elements. Check atm.html includes #shop-wrap and tab ids.");
|
||||
return;
|
||||
}
|
||||
|
||||
const ALL_TABS = ["weapons", "ammo", "vehicles"];
|
||||
|
||||
let state = {
|
||||
open: false,
|
||||
tab: "weapons",
|
||||
gangId: 0,
|
||||
balance: 0,
|
||||
payload: { weapons: {}, ammo: {}, vehicles: {}, gangs: {} },
|
||||
|
||||
// NEW: which tabs are allowed for this open instance
|
||||
allowedTabs: ["weapons", "ammo", "vehicles"],
|
||||
};
|
||||
|
||||
function fmtMoney(n) {
|
||||
n = Number(n) || 0;
|
||||
return "$" + n.toLocaleString();
|
||||
}
|
||||
|
||||
function normalizeAllowedTabs(arr) {
|
||||
// If not provided, allow all
|
||||
if (!Array.isArray(arr) || arr.length === 0) return [...ALL_TABS];
|
||||
|
||||
// Keep only known tabs, unique, in ALL_TABS order
|
||||
const set = new Set(arr.map(String));
|
||||
return ALL_TABS.filter(t => set.has(t));
|
||||
}
|
||||
|
||||
function firstAllowedTab() {
|
||||
return (state.allowedTabs && state.allowedTabs[0]) ? state.allowedTabs[0] : "weapons";
|
||||
}
|
||||
|
||||
function applyAllowedTabsUI() {
|
||||
// Hide / show tab buttons
|
||||
for (const t of ALL_TABS) {
|
||||
const btn = tabBtns[t];
|
||||
const ok = state.allowedTabs.includes(t);
|
||||
btn.classList.toggle("hidden", !ok);
|
||||
// Also prevent focusing hidden buttons
|
||||
btn.tabIndex = ok ? 0 : -1;
|
||||
}
|
||||
|
||||
// If current tab not allowed, force to first allowed
|
||||
if (!state.allowedTabs.includes(state.tab)) {
|
||||
state.tab = firstAllowedTab();
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveTab(tab) {
|
||||
tab = String(tab || "");
|
||||
|
||||
// Enforce allowed tabs
|
||||
if (!state.allowedTabs.includes(tab)) {
|
||||
tab = firstAllowedTab();
|
||||
}
|
||||
|
||||
state.tab = tab;
|
||||
Object.keys(tabBtns).forEach((k) => tabBtns[k].classList.toggle("active", k === tab));
|
||||
renderList();
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
list.innerHTML = "";
|
||||
|
||||
// Enforce allowed tab again (in case something changed)
|
||||
if (!state.allowedTabs.includes(state.tab)) {
|
||||
setActiveTab(firstAllowedTab());
|
||||
return;
|
||||
}
|
||||
|
||||
const catalog = state.payload[state.tab] || {};
|
||||
const entries = Object.entries(catalog);
|
||||
|
||||
if (entries.length === 0) {
|
||||
const empty = document.createElement("div");
|
||||
empty.style.padding = "10px 0";
|
||||
empty.style.opacity = "0.8";
|
||||
empty.textContent = "No items configured.";
|
||||
list.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [itemId, it] of entries) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "shop-row";
|
||||
|
||||
const left = document.createElement("div");
|
||||
|
||||
const name = document.createElement("div");
|
||||
name.className = "shop-item-name";
|
||||
name.textContent = it.label || itemId;
|
||||
|
||||
const sub = document.createElement("div");
|
||||
sub.className = "shop-item-sub";
|
||||
|
||||
if (state.tab === "vehicles" && itemId === "gangveh") {
|
||||
const gName =
|
||||
(state.payload.gangs &&
|
||||
state.payload.gangs[state.gangId] &&
|
||||
state.payload.gangs[state.gangId].name) ||
|
||||
"Your Gang";
|
||||
sub.textContent = `Free gang vehicle for ${gName}`;
|
||||
} else {
|
||||
sub.textContent = `ID: ${itemId}`;
|
||||
}
|
||||
|
||||
left.appendChild(name);
|
||||
left.appendChild(sub);
|
||||
|
||||
const right = document.createElement("div");
|
||||
right.style.display = "flex";
|
||||
right.style.alignItems = "center";
|
||||
|
||||
const price = document.createElement("div");
|
||||
price.className = "shop-price";
|
||||
price.textContent = fmtMoney(it.price || 0);
|
||||
|
||||
const buy = document.createElement("button");
|
||||
buy.className = "shop-buy";
|
||||
buy.type = "button";
|
||||
buy.textContent = "Buy";
|
||||
buy.onclick = () => doBuy(state.tab, itemId);
|
||||
|
||||
right.appendChild(price);
|
||||
right.appendChild(buy);
|
||||
|
||||
row.appendChild(left);
|
||||
row.appendChild(right);
|
||||
|
||||
list.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
function post(name, data) {
|
||||
return fetch(`https://${GetParentResourceName()}/${name}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json; charset=UTF-8" },
|
||||
body: JSON.stringify(data || {}),
|
||||
}).catch((err) => {
|
||||
console.error("[turfwar] NUI post failed:", name, err);
|
||||
});
|
||||
}
|
||||
|
||||
function doBuy(tab, itemId) {
|
||||
// Hard enforce allowed tabs (security-ish)
|
||||
if (!state.allowedTabs.includes(tab)) {
|
||||
console.warn("[turfwar] blocked buy from disallowed tab:", tab, itemId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab === "weapons") return post("shop:buyWeapon", { itemId });
|
||||
if (tab === "ammo") return post("shop:buyAmmo", { itemId });
|
||||
if (tab === "vehicles") return post("shop:buyVehicle", { itemId });
|
||||
}
|
||||
|
||||
function open(msg) {
|
||||
state.open = true;
|
||||
|
||||
state.gangId = Number(msg.gangId) || 0;
|
||||
state.payload = msg.payload || state.payload;
|
||||
|
||||
// NEW: allowed tabs
|
||||
state.allowedTabs = normalizeAllowedTabs(msg.allowedTabs);
|
||||
|
||||
// requested tab (will be forced to allowed)
|
||||
state.tab = String(msg.tab || "weapons");
|
||||
|
||||
title.textContent = "Gang Shop";
|
||||
balanceNum.textContent = fmtMoney(state.balance);
|
||||
|
||||
// apply allowed tab UI before setting active tab
|
||||
applyAllowedTabsUI();
|
||||
|
||||
wrap.classList.remove("hidden");
|
||||
setActiveTab(state.tab);
|
||||
}
|
||||
|
||||
function close() {
|
||||
state.open = false;
|
||||
wrap.classList.add("hidden");
|
||||
post("shop:close", {});
|
||||
}
|
||||
|
||||
// Buttons (they’ll be hidden when not allowed)
|
||||
tabBtns.weapons.addEventListener("click", () => setActiveTab("weapons"));
|
||||
tabBtns.ammo.addEventListener("click", () => setActiveTab("ammo"));
|
||||
tabBtns.vehicles.addEventListener("click", () => setActiveTab("vehicles"));
|
||||
closeBtn.addEventListener("click", close);
|
||||
|
||||
// ESC closes
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.data.type === "shop:open") {
|
||||
const rgb = e.data.gangRGB || { r: 10, g: 10, b: 10 }
|
||||
document.documentElement.style.setProperty(
|
||||
"--gang-rgb",
|
||||
`${rgb.r}, ${rgb.g}, ${rgb.b}`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Receive messages from Lua
|
||||
window.addEventListener("message", (event) => {
|
||||
const msg = event.data;
|
||||
if (!msg || !msg.type) return;
|
||||
|
||||
if (msg.type === "shop:open") {
|
||||
console.log("[turfwar] shop:open", msg);
|
||||
open(msg);
|
||||
|
||||
} else if (msg.type === "shop:close") {
|
||||
wrap.classList.add("hidden");
|
||||
state.open = false;
|
||||
|
||||
} else if (msg.type === "shop:balance") {
|
||||
state.balance = Number(msg.balance) || 0;
|
||||
state.gangId = Number(msg.gangId) || state.gangId;
|
||||
balanceNum.textContent = fmtMoney(state.balance);
|
||||
|
||||
} else if (msg.type === "shop:gang") {
|
||||
state.gangId = Number(msg.gangId) || 0;
|
||||
renderList();
|
||||
}
|
||||
});
|
||||
})();
|
||||
Loading…
Reference in New Issue
Block a user