Upload files to "html"

This commit is contained in:
tanthius 2026-02-12 04:17:15 +00:00
parent 8b1c54a95d
commit 964a6fc764
2 changed files with 414 additions and 0 deletions

165
html/shop.css Normal file
View 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
View 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 (theyll 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();
}
});
})();