Upload files to "html"
This commit is contained in:
parent
d2e5303901
commit
8b1c54a95d
349
html/atm.css
Normal file
349
html/atm.css
Normal file
|
|
@ -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);
|
||||
}
|
||||
127
html/atm.html
Normal file
127
html/atm.html
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>turfwar ui</title>
|
||||
|
||||
<!-- Existing styles -->
|
||||
<link rel="stylesheet" href="atm.css" />
|
||||
<link rel="stylesheet" href="killfeed.css" />
|
||||
|
||||
<!-- NEW shop styles -->
|
||||
<link rel="stylesheet" href="shop.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- =========================
|
||||
ATM UI
|
||||
========================= -->
|
||||
<div id="wrap" class="hidden">
|
||||
<div class="card">
|
||||
<div class="title">Fleeca Bank</div>
|
||||
|
||||
<div class="balances">
|
||||
<div class="row">
|
||||
<span>Cash</span>
|
||||
<strong id="cash">$0</strong>
|
||||
</div>
|
||||
<div class="row" style="border-bottom:0;">
|
||||
<span>Bank</span>
|
||||
<strong id="bank">$0</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<input id="amount" type="number" inputmode="numeric" min="0" step="1" placeholder="Amount" />
|
||||
|
||||
<div class="btnrow">
|
||||
<button id="btnDeposit" type="button">Deposit</button>
|
||||
<button id="btnWithdraw" type="button">Withdraw</button>
|
||||
</div>
|
||||
|
||||
<button id="btnClose" type="button" class="secondary">Exit</button>
|
||||
<div class="hint">Press <b>ESC</b> to close</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =========================
|
||||
Gang Bank HUD
|
||||
========================= -->
|
||||
<div id="gangbankHud" class="gb-hidden">
|
||||
<div class="gb-card">
|
||||
<div class="gb-label" id="gbLabel">Gang Bank</div>
|
||||
<div class="gb-value" id="gbAmount">$0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =========================
|
||||
Turf Capture HUD
|
||||
========================= -->
|
||||
<div id="captureHud" class="cap-hidden">
|
||||
<div class="cap-card">
|
||||
<div class="cap-title" id="capTitle">Capturing…</div>
|
||||
|
||||
<div class="cap-bar" id="capBar">
|
||||
<div class="cap-fill" id="capFill"></div>
|
||||
</div>
|
||||
|
||||
<div class="cap-sub" id="capSub">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =========================
|
||||
Leaderboard HUD
|
||||
========================= -->
|
||||
<div id="leaderboardHud" class="lb-hidden">
|
||||
<div class="lb-card">
|
||||
<div class="lb-title" id="lbTitle">Most influence</div>
|
||||
<div class="lb-error lb-hidden" id="lbError"></div>
|
||||
<div class="lb-rows" id="lbRows"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =========================
|
||||
Killfeed container
|
||||
========================= -->
|
||||
<div id="killfeed"></div>
|
||||
|
||||
<!-- =========================
|
||||
GANG SHOP UI (NEW)
|
||||
========================= -->
|
||||
<div id="shop-wrap" class="hidden">
|
||||
<div class="shop-card">
|
||||
<div class="shop-top">
|
||||
<div>
|
||||
<h1 id="shop-title" class="shop-title">Gang Shop</h1>
|
||||
<div class="shop-sub">Weapons • Ammo • Vehicles</div>
|
||||
</div>
|
||||
<div class="shop-balance">
|
||||
<div>Gang Bank</div>
|
||||
<div id="shop-balance-num" class="num">$0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shop-tabs">
|
||||
<div id="shop-tab-weapons" class="shop-tab active">Weapons</div>
|
||||
<div id="shop-tab-ammo" class="shop-tab">Ammo</div>
|
||||
<div id="shop-tab-vehicles" class="shop-tab">Vehicles</div>
|
||||
</div>
|
||||
|
||||
<div id="shop-list" class="shop-list"></div>
|
||||
|
||||
<div class="shop-footer">
|
||||
<div>Tip: Press ESC to close</div>
|
||||
<div id="shop-close" class="shop-close">Close</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts (order matters a bit) -->
|
||||
<script src="killfeed.js"></script>
|
||||
<script src="atm.js"></script>
|
||||
<script src="shop.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
384
html/atm.js
Normal file
384
html/atm.js
Normal file
|
|
@ -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 = `
|
||||
<span class="lb-name" style="color: rgb(${R}, ${G}, ${B});">
|
||||
${escapeHtml(name)}
|
||||
</span>
|
||||
<span class="lb-val">${Number.isFinite(val) ? val : 0}</span>
|
||||
`;
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
108
html/killfeed.css
Normal file
108
html/killfeed.css
Normal file
|
|
@ -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); }
|
||||
}
|
||||
102
html/killfeed.js
Normal file
102
html/killfeed.js
Normal file
|
|
@ -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 = `
|
||||
<span class="kf-victim" style="color:${vCss}">${victim}</span>
|
||||
<span class="kf-weapon">has died</span>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<span class="kf-killer" style="color:${kCss}">${killer}</span>
|
||||
<span class="kf-weapon">[${weapon}]</span>
|
||||
<span class="kf-victim" style="color:${vCss}">${victim}</span>
|
||||
${extraText ? `<span class="kf-extra">${safeText(extraText)}</span>` : ""}
|
||||
`;
|
||||
|
||||
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 = "";
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user