diff --git a/html/apperance.css b/html/apperance.css
new file mode 100644
index 0000000..d2fbc69
--- /dev/null
+++ b/html/apperance.css
@@ -0,0 +1,112 @@
+html, body {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ overflow: hidden;
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+/* block game clicks by default */
+body { pointer-events: none; }
+#wrap, #wrap * { pointer-events: auto; }
+
+.hidden { display: none !important; }
+
+#wrap {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+#wrap::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at 50% 40%, rgba(0,0,0,0.15), rgba(0,0,0,0.60));
+}
+
+.panel {
+ position: relative;
+ z-index: 2;
+ width: 420px;
+ border-radius: 14px;
+ background: rgba(18,18,22,0.90);
+ box-shadow: 0 12px 40px rgba(0,0,0,0.45);
+ padding: 18px;
+ color: #fff;
+}
+
+.title {
+ font-size: 22px;
+ font-weight: 700;
+ margin-bottom: 14px;
+}
+
+.row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 0;
+ border-top: 1px solid rgba(255,255,255,0.08);
+}
+
+.label {
+ font-size: 14px;
+ opacity: 0.9;
+}
+
+.controls {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.value {
+ min-width: 48px;
+ text-align: center;
+ font-weight: 700;
+ opacity: 0.95;
+}
+
+button {
+ border: 0;
+ padding: 8px 10px;
+ border-radius: 10px;
+ background: rgba(255,255,255,0.12);
+ color: #fff;
+ cursor: pointer;
+}
+
+button:hover {
+ background: rgba(255,255,255,0.18);
+}
+
+button.primary {
+ background: rgba(80,160,255,0.85);
+}
+
+button.primary:hover {
+ background: rgba(80,160,255,1);
+}
+
+button.active {
+ outline: 2px solid rgba(80,160,255,0.9);
+}
+
+.footer {
+ display: flex;
+ justify-content: flex-end;
+ padding-top: 12px;
+ border-top: 1px solid rgba(255,255,255,0.08);
+ margin-top: 12px;
+}
+
+.hint {
+ margin-top: 10px;
+ font-size: 12px;
+ opacity: 0.65;
+}
diff --git a/html/apperance.js b/html/apperance.js
new file mode 100644
index 0000000..55d38ff
--- /dev/null
+++ b/html/apperance.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;
+ }
+});