diff --git a/html/atm.css b/html/atm.css new file mode 100644 index 0000000..86a7acb --- /dev/null +++ b/html/atm.css @@ -0,0 +1,349 @@ +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + background: transparent; + font-family: Arial, Helvetica, sans-serif; + overflow: hidden; +} + +.hidden { display: none !important; } + +/* Stop clicks leaking into the game world */ +body { pointer-events: none; } +#wrap, #wrap * { pointer-events: auto; } + +/* Fullscreen wrapper that centers the ATM card */ +/* Fullscreen wrapper that centers the ATM card */ +#wrap { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; +} + +/* ✅ Only draw the ATM vignette when ATM is visible (not hidden) */ +#wrap:not(.hidden)::before { + content: ""; + position: absolute; + inset: 0; + background: radial-gradient( + circle at 50% 40%, + rgba(0,0,0,0.25), + rgba(0,0,0,0.65) + ); +} + +/* ATM card */ +.card { + position: relative; + z-index: 10; + + width: 420px; + border-radius: 14px; + padding: 18px; + + color: #e7f2ff; + + background: linear-gradient(180deg, rgba(28, 40, 52, 0.95), rgba(10, 14, 18, 0.92)); + border: 1px solid rgba(130, 190, 255, 0.25); + + backdrop-filter: blur(8px); + box-shadow: + 0 0 0 1px rgba(255,255,255,0.06), + 0 25px 70px rgba(0,0,0,0.85); +} + +/* Header */ +.title { + font-size: 20px; + font-weight: 700; + margin-bottom: 12px; + text-align: center; + letter-spacing: 1px; + color: rgba(160, 210, 255, 0.95); +} + +/* LCD balance panel */ +.balances { + margin-bottom: 14px; + + border-radius: 12px; + padding: 12px 14px; + + background: linear-gradient(180deg, rgba(10, 18, 26, 0.88), rgba(6, 10, 14, 0.78)); + border: 1px solid rgba(120, 200, 255, 0.18); + box-shadow: inset 0 0 22px rgba(0,0,0,0.65); +} + +.row { + display: flex; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid rgba(255,255,255,0.08); +} + +.row strong { + color: rgba(155, 231, 255, 0.95); + font-size: 18px; +} + +/* Actions area */ +.actions { + margin-top: 12px; + display: grid; + gap: 10px; +} + +/* Input */ +input { + width: 100%; + padding: 10px 12px; + border-radius: 10px; + + border: 1px solid rgba(120, 200, 255, 0.14); + background: rgba(0,0,0,0.38); + + color: #eaf4ff; + outline: none; + + box-shadow: inset 0 0 10px rgba(0,0,0,0.55); +} + +input:focus { + border-color: rgba(140, 220, 255, 0.35); + box-shadow: inset 0 0 10px rgba(0,0,0,0.55), 0 0 0 2px rgba(100, 180, 255, 0.15); +} + +/* Buttons */ +.btnrow { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +button { + padding: 10px 12px; + border-radius: 10px; + border: 0; + cursor: pointer; + font-weight: 700; + color: #fff; + + box-shadow: + inset 0 1px 0 rgba(255,255,255,0.18), + 0 8px 18px rgba(0,0,0,0.55); + + transition: transform 0.06s ease, filter 0.15s ease; +} + +button:hover { filter: brightness(1.12); } +button:active { transform: translateY(2px); } + +/* Primary actions */ +#btnDeposit { background: linear-gradient(180deg, rgba(0, 180, 90, 0.95), rgba(0, 120, 60, 0.9)); } +#btnWithdraw { background: linear-gradient(180deg, rgba(0, 130, 255, 0.95), rgba(0, 90, 180, 0.9)); } + +/* Exit / secondary */ +.secondary { + background: rgba(255,255,255,0.12); + color: #fff; +} + +/* Hint text */ +.hint { + margin-top: 10px; + opacity: 0.8; + font-size: 12px; +} + +/* ========================= + Gang Bank HUD (overlay) + ========================= */ +#gangbankHud { + position: absolute; + bottom: 170px; /* pushed up ~2x HUD height */ + right: 22px; + z-index: 2; + pointer-events: none; +} + +.gb-hidden { display: none !important; } + +.gb-card { + min-width: 190px; + padding: 6px 0; /* tighter HUD feel */ + background: none; /* ✅ no background */ + border: none; /* ✅ no border */ + box-shadow: none; /* ✅ no shadow */ + backdrop-filter: none; + color: #fff; +} + + +/* GTA-ish font stack (safe fallback) */ +.gb-card, .gb-card * { + font-family: "ChaletLondonNineteenSixty", "Chalet Comprime Cologne", "ChaletComprime Cologne", Arial, Helvetica, sans-serif; + letter-spacing: 0.4px; +} + +.gb-label { + font-size: 12px; + font-weight: 800; + margin-bottom: 4px; + color: var(--gang-color, #ffffff); /* ✅ dynamic gang color */ + text-shadow: 0 0 6px rgba(0,0,0,0.6); +} + +.gb-value { + font-size: 20px; + font-weight: 800; + color: #3cff57; /* cash green */ + text-shadow: 0 2px 10px rgba(0,0,0,0.35); +} + +/* Pulse animation */ +@keyframes gbPulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.06); } + 100% { transform: scale(1); } +} +.gb-pulse { animation: gbPulse 0.25s ease; } + +/* ========================= + Turf Capture HUD (overlay) + ========================= */ +#captureHud { + position: absolute; + left: 50%; + bottom: 10%; + transform: translateX(-50%); + z-index: 3; + pointer-events: none; /* never blocks input */ +} + +.cap-hidden { display: none !important; } + +.cap-card { + width: 360px; + padding: 12px 14px; + border-radius: 14px; + + background: rgba(10, 10, 10, 0.60); + border: 1px solid rgba(255,255,255,0.12); + color: #fff; + backdrop-filter: blur(8px); +} + +.cap-card, .cap-card * { + font-family: "ChaletLondonNineteenSixty", "Chalet Comprime Cologne", "ChaletComprime Cologne", Arial, Helvetica, sans-serif; + letter-spacing: 0.4px; +} + +.cap-title { + font-size: 14px; + font-weight: 800; + opacity: 0.95; + margin-bottom: 8px; +} + +.cap-bar { + height: 12px; + border-radius: 999px; + background: rgba(255,255,255,0.14); + overflow: hidden; +} + +.cap-fill { + height: 100%; + width: 0%; + background: rgba(255,255,255,0.85); + transition: width 80ms linear; +} + +.cap-sub { + margin-top: 7px; + font-size: 12px; + opacity: 0.85; +} + +/* ========================= + ✅ Leaderboard HUD (overlay) + ========================= */ +#leaderboardHud { + position: absolute; + top: 12%; + right: 22px; + z-index: 4; + pointer-events: none; +} + +.lb-hidden { display: none !important; } + +.lb-card { + width: 260px; + padding: 6px 0; /* tighter, cleaner */ + background: none; /* ✅ fully transparent */ + border: none; /* ✅ no border */ + box-shadow: none; /* ✅ no shadow */ + backdrop-filter: none; + color: #fff; +} + + +.lb-card, .lb-card * { + font-family: "ChaletLondonNineteenSixty", "Chalet Comprime Cologne", "ChaletComprime Cologne", Arial, Helvetica, sans-serif; + letter-spacing: 0.4px; +} + +.lb-title { + font-size: 14px; + font-weight: 900; + opacity: 0.95; + margin-bottom: 10px; +} + +.lb-error { + margin-bottom: 10px; + padding: 8px 10px; + border-radius: 10px; + background: rgba(255, 80, 80, 0.18); + border: 1px solid rgba(255, 80, 80, 0.35); + font-size: 12px; + opacity: 0.95; +} + +.lb-rows .lb-row { + display: flex; + justify-content: space-between; + padding: 6px 0; + border-bottom: none; /* cleaner HUD look */ + font-size: 13px; +} + + +.lb-rows .lb-row:last-child { + border-bottom: 0; +} + +.lb-rank { + opacity: 0.8; + margin-right: 8px; +} + +.lb-name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 10px; + text-shadow: 0 0 6px rgba(0,0,0,0.6); +} + +.lb-val { + font-weight: 900; + opacity: 0.95; + text-shadow: 0 0 6px rgba(0,0,0,0.6); +} diff --git a/html/atm.html b/html/atm.html new file mode 100644 index 0000000..f0b2ae2 --- /dev/null +++ b/html/atm.html @@ -0,0 +1,127 @@ + + + + + + + turfwar ui + + + + + + + + + + + + + + +
+
+
Gang Bank
+
$0
+
+
+ + +
+
+
Capturing…
+ +
+
+
+ +
0%
+
+
+ + +
+
+
Most influence
+
+
+
+
+ + +
+ + + + + + + + + + diff --git a/html/atm.js b/html/atm.js new file mode 100644 index 0000000..55d38ff --- /dev/null +++ b/html/atm.js @@ -0,0 +1,384 @@ +console.log("[ATM UI] atm.js loaded"); +console.log("[NUI] loaded"); + +// ===================== +// DOM refs +// ===================== +const wrap = document.getElementById("wrap"); +const cashEl = document.getElementById("cash"); +const bankEl = document.getElementById("bank"); +const amountEl = document.getElementById("amount"); + +const btnDeposit = document.getElementById("btnDeposit"); +const btnWithdraw = document.getElementById("btnWithdraw"); +const btnClose = document.getElementById("btnClose"); + +// Gang Bank HUD +const gbHud = document.getElementById("gangbankHud"); +const gbAmount = document.getElementById("gbAmount"); +const gbLabel = document.getElementById("gbLabel"); + +// Capture HUD +const capHud = document.getElementById("captureHud"); +const capTitle = document.getElementById("capTitle"); +const capFill = document.getElementById("capFill"); +const capSub = document.getElementById("capSub"); +const capBar = document.getElementById("capBar"); + +// Leaderboard HUD +const lbHud = document.getElementById("leaderboardHud"); +const lbTitle = document.getElementById("lbTitle"); +const lbRows = document.getElementById("lbRows"); +const lbError = document.getElementById("lbError"); + +// ===================== +// State +// ===================== +let lastGangBank = null; + +// ===================== +// Helpers +// ===================== +function fmt(n) { + n = Number(n || 0); + if (!Number.isFinite(n)) n = 0; + return "$" + Math.floor(n).toLocaleString(); +} + +function post(name, data = {}) { + try { + fetch(`https://${GetParentResourceName()}/${name}`, { + method: "POST", + headers: { "Content-Type": "application/json; charset=UTF-8" }, + body: JSON.stringify(data) + }).catch(() => {}); + } catch (e) { + console.log("[ATM UI] post failed:", name, e); + } +} + +function getAmount() { + const v = Number(amountEl?.value || 0); + if (!Number.isFinite(v)) return 0; + return Math.floor(v); +} + +// ===================== +// ATM UI +// ===================== +function hideATM() { + wrap?.classList.add("hidden"); +} + +function showATM() { + wrap?.classList.remove("hidden"); + setTimeout(() => amountEl?.focus(), 0); +} + +// ======================== +// Turf Capture HUD (fixed) +// ======================== +function capShow() { + capHud?.classList.remove("cap-hidden"); +} + +function capHide() { + capHud?.classList.add("cap-hidden"); + if (capFill) capFill.style.width = "0%"; + if (capSub) capSub.textContent = "0%"; +} + +function capStart(titleText) { + capShow(); + if (capTitle) capTitle.textContent = titleText || "Capturing…"; + capSet(0); +} + +function capSet(t) { + t = Math.max(0, Math.min(1, Number(t || 0))); + const pct = Math.round(t * 100); + if (capFill) capFill.style.width = pct + "%"; + if (capSub) capSub.textContent = pct + "%"; +} + +/** + * Capture styling: + * - fillCss = contesting/capturing gang + * - bgCss = outgoing/current owner gang + */ +function capStyle(fillCss, bgCss) { + if (capFill && fillCss) capFill.style.background = fillCss; + if (capBar && bgCss) capBar.style.background = bgCss; +} + +function capPaused(isPaused) { + if (!capSub) return; + if (isPaused) capSub.textContent = "PAUSED"; +} + +// ======================== +// Leaderboard HUD +// ======================== +function lbShow() { + lbHud?.classList.remove("lb-hidden"); +} + +function lbHide() { + lbHud?.classList.add("lb-hidden"); +} + +function lbRender(payload) { + if (!payload) return; + + lbShow(); + + if (lbTitle) lbTitle.textContent = String(payload.title || "Most influence"); + + // Error display (optional) + if (payload.error) { + if (lbError) { + lbError.textContent = String(payload.error); + lbError.classList.remove("lb-hidden"); + } + } else { + lbError?.classList.add("lb-hidden"); + if (lbError) lbError.textContent = ""; + } + + if (!lbRows) return; + lbRows.innerHTML = ""; + + const rows = Array.isArray(payload.rows) ? payload.rows : []; + + rows.forEach((r) => { + const gangId = Number(r.gangId); + const name = String(r.name ?? "Unknown"); + const val = Number(r.value ?? 0); + + // 🚫 Skip Police + if (gangId === 3) return; + + // --- Color handling --- + const rgb = Array.isArray(r.rgb) ? r.rgb : [255, 255, 255]; + + let R = Number(rgb[0]); + let G = Number(rgb[1]); + let B = Number(rgb[2]); + + // Fallback safety + if (!Number.isFinite(R)) R = 255; + if (!Number.isFinite(G)) G = 255; + if (!Number.isFinite(B)) B = 255; + + // Brighten very dark colors only (Lost Gang) + if (R < 80 && G < 80 && B < 80) { + R = Math.min(255, R + 80); + G = Math.min(255, G + 80); + B = Math.min(255, B + 80); + } + + const div = document.createElement("div"); + div.className = "lb-row"; + + div.innerHTML = ` + + ${escapeHtml(name)} + + ${Number.isFinite(val) ? val : 0} + `; + + lbRows.appendChild(div); + }); +} + +function escapeHtml(s) { + return String(s) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +function setGangBankColor(rgb) { + if (!gbLabel) return; + + const arr = Array.isArray(rgb) ? rgb : [255, 255, 255]; + + let R = Number(arr[0]); + let G = Number(arr[1]); + let B = Number(arr[2]); + + if (!Number.isFinite(R)) R = 255; + if (!Number.isFinite(G)) G = 255; + if (!Number.isFinite(B)) B = 255; + + // Brighten very dark colors (e.g. Lost Gang) + if (R < 80 && G < 80 && B < 80) { + R = Math.min(255, R + 80); + G = Math.min(255, G + 80); + B = Math.min(255, B + 80); + } + + const css = `rgb(${R}, ${G}, ${B})`; + + // Apply to label (and optionally amount if you want later) + gbLabel.style.setProperty("--gang-color", css); + gbLabel.style.color = "var(--gang-color)"; // ensures it shows even if CSS missing +} + + + +// ===================== +// Boot +// ===================== +document.addEventListener("DOMContentLoaded", () => { + hideATM(); + capHide(); // ✅ was capStop() in your file (but didn’t exist) + lbHide(); // start hidden until first update + + console.log("[ATM UI] DOMContentLoaded -> posting atm:ready"); + post("atm:ready"); +}); + +// ===================== +// Button wiring +// ===================== +btnDeposit?.addEventListener("click", () => { + const amount = getAmount(); + if (amount > 0) post("atm:deposit", { amount }); +}); + +btnWithdraw?.addEventListener("click", () => { + const amount = getAmount(); + if (amount > 0) post("atm:withdraw", { amount }); +}); + +btnClose?.addEventListener("click", () => post("atm:close")); + +document.addEventListener("keydown", (e) => { + if (e.key === "Escape") post("atm:close"); +}); + +// ===================== +// NUI messages (single listener) +// ===================== +window.addEventListener("message", (event) => { + const data = event.data || {}; + const type = data.type; + if (!type) return; + + // Capture debug (optional) + if (type.startsWith("capture:")) { + // console.log("[NUI] capture msg", data); + } + + switch (type) { + // -------- ATM -------- + case "atm:open": + showATM(); + break; + + case "atm:close": + hideATM(); + break; + + case "atm:balances": + if (cashEl) cashEl.textContent = fmt(data.cash); + if (bankEl) bankEl.textContent = fmt(data.bank); + break; + +// ---- Gang Bank HUD ---- +case "gangbank:show": + gbHud?.classList.remove("gb-hidden"); + break; + +case "gangbank:hide": + gbHud?.classList.add("gb-hidden"); + break; + +case "gangbank:label": { + if (gbLabel) gbLabel.textContent = String(data.label || "Gang Bank"); + + // ✅ set color if provided + if (data.rgb) setGangBankColor(data.rgb); + + break; +} + +case "gangbank:update": { + if (!gbAmount) break; + + const amt = Number(data.balance ?? data.amount ?? 0); + gbAmount.textContent = fmt(amt); + + // ✅ also accept rgb here (in case update is the only message carrying it) + if (data.rgb) setGangBankColor(data.rgb); + + // pulse only if changed + if (lastGangBank !== null && amt !== lastGangBank) { + gbAmount.classList.remove("gb-pulse"); + void gbAmount.offsetWidth; // restart animation + gbAmount.classList.add("gb-pulse"); + } + lastGangBank = amt; + break; +} + + // ---- Turf Capture HUD ---- + case "capture:start": + capStart(data.turfName || data.title || "Capturing…"); + if (data.t !== undefined) capSet(data.t); + if (data.fill || data.bg) capStyle(data.fill, data.bg); + break; + + case "capture:set": + capShow(); + capSet(data.t); + break; + + case "capture:style": + capStyle(data.fill, data.bg); + break; + + case "capture:paused": + capPaused(!!data.paused); + break; + + case "capture:hint": + if (capSub && data.text) { + capSub.textContent = String(data.text); + setTimeout(() => { + // only clear if still showing the same hint-like text + if (capSub && capSub.textContent === String(data.text)) { + capSub.textContent = ""; + } + }, 1500); + } + break; + + case "capture:stop": + capHide(); + break; + + // ---- ✅ Leaderboard ---- + // We accept either name: + // - "leaderboard:update" (client might send this) + // - "turfwar:leaderboard:update" (recommended) + case "leaderboard:update": + case "turfwar:leaderboard:update": + lbRender(data.payload || data); + break; + + case "leaderboard:hide": + lbHide(); + break; + + case "leaderboard:show": + lbShow(); + break; + + default: + break; + } +}); diff --git a/html/killfeed.css b/html/killfeed.css new file mode 100644 index 0000000..d39fb49 --- /dev/null +++ b/html/killfeed.css @@ -0,0 +1,108 @@ +/* ========================================================= + Killfeed (NUI) + - Bottom-left (above minimap) + - Stacks UP + - Bigger, bold, white text + - FULLY TRANSPARENT (no background) + ========================================================= */ + +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + background: transparent; + overflow: hidden; +} + +/* Killfeed container */ +#killfeed { + position: absolute; + left: 18px; + bottom: 250px; /* tweak if needed */ + + z-index: 50; + pointer-events: none; + + display: flex; + flex-direction: column-reverse; /* stack upward */ + + gap: 8px; + max-width: 460px; +} + +/* Each line */ +.kf-line { + display: inline-flex; + align-items: center; + gap: 10px; + + padding: 0; /* no box feel */ + border-radius: 0; + + /* ❌ NO BACKGROUND */ + background: none; + backdrop-filter: none; + box-shadow: none; + + font-family: "ChaletLondonNineteenSixty", + "Chalet Comprime Cologne", + "ChaletComprime Cologne", + Arial, Helvetica, sans-serif; + + font-size: 16px; /* bigger */ + font-weight: 800; /* bold */ + line-height: 1.25; + + color: #ffffff; /* white text */ + + /* Strong GTA-style shadow for readability */ + text-shadow: + 0 1px 2px rgba(0,0,0,0.95), + 0 0 8px rgba(0,0,0,0.85); + + opacity: 0; + transform: translateX(-6px); + animation: kfIn 140ms ease forwards; +} + +/* Force all spans to white unless overridden inline */ +.kf-line span { + color: #ffffff; +} + +/* Killer & victim names (still gang-colored inline) */ +.kf-killer, +.kf-victim { + font-weight: 900; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; + + text-shadow: + 0 1px 2px rgba(0,0,0,0.98), + 0 0 8px rgba(0,0,0,0.90); +} + +/* Weapon + extras */ +.kf-weapon, +.kf-extra { + font-size: 14px; + font-weight: 800; + opacity: 0.98; + white-space: nowrap; +} + +/* Fade out */ +.kf-out { + animation: kfOut 260ms ease forwards; +} + +@keyframes kfIn { + to { opacity: 1; transform: translateX(0); } +} + +@keyframes kfOut { + to { opacity: 0; transform: translateX(-8px); } +} diff --git a/html/killfeed.js b/html/killfeed.js new file mode 100644 index 0000000..a0503b4 --- /dev/null +++ b/html/killfeed.js @@ -0,0 +1,102 @@ +console.log("[KILLFEED] killfeed.js loaded"); + +const kfWrap = document.getElementById("killfeed"); + +function safeText(s) { + return String(s ?? "").replace(/[&<>"']/g, (c) => ({ + "&":"&", "<":"<", ">":">", '"':""", "'":"'" + }[c])); +} + +function rgbCss(rgb) { + const a = Array.isArray(rgb) ? rgb : [255,255,255]; + let r = Number(a[0]), g = Number(a[1]), b = Number(a[2]); + if (!Number.isFinite(r)) r = 255; + if (!Number.isFinite(g)) g = 255; + if (!Number.isFinite(b)) b = 255; + + // brighten very dark colors slightly + if (r < 70 && g < 70 && b < 70) { + r = Math.min(255, r + 80); + g = Math.min(255, g + 80); + b = Math.min(255, b + 80); + } + return `rgb(${r},${g},${b})`; +} + +function addKillfeedLine(payload) { + if (!kfWrap) return; + payload = payload || {}; + + // ✅ Death without a killer + if (payload.isDeathOnly) { + const victim = safeText(payload.victimName || "Unknown"); + const vCss = rgbCss(payload.victimRgb); + + const line = document.createElement("div"); + line.className = "kf-line"; + line.innerHTML = ` + ${victim} + has died + `; + + kfWrap.prepend(line); + + const maxLines = 6; + while (kfWrap.children.length > maxLines) { + kfWrap.lastElementChild?.remove(); + } + + setTimeout(() => { + line.classList.add("kf-out"); + setTimeout(() => line.remove(), 300); + }, 6000); + + return; + } + + // ✅ Normal PvP line + const killer = safeText(payload.killerName || "Unknown"); + const victim = safeText(payload.victimName || "Unknown"); + const weapon = safeText(payload.weapon || "Unknown"); + + const extras = []; + if (payload.headshot) extras.push("HS"); + const dist = Number(payload.distance || 0); + if (Number.isFinite(dist) && dist > 0.1) extras.push(`${Math.round(dist)}m`); + const extraText = extras.length ? `(${extras.join(" · ")})` : ""; + + const line = document.createElement("div"); + line.className = "kf-line"; + + const kCss = rgbCss(payload.killerRgb); + const vCss = rgbCss(payload.victimRgb); + + line.innerHTML = ` + ${killer} + [${weapon}] + ${victim} + ${extraText ? `${safeText(extraText)}` : ""} + `; + + kfWrap.prepend(line); + + const maxLines = 6; + while (kfWrap.children.length > maxLines) { + kfWrap.lastElementChild?.remove(); + } + + setTimeout(() => { + line.classList.add("kf-out"); + setTimeout(() => line.remove(), 300); + }, 7000); +} + +window.addEventListener("message", (event) => { + const data = event.data || {}; + if (data.type === "killfeed:add") { + addKillfeedLine(data); + } else if (data.type === "killfeed:clear") { + if (kfWrap) kfWrap.innerHTML = ""; + } +});