Greasy Fork is available in English.

Nitro Type - Admin Panel

Pick any car from the Admin Panel and apply it to your account server-side so everyone sees it.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Nitro Type - Admin Panel
// @version      0.4.0
// @description  Pick any car from the Admin Panel and apply it to your account server-side so everyone sees it.
// @author       Toonidy (extended)
// @match        *://*.nitrotype.com/race
// @match        *://*.nitrotype.com/race/*
// @match        *://*.nitrotype.com/garage
// @match        *://*.nitrotype.com/garage/customizer
// @match        *://*.nitrotype.com/garage/customizer/*
// @icon         https://i.ibb.co/YRs06pc/toonidy-userscript.png
// @grant        GM_getResourceURL
// @resource     icon_tab https://i.ibb.co/28Ts3Xd/key-icon.png#sha512=3b8723fb0a6f220c9fa03ea38d9a600df2efe9dc38217b0be4a71132d12457edd6344c7c12370e5a451ea6199fb39a06dabc94c7c8c9782c1c4584b6e5d04a53
// @require      https://greasyfork.org/scripts/443718-nitro-type-userscript-utils/code/Nitro%20Type%20Userscript%20Utils.js?version=1042360
// @require      https://cdnjs.cloudflare.com/ajax/libs/dexie/3.2.2/dexie.min.js#sha512=/Aa8vGWIh0EnOTIVN/ZWTS3UqyJJDhWYtIPS/IqtaaSG0VA6hC6CSvtWdh2+T72q74+2l1RFgu+ig91LGLX57A==
// @license      MIT
// @namespace    https://greasyfork.org/users/858426
// ==/UserScript==

/* global NTGLOBALS findReact createLogger Dexie */

const logging = createLogger("Nitro Type Admin Panel")

const db = new Dexie("NTAdminPanel")
db.version(1).stores({ savedCar: "userID" })
db.open().catch((e) => logging.error("Init")("Failed to open up the config database", e))

let currentUser = null
try {
	currentUser = JSON.parse(JSON.parse(localStorage.getItem("persist:nt")).user)
	if (!currentUser.loggedIn) {
		logging.error("Init")("Custom Car is only available for logged in users.")
		return
	}
} catch (err) {
	logging.error("Init")("Failed to identify current logged in user")
	return
}

db.savedCar.get(currentUser.userID).then(main)

function main(config) {

	// ─────────────────────────────────────────────────────────────────
	//  applyCarToAccount
	//  Calls the exact same endpoints the normal Nitro Type customizer
	//  uses when you click "Equip":
	//    POST /api/cars/<carId>/use   → equips the car server-side
	//    POST /api/cars/<carId>/paint → saves the hue server-side
	//  Because these use your existing login session cookie they need
	//  no password and no special auth — you're already logged in.
	//  The result is permanent and visible to ALL players on your
	//  public profile and in every race, no script required on their end.
	// ─────────────────────────────────────────────────────────────────
	const applyCarToAccount = async (carID, hue) => {
		if (!carID) return { ok: false, message: "No car selected." }
		try {
			// Equip the car
			const useRes  = await fetch(`https://www.nitrotype.com/api/cars/${carID}/use`, {
				method: "POST",
				credentials: "include",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ carID }),
			})
			const useData = await useRes.json()
			if (!useRes.ok || useData.status !== "ok") {
				return { ok: false, message: useData?.errMsg || `Equip failed (HTTP ${useRes.status})` }
			}

			// Apply hue/paint if non-zero
			if (hue && hue !== 0) {
				const paintRes  = await fetch(`https://www.nitrotype.com/api/cars/${carID}/paint`, {
					method: "POST",
					credentials: "include",
					headers: { "Content-Type": "application/json" },
					body: JSON.stringify({ angle: hue, carID }),
				})
				const paintData = await paintRes.json()
				if (!paintRes.ok || paintData.status !== "ok") {
					logging.warn("Apply")("Paint failed (non-fatal):", paintData)
				}
			}

			return { ok: true, message: "✅ Car applied! Everyone will see it now." }
		} catch (err) {
			logging.error("Apply")("Network error:", err)
			return { ok: false, message: "❌ Network error — check the console." }
		}
	}

	///////////////////////
	//  Customizer Page  //
	///////////////////////

	if (
		window.location.pathname === "/garage/customizer" ||
		window.location.pathname.startsWith("/garage/customizer/")
	) {
		const container   = document.querySelector("#root main.structure-content div.customizer"),
			reactObj      = container ? findReact(container) : null
		if (!container || !reactObj) { logging.error("Init")("Unable to find customizer container"); return }

		const RARITY_VALUES = { common: 1, uncommon: 2, rare: 3, epic: 4, legendary: 5 }

		const tabContainer    = container.querySelector(".customizer--tabs.nav-list"),
			titleHeading      = container.querySelector(".customizer--about--title"),
			previewer         = container.querySelector(".customizer--previewer"),
			previewerCanvas   = previewer.querySelector("canvas")

		if (!tabContainer || !titleHeading) { logging.error("Init")("Unable to modify tab navigation"); return }

		/* Styles */
		const style = document.createElement("style")
		style.appendChild(document.createTextNode(`
.section-nt-admin-panel .customizer--previewer { right: 714px; bottom: 230px; }
.section-nt-admin-panel.nt-admin-panel-unset .customizer--previewer { right: 580px; bottom: 230px; }
.nt-admin-panel-label.customizer--preview { left: 10px; right: 715px; top: 285px; bottom: 230px; }
.nt-admin-panel-label.customizer--preview .customizer--vehicle-selection--name { font-size: 16px; }
.nt-admin-panel-label.customizer--preview .customizer--vehicle-selection--rarity { padding-bottom: 0; }
.nt-admin-panel-no-cars {
    position: absolute; top: 90px; left: 10px; right: 580px; bottom: 230px;
    display: none; align-items: center; justify-content: center; border-radius: 3px;
    background-color: #202020; font-size: 18px; font-weight: 600;
    text-shadow: 0 2px 3px rgb(0 0 0 / 50%); color: #fff; z-index: 2;
}
.nt-admin-panel-unset .nt-admin-panel-no-cars { display: flex; }
.nt-admin-panel-scrollable { overflow-y: scroll; scrollbar-face-color: #1C99F4; scrollbar-track-color: #232633; }
.nt-admin-panel-scrollable::-webkit-scrollbar { width: 10px; height: 10px; }
.nt-admin-panel-scrollable::-webkit-scrollbar-thumb { background-color: #1C99F4; }
.nt-admin-panel-scrollable::-webkit-scrollbar-track { background-color: #232633; }
.section-nt-admin-panel .customizer--item-selector-controls { grid-template-columns: 1fr 210px; }
.customizer--item-selector.nt-admin-panel-car-selector { width: 560px; }
.customizer--item-selector.nt-admin-panel-car-selector .customizer--item-selector-items { grid-template-columns: repeat(4, 1fr); }
.customizer--item-selector.nt-admin-panel-trail-selector { top: 380px; left: 10px; }
.customizer--item-selector.nt-admin-panel-paint-selector { top: 90px; bottom: 230px; left: 320px; width: 125px; }
.customizer--item-selector.nt-admin-panel-paint-selector .nt-admin-panel-paint-selector-heading {
    display: flex; align-items: center; column-gap: 10px; height: 35px; padding: 0 10px;
    margin-bottom: 5px; border-top-left-radius: 4px; border-top-right-radius: 4px;
    background-color: #282b3a; color: #eee; font-weight: bold; font-size: 13px;
}
.customizer--item-selector.nt-admin-panel-paint-selector .nt-admin-panel-paint-selector-heading .nt-admin-panel-paint-selector-heading-icon,
.customizer--item-selector.nt-admin-panel-paint-selector .nt-admin-panel-paint-selector-heading .nt-admin-panel-paint-selector-heading-icon svg { width: 20px; height: 20px; }
.customizer--item-selector.nt-admin-panel-paint-selector .nt-admin-panel-paint-selector-heading .nt-admin-panel-paint-selector-heading-icon svg { fill: #ccc; }
.customizer--item-selector.nt-admin-panel-paint-selector .nt-admin-panel-paint-selector-heading .nt-admin-panel-paint-selector-heading-label { flex-grow: 1; color: #ccc; }
.customizer--item-selector.nt-admin-panel-paint-selector .nt-admin-panel-scrollable { position: absolute; left: 0; top: 40px; right: 0; bottom: 0; }
.customizer--item-selector.nt-admin-panel-paint-selector .customizer--item-selector-items { grid-template-columns: 1fr; grid-gap: 5px; margin: 0 5px 5px; }
.nt-admin-panel-unset .customizer--item-selector.nt-admin-panel-paint-selector { display: none; }
.nt-admin-panel-paint-selector .paint-select-preview {
    width: 100%; height: 100%; background-repeat: no-repeat;
    background-position: 50% 50%; background-size: auto 40px;
}

/* ── Apply to Account bar ── */
.nt-admin-apply-bar {
    position: absolute;
    bottom: 82px;
    left: 10px;
    right: 580px;
    display: flex;
    align-items: center;
    gap: 12px;
    z-index: 10;
}
.nt-admin-apply-btn {
    flex-shrink: 0;
    padding: 9px 20px;
    border: none;
    border-radius: 5px;
    background: linear-gradient(135deg, #1C99F4 0%, #0d6ebd 100%);
    color: #fff;
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    letter-spacing: 0.3px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
    transition: opacity 0.15s, transform 0.1s;
}
.nt-admin-apply-btn:hover  { opacity: 0.88; transform: translateY(-1px); }
.nt-admin-apply-btn:active { transform: translateY(0px); }
.nt-admin-apply-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
.nt-admin-apply-status {
    font-size: 12px; font-weight: 600;
    padding: 5px 11px; border-radius: 4px;
    opacity: 0; transition: opacity 0.3s;
    pointer-events: none;
}
.nt-admin-apply-status.visible  { opacity: 1; }
.nt-admin-apply-status.success  { background: #1a3a26; color: #4ade80; }
.nt-admin-apply-status.error    { background: #3a1a1a; color: #f87171; }
.nt-admin-panel-unset .nt-admin-apply-bar { display: none; }
`))
		document.head.appendChild(style)

		const saveConfig = (carID, hue, trailID) => {
			config = { userID: currentUser.userID, car: carID || null, hue: hue || 0, trail: trailID || null }
			db.savedCar.put(config)
		}

		const setCar = (carID, hue, trailID) => {
			const carData  = reactObj.props.getCarMetaData(carID),
				isAnimated  = carData.isAnimated,
				trailData   = trailID ? NTGLOBALS.LOOT.find((l) => l.lootID === trailID && l.type === "trail")?.assetKey : undefined
			reactObj.previewer.setCar({ type: isAnimated ? carData.assetKey : carID, hue, isAnimated, trail: trailData, tweaks: carData.tweaks })
		}

		/* Car Paint Worker */
		const CarPainter = ((reactObj) => {
			function carHueShiftWorkerScript() {
				this.onmessage = function (e) {
					for (var t = e.data, r = t.pixels, n = t.hue, a = t.id, o = r.length, i = 0; i < o; i += 4) {
						var l, s,
							c = r[i]/255, u = r[i+1]/255, f = r[i+2]/255,
							d = Math.min(c,u,f), p = Math.max(c,u,f), m = p-d, h = 0
						;(h = 0===m ? 0 : p===c ? ((u-f)/m)%6 : p===u ? (f-c)/m+2 : (c-u)/m+4),
							(h = Math.round(60*h)), (h+=n)<0&&(h+=360), (h%=360),
							(s=(p+d)/2), (l=0===m?0:m/(1-Math.abs(2*s-1)))
						var v=(1-Math.abs(2*s-1))*l, b=v*(1-Math.abs(((h/60)%2)-1)), g=s-v/2
						0<=h&&h<60?((c=v),(u=b),(f=0)):60<=h&&h<120?((c=b),(u=v),(f=0))
							:120<=h&&h<180?((c=0),(u=v),(f=b)):180<=h&&h<240?((c=0),(u=b),(f=v))
							:240<=h&&h<300?((c=b),(u=0),(f=v)):300<=h&&h<360&&((c=v),(u=0),(f=b))
						;(r[i]=Math.round(255*(c+g))),(r[i+1]=Math.round(255*(u+g))),(r[i+2]=Math.round(255*(f+g)))
					}
					this.postMessage({id:a,updated:r},[r.buffer])
				}
			}
			let completed={}, pending={}, onCarPaintCreated=null
			const carHueShiftWorker = (() => {
				try {
					const data = carHueShiftWorkerScript.toString().replace(/^[^{]*{\s*/,"").replace(/\s*}[^}]*$/,""),
						blob = new Blob([data],{type:"text/javascript"})
					return new Worker(URL.createObjectURL(blob))
				} catch(e){ logging.error("Init")("Failed to setup worker") }
			})()
			carHueShiftWorker.onmessage = (e) => {
				const {id,updated}=e.data, {canvas,ctx,width,height}=pending[id], newImgData=ctx.createImageData(width,height)
				delete pending[id]; newImgData.data.set(updated); ctx.putImageData(newImgData,0,0)
				completed[id]=canvas.toDataURL()
				if(onCarPaintCreated) onCarPaintCreated(id,completed[id])
			}
			const performHueShift=(carImg,hue)=>{
				const canvas=document.createElement("canvas"),ctx=canvas.getContext("2d"),{width,height}=carImg
				canvas.width=width; canvas.height=height; ctx.drawImage(carImg,0,0)
				const imgData=ctx.getImageData(0,0,width,height)
				pending[hue]={id:hue,width,height,canvas,ctx}
				carHueShiftWorker.postMessage({id:hue,hue,pixels:imgData.data},[imgData.data.buffer])
			}
			return {
				generateSampleCarPaints:(carID)=>{
					const src=reactObj.props.getCarUrl(carID,false,0); if(!src)return
					const img=document.createElement("img")
					img.addEventListener("load",()=>{ pending={};completed={};for(let hue=0;hue<=340;hue+=10)performHueShift(img,hue) })
					img.src=src
				},
				setCarPaintCreatedHandler:(fn)=>{ onCarPaintCreated=fn },
			}
		})(reactObj)

		/* Sort helpers */
		const sortAlphaHandler  = (r)=>(a,b)=> r?b.data.name.localeCompare(a.data.name):a.data.name.localeCompare(b.data.name)
		const sortRarityHandler = (r)=>(a,b)=>{
			const ra=RARITY_VALUES[a.data.options?.rarity]||0, rb=RARITY_VALUES[b.data.options?.rarity]||0
			if(ra===rb)return 0; return r?(ra<rb?-1:1):(ra>rb?-1:1)
		}
		const sortIDHandler=(key,r)=>(a,b)=>{
			if(a.data[key]===b.data[key])return 0; return r?(a.data[key]<b.data[key]?1:-1):(a.data[key]>b.data[key]?1:-1)
		}

		/* Selected car label */
		const selectedCarLabel = document.createElement("div")
		selectedCarLabel.className = "nt-admin-panel-label customizer--preview vehicle-preview"
		selectedCarLabel.innerHTML = `
            <div class="customizer--vehicle-selection">
                <div class="customizer--vehicle-selection--name"></div>
                <div class="customizer--vehicle-selection--rarity">
                    <div class="rarity-badge rarity-badge--small">
                        <div class="rarity-badge--extra"></div>
                        <div class="rarity-badge--content"></div>
                    </div>
                </div>
                <div class="customizer--vehicle-selection--equipped">Currently Equipped</div>
            </div>`
		const carLabel   = selectedCarLabel.querySelector(".customizer--vehicle-selection--name"),
			rarityBadge  = selectedCarLabel.querySelector(".customizer--vehicle-selection--rarity .rarity-badge"),
			rarityLabel  = selectedCarLabel.querySelector(".customizer--vehicle-selection--rarity .rarity-badge--content")
		const updateCarLabel = (c) => {
			carLabel.textContent = c.name
			if (c.options?.rarity) {
				rarityBadge.className = `rarity-badge rarity-badge--small rarity-badge--${c.options.rarity}`
				rarityLabel.textContent = `${c.options.rarity[0].toUpperCase()+c.options.rarity.substr(1)} Car`
			}
		}

		/* ── Apply to Account bar ── */
		const applyBar = document.createElement("div")
		applyBar.className = "nt-admin-apply-bar"
		applyBar.innerHTML = `
            <button class="nt-admin-apply-btn">🚗 Apply to Account</button>
            <span class="nt-admin-apply-status"></span>`
		const applyBtn    = applyBar.querySelector(".nt-admin-apply-btn")
		const applyStatus = applyBar.querySelector(".nt-admin-apply-status")
		let   statusTimer = null

		const showStatus = (msg, type) => {
			clearTimeout(statusTimer)
			applyStatus.textContent = msg
			applyStatus.className   = `nt-admin-apply-status visible ${type}`
			statusTimer = setTimeout(() => { applyStatus.className = "nt-admin-apply-status" }, 5000)
		}

		applyBtn.addEventListener("click", async () => {
			if (!config?.car) return
			applyBtn.disabled     = true
			applyBtn.textContent  = "Applying…"
			const result = await applyCarToAccount(config.car, config.hue)
			applyBtn.disabled     = false
			applyBtn.textContent  = "🚗 Apply to Account"
			showStatus(result.message, result.ok ? "success" : "error")
		})

		/* Selector item template */
		const selectItemTemplate = document.createElement("div")
		selectItemTemplate.className = "customizer--item-selector-item"
		selectItemTemplate.innerHTML = `
            <div class="customizer--item-selector-item--labels">
                <div class="customizer--item-selector-item--equipped">Equipped</div>
            </div>
            <div class="customizer--item-selector-item--controls">
                <div class="customizer--item-selector-item--favorite">Favorite</div>
                <div class="customizer--item-selector-item--hide">Hide</div>
            </div>
            <div class="customizer--item-selector-item--content">
                <div class="rarity-frame rarity-frame--small">
                    <div class="rarity-frame--extra"></div>
                    <div class="rarity-frame--content">
                        <div class="customizer--item-selector-item--container">
                            <div>
                                <div class="customizer--item-selector-item--vehicle"></div>
                                <div class="customizer--tooltip"></div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>`

		/* Car selector UI */
		const customCarUI = document.createElement("div")
		customCarUI.className = "nt-admin-panel-car-selector customizer--item-selector vehicle-selector show-search scrollable"
		customCarUI.innerHTML = `
            <div class="customizer--item-selector-controls">
                <div class="customizer--item-selector-controls--filter">
                    <input type="filter" class="input-field customizer--item-selector-controls--filter-input" placeholder="Search Car" value="">
                    <button class="customizer--item-selector-controls--filter-clear">×</button>
                </div>
                <div class="customizer--item-selector-controls--sort">
                    <div class="customizer--item-selector-controls--sort-label">Sort By</div>
                    <select class="input-select customizer--item-selector-controls--sort-options">
                        <option value="rarity_commons">Rarity: Least Rarest</option>
                        <option value="rarity_rarests">Rarity: Most Rarest</option>
                        <option value="name_a-z" selected>Name: A - Z</option>
                        <option value="name_z-a">Name: Z - A</option>
                        <option value="id_asc">ID: Ascending</option>
                        <option value="id_desc">ID: Descending</option>
                    </select>
                </div>
            </div>
            <div class="customizer--item-selector-container nt-admin-panel-scrollable">
                <div class="customizer--item-selector-items"></div>
            </div>`
		const customCarSelectorContainer = customCarUI.querySelector(".customizer--item-selector-items")

		const customCarNoPreview = document.createElement("div")
		customCarNoPreview.className = "nt-admin-panel-no-cars"
		customCarNoPreview.textContent = "Choose your Custom Car"
		if (!config?.car) container.classList.add("nt-admin-panel-unset")

		const noCarItem = document.createElement("div")
		noCarItem.className = " customizer--item-selector-item"
		noCarItem.innerHTML = `
            <div class="customizer--item-selector-item--labels">
                <div class="customizer--item-selector-item--equipped">Equipped</div>
            </div>
            <div class="customizer--item-selector-item--content">
                <div class="customizer--item-selector-item--container">
                    <div>
                        <div class="customizer--item-selector-item--remove">No Car</div>
                        <div class="customizer--tooltip">Remove Car</div>
                    </div>
                </div>
            </div>`
		noCarItem.addEventListener("pointerup", () => {
			if (!config?.car) return
			container.classList.add("nt-admin-panel-unset")
			saveConfig(null, config?.hue, config?.trail)
			customCarSelectorContainer.querySelectorAll(".is-equipped").forEach((n) => n.classList.remove("is-equipped"))
			noCarItem.classList.add("is-equipped")
			selectedCarLabel.remove()
		})
		if (!config?.car) noCarItem.classList.add("is-equipped")
		else noCarItem.classList.remove("is-equipped")

		const carSelectItems = NTGLOBALS.CARS.map((c) => {
			const item = selectItemTemplate.cloneNode(true)
			item.querySelector(".rarity-frame").classList.add(`rarity-frame--${c.options?.rarity}`)
			item.querySelector(".customizer--tooltip").textContent = c.name
			item.querySelector(".customizer--item-selector-item--vehicle").style.backgroundImage = `url(/cars/${c.options?.smallSrc})`
			item.addEventListener("pointerup", () => {
				if (c.id === config?.car) return
				container.classList.remove("nt-admin-panel-unset")
				setCar(c.id, config?.hue, config?.trail)
				saveConfig(c.id, config?.hue, config?.trail)
				CarPainter.generateSampleCarPaints(config.car)
				customCarSelectorContainer.querySelectorAll(".is-equipped").forEach((n) => n.classList.remove("is-equipped"))
				item.classList.add("is-equipped")
				container.querySelector(".customizer--preview").after(selectedCarLabel)
				updateCarLabel(c)
			})
			if (c.id === config?.car) item.classList.add("is-equipped")
			return { data: c, node: item }
		})

		const populateList = (search, sortBy) => {
			if (sortBy==="name_a-z")        carSelectItems.sort(sortAlphaHandler(false))
			else if(sortBy==="name_z-a")    carSelectItems.sort(sortAlphaHandler(true))
			else if(sortBy==="rarity_rarests") carSelectItems.sort(sortRarityHandler(false))
			else if(sortBy==="rarity_commons") carSelectItems.sort(sortRarityHandler(true))
			else if(sortBy==="id_asc")      carSelectItems.sort(sortIDHandler("id",false))
			else if(sortBy==="id_desc")     carSelectItems.sort(sortIDHandler("id",true))
			const frag = document.createDocumentFragment()
			frag.append(noCarItem)
			carSelectItems.forEach((c)=>{ if(search&&c.data.name.toLowerCase().indexOf(search.toLowerCase())===-1)return; frag.append(c.node) })
			while(customCarSelectorContainer.firstChild) customCarSelectorContainer.removeChild(customCarSelectorContainer.firstChild)
			customCarSelectorContainer.append(frag)
		}

		const customCarSortBy       = customCarUI.querySelector(".input-select.customizer--item-selector-controls--sort-options"),
			customCarSearch          = customCarUI.querySelector(".input-field.customizer--item-selector-controls--filter-input"),
			customCarSearchClear     = customCarUI.querySelector(".customizer--item-selector-controls--filter-clear")
		customCarSortBy.addEventListener("change", ()=>populateList(customCarSearch.value,customCarSortBy.value))
		customCarSearch.addEventListener("keyup",  ()=>populateList(customCarSearch.value,customCarSortBy.value))
		customCarSearchClear.addEventListener("click",()=>{ customCarSearch.value=""; populateList("",customCarSortBy.value) })

		/* Trail selector UI */
		const customTrailUI = customCarUI.cloneNode(true)
		customTrailUI.className = "nt-admin-panel-trail-selector customizer--item-selector vehicle-selector show-search scrollable"
		const customTrailSelectorContainer = customTrailUI.querySelector(".customizer--item-selector-items")

		const noTrailItem = noCarItem.cloneNode(true)
		noTrailItem.querySelector(".customizer--item-selector-item--remove").textContent = "No Trail"
		noTrailItem.querySelector(".customizer--tooltip").textContent = "Remove Trail"
		noTrailItem.addEventListener("pointerup", () => {
			if (!config?.trail) return
			if (config?.car) setCar(config.car, config?.hue, null)
			saveConfig(config?.car, config?.hue, null)
			customTrailSelectorContainer.querySelectorAll(".is-equipped").forEach((n) => n.classList.remove("is-equipped"))
			noTrailItem.classList.add("is-equipped")
		})
		if (!config?.trail) noTrailItem.classList.add("is-equipped")
		else noTrailItem.classList.remove("is-equipped")

		const trailSelectItems = NTGLOBALS.LOOT.filter((l) => l.type === "trail").map((t) => {
			const item = selectItemTemplate.cloneNode(true)
			item.querySelector(".rarity-frame").classList.add(`rarity-frame--${t.options?.rarity}`)
			item.querySelector(".customizer--tooltip").textContent = t.name
			item.querySelector(".customizer--item-selector-item--vehicle").style.backgroundImage = `url(${t.options?.src})`
			item.addEventListener("pointerup", () => {
				if (t.lootID === config?.trail) return
				if (config?.car) setCar(config.car, config?.hue, t.lootID)
				saveConfig(config?.car, config?.hue, t.lootID)
				customTrailSelectorContainer.querySelectorAll(".is-equipped").forEach((n) => n.classList.remove("is-equipped"))
				item.classList.add("is-equipped")
			})
			if (t.lootID === config?.trail) item.classList.add("is-equipped")
			return { data: t, node: item }
		})

		const populateTrailList = (search, sortBy) => {
			if (sortBy==="name_a-z")        trailSelectItems.sort(sortAlphaHandler(false))
			else if(sortBy==="name_z-a")    trailSelectItems.sort(sortAlphaHandler(true))
			else if(sortBy==="rarity_rarests") trailSelectItems.sort(sortRarityHandler(false))
			else if(sortBy==="rarity_commons") trailSelectItems.sort(sortRarityHandler(true))
			else if(sortBy==="id_asc")      trailSelectItems.sort(sortIDHandler("lootID",false))
			else if(sortBy==="id_desc")     trailSelectItems.sort(sortIDHandler("lootID",true))
			const frag = document.createDocumentFragment()
			frag.append(noTrailItem)
			trailSelectItems.forEach((t)=>{ if(search&&t.data.name.toLowerCase().indexOf(search.toLowerCase())===-1)return; frag.append(t.node) })
			while(customTrailSelectorContainer.firstChild) customTrailSelectorContainer.removeChild(customTrailSelectorContainer.firstChild)
			customTrailSelectorContainer.append(frag)
		}

		const customTrailSortBy     = customTrailUI.querySelector(".input-select.customizer--item-selector-controls--sort-options"),
			customTrailSearch        = customTrailUI.querySelector(".input-field.customizer--item-selector-controls--filter-input"),
			customTrailSearchClear   = customTrailUI.querySelector(".customizer--item-selector-controls--filter-clear")
		customTrailSearch.placeholder = "Search Trail"
		customTrailSortBy.addEventListener("change", ()=>populateTrailList(customTrailSearch.value,customTrailSortBy.value))
		customTrailSearch.addEventListener("keyup",  ()=>populateTrailList(customTrailSearch.value,customTrailSortBy.value))
		customTrailSearchClear.addEventListener("click",()=>{ customTrailSearch.value=""; populateTrailList("",customTrailSortBy.value) })

		/* Paint selector UI */
		const customCarPaintUI = document.createElement("div")
		customCarPaintUI.className = "nt-admin-panel-paint-selector customizer--item-selector paint-selector"
		customCarPaintUI.innerHTML = `
            <div class="customizer--item-selector-container">
                <div class="nt-admin-panel-paint-selector-heading">
                    <div class="nt-admin-panel-paint-selector-heading-icon">
                        <svg width="296" height="298" viewBox="0 0 296 298" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M122.94 67.8906C125.687 80.7934 131.287 90.9781 139.312 99.0844C149.684 109.561 163.054 115.395 174.89 120.029C177.209 120.938 179.494 121.814 181.74 122.677L181.742 122.677L181.749 122.68C203.696 131.103 222.001 138.128 233.456 159.298L234.766 168.903L185.238 218.432L78.8184 112.012L122.94 67.8906ZM71.7473 119.083L42.0488 148.782C38.1436 152.687 38.1436 159.018 42.0488 162.924L66.7976 187.672L8.99159 245.478C-2.82176 257.292 -2.82177 276.445 8.99159 288.258C20.805 300.072 39.9582 300.072 51.7716 288.258L109.578 230.452L134.326 255.201C138.232 259.106 144.563 259.106 148.468 255.201L178.167 225.503L71.7473 119.083ZM19.775 277.829C26.6091 284.663 37.6895 284.663 44.5237 277.829C51.3579 270.994 51.3579 259.914 44.5237 253.08C37.6895 246.246 26.6091 246.246 19.775 253.08C12.9408 259.914 12.9408 270.994 19.775 277.829Z"></path><path d="M176.206 5.20904L137.797 45.0956C136.002 46.959 134.976 49.5184 135.082 52.1031C137.946 122.171 214.991 94.7163 246.062 153.181C246.687 154.356 247.092 155.673 247.272 156.992L251.312 186.622C252.279 193.713 258.336 199 265.492 199C272.213 199 278.028 194.324 279.47 187.761L294.699 118.468C296.136 111.929 294.207 105.105 289.558 100.287L197.805 5.19812C191.903 -0.918788 182.102 -0.913825 176.206 5.20904Z"></path><circle cx="265" cy="226" r="14"></circle></svg>
                    </div>
                    <div class="nt-admin-panel-paint-selector-heading-label">Paint</div>
                </div>
                <div class="nt-admin-panel-scrollable">
                    <div class="customizer--item-selector-items"></div>
                </div>
            </div>`
		const customCarPaintContainer = customCarPaintUI.querySelector(".customizer--item-selector-items")

		;(() => {
			const tmpl = document.createElement("div")
			tmpl.className = "customizer--item-selector-item"
			tmpl.innerHTML = `
                <div class="customizer--item-selector-item--labels">
                    <div class="customizer--item-selector-item--equipped">Equipped</div>
                </div>
                <div class="paint-select-preview"></div>`
			const handler = (e) => {
				const item = e.target.closest(".customizer--item-selector-item"), hue = parseInt(item.dataset.selectedhue)
				if (hue===(config?.hue||0)) return
				if (config?.car) setCar(config.car,hue,config.trail)
				saveConfig(config?.car,hue,config.trail)
				customCarPaintContainer.querySelectorAll(".is-equipped").forEach((n)=>n.classList.remove("is-equipped"))
				item.classList.add("is-equipped")
			}
			const frag = document.createDocumentFragment()
			for (let hue=0; hue<=340; hue+=10) {
				const item = tmpl.cloneNode(true)
				item.dataset.selectedhue = hue
				if (hue===config?.hue) item.classList.add("is-equipped")
				item.addEventListener("pointerup",handler)
				frag.append(item)
			}
			customCarPaintContainer.append(frag)
		})()

		CarPainter.setCarPaintCreatedHandler((hue,imgDataURL)=>{
			const target = customCarPaintContainer.querySelector(`.customizer--item-selector-item[data-selectedhue="${hue}"] .paint-select-preview`)
			if (!target) return
			target.style.backgroundImage = `url(${imgDataURL})`
		})
		if (config?.car) CarPainter.generateSampleCarPaints(config.car)

		/* Admin Panel tab */
		const customCarTab = tabContainer.firstElementChild.cloneNode(true)
		customCarTab.setAttribute("customizertabindex", null)
		customCarTab.classList.remove("customizer--tab--selected","is-current")
		customCarTab.querySelector(".customizer--tab--label").textContent = "Admin Panel"
		customCarTab.querySelector(".customizer--tab--icon").innerHTML = `<img src="${GM_getResourceURL("icon_tab")}" alt="admin_panel_icon" width="25" height="24" />`
		customCarTab.addEventListener("pointerup",(e)=>{ e.preventDefault(); showCustomCarPage(true) })

		const changeTitle = (title) => { const h=container.querySelector(".customizer--about--title"); if(h) h.textContent=title }

		const showCustomCarPage = (show, tabName) => {
			const customizerItemSelector = container.querySelector(".customizer--item-selector"),
				customizerPreview = container.querySelector(".customizer--preview")
			customizerItemSelector.hidden = show

			if (show) {
				customizerPreview.style.opacity = 0
				customizerPreview.style.zIndex  = -1000
				container.classList.remove("section-paint","section-trails","section-stickers","section-titles","no-preview")
				container.classList.add("section-cars","section-nt-admin-panel")
				previewerCanvas.style.width  = "300px"
				previewerCanvas.style.height = "280px"
				otherTabs.forEach((t)=>t.classList.remove("customizer--tab--selected","is-current"))
				customCarTab.classList.add("customizer--tab--selected","is-current")
				changeTitle("Admin Panel")

				reactObj.previewer.setFocus("car")
				if (config?.car) setCar(config.car,config.hue||0,config.trail)
				customizerItemSelector.after(customCarUI,customTrailUI,customCarPaintUI)
				previewer.after(customCarNoPreview)
				previewer.after(applyBar)           // ← inject the Apply bar

				if (config?.car) {
					updateCarLabel(NTGLOBALS.CARS.find((c)=>c.id===config.car))
					customizerPreview.after(selectedCarLabel)
				}

				const selCar   = customCarSelectorContainer.querySelector(".is-equipped"),
					selTrail    = customTrailSelectorContainer.querySelector(".is-equipped"),
					selPaint    = customCarPaintContainer.querySelector(".is-equipped")
				if(selCar)   customCarSelectorContainer.parentNode.scrollTop   = selCar.offsetTop
				if(selTrail) customTrailSelectorContainer.parentNode.scrollTop = selTrail.offsetTop
				if(selPaint) customCarPaintContainer.parentNode.scrollTop      = selPaint.offsetTop
			} else {
				customizerPreview.style.opacity = 1
				customizerPreview.style.zIndex  = ""
				container.classList.add(`section-${tabName.toLowerCase()}`)
				container.classList.remove("section-nt-admin-panel")
				previewerCanvas.style.width  = "559px"
				previewerCanvas.style.height = "500px"
				if (tabName!=="Cars") container.classList.remove("section-cars")
				if (tabName==="Trails") reactObj.previewer.setFocus("trails")
				if (["Stickers","Titles"].includes(tabName)) container.classList.add("no-preview")
				customCarTab.classList.remove("customizer--tab--selected","is-current")
				changeTitle(tabName)

				const realConfig = reactObj.props.config
				if (!realConfig) { logging.warn("Init")("Unable to find user's customizer settings"); return }
				const {id:realCarID,hueAngle:realHue} = realConfig.find((c)=>c.type==="car")||{},
					realTrailID = realConfig.find((c)=>c.type==="trail")?.id
				setCar(realCarID,realHue,realTrailID)

				customCarUI.remove(); customTrailUI.remove(); customCarPaintUI.remove()
				customCarNoPreview.remove(); selectedCarLabel.remove(); applyBar.remove()
			}
		}

		const otherTabs = tabContainer.querySelectorAll(".nav-list-item.customizer--tab")
		otherTabs.forEach((tab) => {
			tab.addEventListener("pointerup", () => {
				const title = tab.querySelector(".customizer--tab--label").textContent
				showCustomCarPage(false, title)
				tab.classList.add("customizer--tab--selected","is-current")
			})
		})

		populateList("","name_a-z")
		populateTrailList("","name_a-z")
		tabContainer.firstElementChild.before(customCarTab)
	}

	///////////////////
	//  Garage Page  //
	///////////////////
	else if (config?.car && window.location.pathname === "/garage") {
		const profileContainer = document.querySelector("section.profile"),
			profileObj = profileContainer ? findReact(profileContainer) : null
		if (!profileObj) { logging.error("Init")("Could not find profile container"); return }

		const style = document.createElement("style")
		style.appendChild(document.createTextNode(`
.nt-admin-panel-label-garage {
    position: absolute; bottom: 150px; left: calc((100% - 202px) / 2); z-index: -1;
    margin: 0 auto; padding: 2px 8px; border-radius: 4px; font-size: 12px;
    text-align: center; text-shadow: 0 2px 2px rgba(2,2,2,0.25); color: #fff; background-color: #136cac;
}`))
		document.head.appendChild(style)

		const car = NTGLOBALS.CARS.find((c) => c.id === config.car)
		if (!car) { logging.error("Init")("Custom Car setting is invalid"); return }

		const oldGetCarParamsFn = profileObj.getCarParams
		profileObj.getCarParams = function () {
			this.props.carID         = car.id
			this.props.carHueAngle   = config.hue || 0
			this.props.selectedTrail = config.trail ? NTGLOBALS.LOOT.find((l) => l.lootID===config.trail && l.type==="trail") : undefined
			return oldGetCarParamsFn()
		}
		profileObj.forceUpdate()

		const customCarText = document.createElement("div")
		customCarText.className = "nt-admin-panel-label-garage"
		customCarText.textContent = "Custom Car from Admin Panel"
		const targetElement = document.querySelector("div.profile--grid--center div")
		if (targetElement) targetElement.after(customCarText)
	}

	///////////////////
	//  Racing Page  //
	///////////////////
	else if (config?.car && (window.location.pathname === "/race" || window.location.pathname.startsWith("/race/"))) {
		const raceContainer = document.getElementById("raceContainer"),
			raceObj = raceContainer ? findReact(raceContainer) : null
		if (!raceObj) { logging.error("Init")("Could not find the race track"); return }

		const car = NTGLOBALS.CARS.find((c) => c.id === config.car)
		if (!car) { logging.error("Init")("Custom Car setting is invalid"); return }

		const style = document.createElement("style")
		style.appendChild(document.createTextNode(`.nt-admin-panel-label { position: absolute; right: -20px; top: 595px; z-index: 3; color: #383c4f; font-size: 10px; }`))
		document.head.appendChild(style)

		const customCarText = document.createElement("div")
		customCarText.className = "nt-admin-panel-label"
		customCarText.textContent = "CC"
		raceContainer.after(customCarText)

		const oldGameAddPlayer = raceObj.game.track.addPlayer
		raceObj.game.track.addPlayer = (e, r) => {
			if (e.isPlayer) {
				e.type       = !!car.options?.isAnimated ? car.key||car.assetKey||car.carID : car.carID
				e.isAnimated = !!car.options?.isAnimated
				e.hue        = config.hue || 0
				e.mods.trail = config.trail ? NTGLOBALS.LOOT.find((l)=>l.lootID===config.trail&&l.type==="trail")?.assetKey : undefined
			}
			oldGameAddPlayer(e, r)
		}

		raceObj.server.on("status", (e) => {
			if (e.status === "racing") {
				raceObj.state.racers = raceObj.state.racers.map((r) => {
					if (r.userID === raceObj.props.user.userID) {
						r.profile.carHueAngle = config.hue || 0
						r.profile.carID       = config.car
						if (config.trail) r.profile.selectedTrail = config.trail
					}
					return r
				})
			}
		})

		const racerPopupObserver = new MutationObserver((mutations) => {
			for (const m of mutations) {
				for (const node of m.addedNodes) {
					if (node.classList?.contains("pane--overlay")) {
						const reactObj = findReact(node, 1)
						if (!reactObj || reactObj.props.userID !== raceObj.props.user.userID) return
						reactObj.props.profile.carID       = config.car
						reactObj.props.profile.carHueAngle = config.hue
						if (config.trail) reactObj.props.profile.selectedTrail = config.trail
						reactObj.forceUpdate()
						return
					}
				}
			}
		})
		racerPopupObserver.observe(document.body, { childList: true })
	}
}