code:
/**
 *
 * @param {"cash"|"rep"} budgetType
 * @returns {HTMLDivElement}
 */
App.Budget.table = function(budgetType) {
	// Set up object to track calculated displays
	const income = (budgetType === "cash") ? "lastWeeksCashIncome" : "lastWeeksRepIncome";
	const expenses = (budgetType === "cash") ? "lastWeeksCashExpenses" : "lastWeeksRepExpenses";

	// Make the total 0 first, otherwise it gets counted as part of the new total
	V[income].Total = 0;
	V[income].Total = hashSum(V[income]);
	V[expenses].Total = 0;
	V[expenses].Total = hashSum(V[expenses]);

	const tableDiv = document.createElement("div");
	tableDiv.classList.add("budget");

	// HEADER
	generateHeader();

	// HEADER: FACILITIES
	createSectionHeader("Facilities");

	const F = App.Entity.facilities; // shortcut

	if (budgetType === "cash") {
		// PENTHOUSE
		addToggle(generateRowGroup("Penthouse", "PENTHOUSE"), [
			...generateRowCategory("Resting Slaves", "slaveAssignmentRest"),
			...generateRowCategory("Resting Vign", "slaveAssignmentRestVign"),
			...generateRowCategory("Fucktoys", "slaveAssignmentFucktoy"),
			...generateRowCategory("Taking Classes", "slaveAssignmentClasses"),
			...generateRowCategory("House Servants", "slaveAssignmentHouse"),
			...generateRowCategory("House Servant Vign", "slaveAssignmentHouseVign"),
			...generateRowCategory("Whores", "slaveAssignmentWhore"),
			...generateRowCategory("Whore Vign", "slaveAssignmentWhoreVign"),
			...generateRowCategory("Public Sluts", "slaveAssignmentPublic"),
			...generateRowCategory("Public Slut Vign", "slaveAssignmentPublicVign"),
			...generateRowCategory("Subordinate Slaves", "slaveAssignmentSubordinate"),
			...generateRowCategory("Milked", "slaveAssignmentMilked"),
			...generateRowCategory("MilkedVign", "slaveAssignmentMilkedVign"),
			...generateRowCategory("ExtraMilk", "slaveAssignmentExtraMilk"),
			...generateRowCategory("ExtraMilkVign", "slaveAssignmentExtraMilkVign"),
			...generateRowCategory("Gloryhole", "slaveAssignmentGloryhole"),
			...generateRowCategory("Confined Slaves", "slaveAssignmentConfinement")
		]);
		// Other
		generateRowCategory("Choosing Own Assignment", "slaveAssignmentChoice");

		// LEADERSHIP ROLES

		// HEAD GIRL
		// find passage name for HGSuite
		addToggle(generateRowGroup(F.headGirlSuite.nameCaps, "HEADGIRLSUITE"), [
			...generateRowCategory("Head Girl", "slaveAssignmentHeadgirl"),
			...generateRowCategory("Head Girl Fucktoys", "slaveAssignmentHeadgirlsuite")
		]);

		// RECRUITER
		addToggle(generateRowGroup("Recruiter", "RECRUITER"), [
			...generateRowCategory("Recruiter", "slaveAssignmentRecruiter")
		]);

		// BODYGUARD
		// find passage name for Armory
		addToggle(generateRowGroup(F.armory.nameCaps, "DOJO"), [
			...generateRowCategory("Bodyguard", "slaveAssignmentBodyguard")
		]);

		// CONCUBINE
		addToggle(generateRowGroup(F.masterSuite.nameCaps, "MASTERSUITE"), [
			...generateRowCategory("Master Suite Operation", "masterSuite"),
			...generateRowCategory("Master Suite Concubine", "slaveAssignmentConcubine"),
			...generateRowCategory("Master Suite Fucktoys", "slaveAssignmentMastersuite")
		]);

		// AGENT
		addToggle(generateRowGroup("Agent", "AGENT"), [
			...generateRowCategory("Agent", "slaveAssignmentAgent"),
			...generateRowCategory("Agent's Partner", "slaveAssignmentAgentPartner")
		]);

		// ARCADE
		addToggle(generateRowGroup(F.arcade.nameCaps, "ARCADE"), [
			...generateRowCategory("Arcade Operation", "arcade"),
			...generateRowCategory("Arcade Fuckdolls", "slaveAssignmentArcade")
		]);

		// BROTHEL
		addToggle(generateRowGroup(F.brothel.nameCaps, "BROTHEL"), [
			...generateRowCategory("Brothel Operation", "brothel"),
			...generateRowCategory("Brothel Madam", "slaveAssignmentMadam"),
			...generateRowCategory("Brothel MadamVign", "slaveAssignmentMadamVign"),
			...generateRowCategory("Brothel Whore", "slaveAssignmentBrothel"),
			...generateRowCategory("Brothel WhoreVign", "slaveAssignmentBrothelVign"),
			...generateRowCategory("Brothel Ads", "brothelAds")
		]);

		// CELLBLOCK
		addToggle(generateRowGroup(F.cellblock.nameCaps, "CELLBLOCK"), [
			...generateRowCategory("Cellblock Operation", "cellblock"),
			...generateRowCategory("Cellblock Warden", "slaveAssignmentWarden"),
			...generateRowCategory("Cellblock Slaves", "slaveAssignmentCellblock")
		]);

		// CLUB
		addToggle(generateRowGroup(F.club.nameCaps, "CLUB"), [
			...generateRowCategory("Club Operation", "club"),
			...generateRowCategory("Club DJ", "slaveAssignmentDj"),
			...generateRowCategory("Club DJVign", "slaveAssignmentDjVign"),
			...generateRowCategory("Club Sluts", "slaveAssignmentClub"),
			...generateRowCategory("Club Slut Vign", "slaveAssignmentClubVign"),
			...generateRowCategory("Club Ads", "clubAds")
		]);

		// CLINIC
		addToggle(generateRowGroup(F.clinic.nameCaps, "CLINIC"), [
			...generateRowCategory("Clinic Operation", "clinic"),
			...generateRowCategory("Clinic Nurse", "slaveAssignmentNurse"),
			...generateRowCategory("Clinic Slaves", "slaveAssignmentClinic")
		]);

		// DAIRY
		addToggle(generateRowGroup(F.dairy.nameCaps, "DAIRY"), [
			...generateRowCategory("Dairy Operation", "dairy"),
			...generateRowCategory("Dairy Milkmaid", "slaveAssignmentMilkmaid"),
			...generateRowCategory("Dairy Cows", "slaveAssignmentDairy"),
			...generateRowCategory("Dairy CowsVign", "slaveAssignmentDairyVign")
		]);

		// FARMYARD
		addToggle(generateRowGroup(F.farmyard.nameCaps, "FARMYARD"), [
			...generateRowCategory("Farmyard Operation", "farmyard"),
			...generateRowCategory("Farmyard Farmer", "slaveAssignmentFarmer"),
			...generateRowCategory("Farmyard Farmhands", "slaveAssignmentFarmyard"),
			...generateRowCategory("Farmyard FarmhandsVign", "slaveAssignmentFarmyardVign")
		]);

		// INCUBATOR
		addToggle(generateRowGroup(F.incubator.nameCaps, "INCUBATOR"), [
			...generateRowCategory("Incubator Operation", "incubator"),
			...generateRowCategory("Incubator Babies", "incubatorSlaves")
		]);

		// NURSERY
		addToggle(generateRowGroup(F.nursery.nameCaps, "NURSERY"), [
			...generateRowCategory("Nursery Operation", "nursery"),
			...generateRowCategory("Nursery Matron", "slaveAssignmentMatron"),
			...generateRowCategory("Nursery Nannies", "slaveAssignmentNursery"),
			...generateRowCategory("Nursery NanniesVign", "slaveAssignmentNurseryVign")
		]);

		// PIT
		addToggle(generateRowGroup(F.pit.nameCaps, "PIT"), [
			...generateRowCategory("Pit Operation", "pit")
		]);

		// PROSTHETIC LAB
		addToggle(generateRowGroup("Prosthetic Lab", "PROSTHETICLAB"), [
			...generateRowCategory("Prosthetic Lab Operation", "lab"),
			...generateRowCategory("Prosthetic Lab Research", "labResearch"),
			...generateRowCategory("Prosthetic Lab Scientists", "labScientists"),
			...generateRowCategory("Prosthetic Lab Menials", "labMenials")
		]);

		// SCHOOLROOM
		addToggle(generateRowGroup(F.schoolroom.nameCaps, "SCHOOLROOM"), [
			...generateRowCategory("Schoolroom Operation", "school"),
			...generateRowCategory("Schoolroom Teacher", "slaveAssignmentTeacher"),
			...generateRowCategory("Schoolroom Students", "slaveAssignmentSchool")
		]);

		// SERVANTS' QUARTERS
		addToggle(generateRowGroup(F.servantsQuarters.nameCaps, "SERVANTSQUARTERS"), [
			...generateRowCategory("Servants' Quarters Operation", "servantsQuarters"),
			...generateRowCategory("Servants' Quarters Steward", "slaveAssignmentSteward"),
			...generateRowCategory("Servants' Quarters Servants", "slaveAssignmentQuarter"),
			...generateRowCategory("Servants' Quarters ServantsVign", "slaveAssignmentQuarterVign")
		]);

		// SPA
		addToggle(generateRowGroup(F.spa.nameCaps, "SPA"), [
			...generateRowCategory("Spa Operation", "spa"),
			...generateRowCategory("Spa Attendant", "slaveAssignmentAttendant"),
			...generateRowCategory("Spa Slaves", "slaveAssignmentSpa")
		]);

		// HEADER: ARCOLOGY
		createSectionHeader("Arcology");

		// SLAVES
		addToggle(generateRowGroup("Slave Miscellaneous", "SLAVES"), [
			...generateRowCategory("Slave Porn", "porn"),
			...generateRowCategory("Slave Modifications", "slaveMod"),
			...generateRowCategory("Slave Surgery", "slaveSurgery"),
			...generateRowCategory("Slave Birthing", "birth")
		]);

		// MENIAL LABOR
		addToggle(generateRowGroup("Menial Labor", "LABOR"), [
			...generateRowCategory("Menials: Slaves", "menialTrades"),
			...generateRowCategory("Menials: Fuckdolls", "fuckdolls"),
			...generateRowCategory("Menials: Bioreactors", "menialBioreactors")
		]);

		// FLIPPING
		addToggle(generateRowGroup("Flipping", "FLIPPING"), [
			...generateRowCategory("Slave Transfer", "slaveTransfer"),
			...generateRowCategory("Indenture Renewal", "indentureRenewal"),
			...generateRowCategory("Menials", "menialTransfer"),
			...generateRowCategory("Fuckdolls", "fuckdollsTransfer"),
			...generateRowCategory("Bioreactors", "menialBioreactorsTransfer"),
			...generateRowCategory("Assistant: Menials", "menialTransferA"),
			...generateRowCategory("Assistant: Fuckdolls", "fuckdollsTransferA"),
			...generateRowCategory("Assistant: Bioreactors", "menialBioreactorsTransferA"),
			...generateRowCategory("Menial Retirement", "menialRetirement"),
			...generateRowCategory("Scientist Transfer", "labScientistsTransfer"),
			...generateRowCategory("Slave Babies", "babyTransfer")
		]);

		// FINANCIALS
		addToggle(generateRowGroup("Financials", "FINANCIALS"), [
			...generateRowCategory("Weather", "weather"),
			...generateRowCategory("Rents", "rents"),
			...generateRowCategory("Fines", "fines"),
			...generateRowCategory("Events", "event"),
			...generateRowCategory("Capital Expenses", "capEx"),
			...generateRowCategory("Future Society Shaping", "futureSocieties"),
			...generateRowCategory("School Subsidy", "schoolBacking"),
			...generateRowCategory("Arcology conflict", "war"),
			...generateRowCategory("Cheating", "cheating")
		]);

		// POLICIES
		addToggle(generateRowGroup("Policies", "POLICIES"), [
			...generateRowCategory("Policies", "policies"),
			...generateRowCategory("Subsidies and Barriers", "subsidiesAndBarriers")
		]);

		// EDICTS
		addToggle(generateRowGroup("Edicts", "EDICTS"), [
			...generateRowCategory("Edicts", "edicts")
		]);

		// PERSONAL FINANCE
		addToggle(generateRowGroup("Personal Finance", "PERSONALFINANCE"), [
			...generateRowCategory("Personal Business", "personalBusiness"),
			...generateRowCategory("Personal Living Expenses", "personalLivingExpenses"),
			...generateRowCategory("Your skills", "PCSkills"),
			...generateRowCategory("Your training expenses", "PCtraining"),
			...generateRowCategory("Your food expenses", "PCdiet"),
			...generateRowCategory("Your drug expenses", "PCdrugs"),
			...generateRowCategory("Your medical expenses", "PCmedical"),
			...generateRowCategory("Your cosmetic expenses", "PCcosmetics"),
			...generateRowCategory("Citizen Orphanage", "citizenOrphanage"),
			...generateRowCategory("Private Orphanage", "privateOrphanage"),
			...generateRowCategory("Stock dividends", "stocks"),
			...generateRowCategory("Stock trading", "stocksTraded")
		]);

		// SECURITY
		addToggle(generateRowGroup("Security", "SECURITY"), [
			...generateRowCategory("Mercenaries", "mercenaries"),
			...generateRowCategory("Security Expansion", "securityExpansion"),
			...generateRowCategory("Special Forces", "specialForces"),
			...generateRowCategory("Special Forces Capital Expenses", "specialForcesCap"),
			...generateRowCategory("Peacekeepers", "peacekeepers")
		]);
	} else if (budgetType === "rep") {
		// PENTHOUSE
		addToggle(generateRowGroup("Penthouse", "PENTHOUSE"), [
			...generateRowCategory("Fucktoys", "fucktoy"),
			...generateRowCategory("Public servants", "publicServant"),
			...generateRowCategory("Free glory holes", "gloryhole"),
			...generateRowCategory("Concubine", "concubine"),
			...generateRowCategory("Head girl", "headGirl"),
			...generateRowCategory("Bodyguard", "bodyguard"),
			...generateRowCategory("Recruiter", "recruiter"),
		]);

		// ARCADE
		addToggle(generateRowGroup(F.arcade.nameCaps, "ARCADE"), [
			...generateRowCategory("Arcade Operation", "arcade"),
			...generateRowCategory("Free arcade", "gloryholeArcade")
		]);

		// BROTHEL
		addToggle(generateRowGroup(F.brothel.nameCaps, "BROTHEL"), [
			...generateRowCategory("Brothel Operation", "brothel"),
		]);

		// CLUB
		addToggle(generateRowGroup(F.club.nameCaps, "CLUB"), [
			...generateRowCategory("Club Operation", "club"),
			...generateRowCategory("Club servants", "publicServantClub"),
			...generateRowCategory("Club ads", "clubAds"),
		]);

		// PIT
		addToggle(generateRowGroup(F.pit.nameCaps, "PIT"), [
			...generateRowCategory("Pit Operation", "pit")
		]);

		// SERVANTS' QUARTERS
		addToggle(generateRowGroup(F.servantsQuarters.nameCaps, "SERVANTSQUARTERS"), [
			...generateRowCategory("Servants' Quarters Operation", "servantsQuarters"),
		]);

		// SPA
		addToggle(generateRowGroup(F.spa.nameCaps, "SPA"), [
			...generateRowCategory("Spa Operation", "spa"),
		]);

		// FARMYARD
		addToggle(generateRowGroup(F.farmyard.nameCaps, "FARMYARD"), [
			...generateRowCategory("Shows", "shows"),
		]);

		// HEADER: ARCOLOGY
		createSectionHeader("Arcology");

		// SLAVES
		addToggle(generateRowGroup("Slave Miscellaneous", "SLAVES"), [
			...generateRowCategory("Slave trust and devotion", "slavesViewOfPC"),
			...generateRowCategory("Prestigious slaves", "prestigiousSlave"),
			...generateRowCategory("Porn", "porn"),
			...generateRowCategory("Selling/buying major slaves", "slaveTransfer"),
			...generateRowCategory("Slave surgery", "babyTransfer"),
			...generateRowCategory("Birth", "birth"),
			...generateRowCategory("Slave retirement", "retirement"),
			...generateRowCategory("Vignettes", "vignette"),
			...generateRowCategory("Free Fuckdolls", "fuckdolls"),
		]);

		// POLICIES
		addToggle(generateRowGroup("Policies", "POLICIES"), [
			...generateRowCategory("Capital expenses", "capEx"),
			...generateRowCategory("Subsidies and Barriers", "subsidiesAndBarriers"),
			...generateRowCategory("Society shaping", "futureSocieties"),
			...generateRowCategory("Food", "food"),
		]);

		// EDICTS
		addToggle(generateRowGroup("Edicts", "EDICTS"), [
			...generateRowCategory("Edicts", "edicts")
		]);

		// PERSONAL FINANCE
		addToggle(generateRowGroup("Personal Finance", "PERSONALFINANCE"), [
			...generateRowCategory("Personal Business", "personalBusiness"),
			...generateRowCategory("Your appearance", "PCappearance"),
			...generateRowCategory("Your actions", "PCactions"),
			...generateRowCategory("Your skills", "PCRelationships"),
			...generateRowCategory("Slave relationships", "SlaveRelationships"),
			...generateRowCategory("Events", "event"),
		]);

		// SECURITY
		addToggle(generateRowGroup("Security", "SECURITY"), [
			...generateRowCategory("Security Expansion", "securityExpansion"),
			...generateRowCategory("Special Forces", "specialForces"),
			...generateRowCategory("Conflict", "war"),
			...generateRowCategory("Peacekeepers", "peacekeepers")
		]);

		addToggle(generateRowGroup("Waste", "WASTE"), [
			...generateRowCategory("Reputation decay", "multiplier"),
			...generateRowCategory("Overflow (your reputation cannot be higher than 20k)", "overflow"),
			...generateRowCategory("Income curve", "curve")
		]);
	}

	// BUDGET REPORT
	generateSummary();

	return tableDiv;

	function generateHeader() {
		App.UI.DOM.appendNewElement("div", tableDiv, (budgetType === "cash") ? "Budget Overview" : "Reputation Overview", ["header"]);
		App.UI.DOM.appendNewElement("div", tableDiv, "Income", ["header"]);
		App.UI.DOM.appendNewElement("div", tableDiv, "Expense", ["header"]);
		App.UI.DOM.appendNewElement("div", tableDiv, "Totals", ["header"]);
	}

	function generateSummary() {
		createSectionHeader("Summary Report");

		App.UI.DOM.appendNewElement("div", tableDiv, "Tracked totals");

		App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(Math.trunc(V[income].Total)), ["number",
			"final-result"]);
		App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(Math.trunc(V[expenses].Total)),
			["number", "final-result"]);
		App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(Math.trunc(V[income].Total + V[expenses].Total)),
			["number", "final-result"]);

		if (budgetType === "cash") {
			App.UI.DOM.appendNewElement("div", tableDiv, `Expenses budget for week ${V.week + 1}`);
			tableDiv.append(document.createElement("div"));
			App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(-V.costs), ["number"]);
			tableDiv.append(document.createElement("div"));
		}

		App.UI.DOM.appendNewElement("div", tableDiv, `Last week actuals`);
		tableDiv.append(document.createElement("div"));
		tableDiv.append(document.createElement("div"));
		if (budgetType === "cash") {
			App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(V.cash - V.cashLastWeek), ["number"]);
		} else {
			App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(V.rep - V.repLastWeek), ["number"]);
		}

		if (
			(budgetType === "cash" && (V.cash - V.cashLastWeek) === (V.lastWeeksCashIncome.Total + V.lastWeeksCashExpenses.Total)) ||
			(budgetType === "rep" && (V.rep - V.repLastWeek) === (V.lastWeeksRepIncome.Total + V.lastWeeksRepExpenses.Total))
		) {
			App.UI.DOM.appendNewElement("div", tableDiv, `The books are balanced, ${properTitle()}!`, ["green",
				"last-row"]);
			tableDiv.append(document.createElement("div"));
			tableDiv.append(document.createElement("div"));
			tableDiv.append(document.createElement("div"));
		} else {
			App.UI.DOM.appendNewElement("div", tableDiv, "Transaction tracking off by", ["red", "last-row"]);
			tableDiv.append(document.createElement("div"));
			tableDiv.append(document.createElement("div"));
			if (budgetType === "cash") {
				App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(
					(V.cash - V.cashLastWeek) - (V.lastWeeksCashIncome.Total + V.lastWeeksCashExpenses.Total)
				), ["number"]);
			} else {
				App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(
					(V.rep - V.repLastWeek) - (V.lastWeeksRepIncome.Total + V.lastWeeksRepExpenses.Total)
				), ["number"]);
			}
		}
	}

	function createSectionHeader(text) {
		App.UI.DOM.appendNewElement("div", tableDiv, text, ["section"]);
		App.UI.DOM.appendNewElement("div", tableDiv, null, ["section"]);
		App.UI.DOM.appendNewElement("div", tableDiv, null, ["section"]);
		App.UI.DOM.appendNewElement("div", tableDiv, null, ["section"]);
	}

	function generateRowCategory(node, category) {
		const r = [];
		if (V[income][category] || V[expenses][category] || V.showAllEntries[budgetType === "cash" ? "costsBudget" : "repBudget"]) {
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, node, ["entry"]));
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(V[income][category]),
				["number", "entry"]));
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(-Math.abs(V[expenses][category])),
				["number", "entry"]));
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(V[income][category] + V[expenses][category]),
				["number", "entry"]));
		}
		return r;
	}

	function generateRowGroup(title, name) {
		/** @type {string[]} */
		const members = (budgetType === "cash" ? CategoryAssociatedGroup[name] : CategoryAssociatedGroupRep[name]);
		const groupIn = members.map((k) => V[income][k]).reduce((acc, cur) => acc + cur);
		const groupEx = members.map((k) => V[expenses][k]).reduce((acc, cur) => acc + cur);

		const r = [];
		if (groupIn || groupEx || V.showAllEntries[budgetType === "cash" ? "costsBudget" : "repBudget"]) {
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, title, ["group"]));
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(groupIn), ["group", "number"]));
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(groupEx), ["group", "number"]));
			r.push(App.UI.DOM.appendNewElement("div", tableDiv, formatNumber(groupIn + groupEx),
				["group", "number"]));
		}
		return r;
	}

	/**
	 * @param {Array<HTMLDivElement>} heads
	 * @param {Array<HTMLDivElement>} content
	 */
	function addToggle(heads, content) {
		heads = heads.filter(e => !!e);
		if (heads.length === 0) {
			return;
		}
		content = content.filter(e => !!e);
		if (content.length === 0) {
			return;
		}
		// Based on App.UI.DOM.elementToggle(), which only allows one toggle element
		for (let htmlElement of heads) {
			htmlElement.classList.add("accordion", "closed");
		}
		for (let htmlElement of content) {
			htmlElement.classList.add("accordion-content", "hidden");
		}
		const toggle = () => {
			for (let htmlElement of heads) {
				htmlElement.classList.toggle("closed");
			}
			for (let htmlElement of content) {
				htmlElement.classList.toggle("hidden");
			}
		};
		for (let htmlElement of heads) {
			htmlElement.onclick = toggle;
		}
	}

	function formatNumber(num, invert = false) {
		if (invert) {
			num = -1 * num;
		}
		let span = document.createElement('span');
		span.append(budgetType === "cash" ? cashFormat(num) : repFormat(num, V.assistant.power < 3));
		if (num === 0) {
			// num overwrites gray, so we don't use it here.
			span.classList.add("gray");
		} else {
			span.classList.add((budgetType === "cash") ? "cash" : "reputation");
			// Display red if the value is negative, unless invert is true
			if (num < 0) {
				span.classList.add("dec");
				// Yellow for positive
			} else if (num > 0) {
				span.classList.add("inc");
				// Gray for exactly zero
			}
		}
		return span;
	}
};


code:
/**
 * Costs Budget Passage
 * @returns {DocumentFragment}
 */
App.Budget.costs = function() {
	const frag = new DocumentFragment();

	App.UI.DOM.appendNewElement("h1", frag, `Budget`);

	frag.append(
		intro(),
		loans(),
		netWorth(),
	);
	if (V.difficultySwitch === 1) {
		frag.append(economy());
	}
	frag.append(settings());

	// Table of Totals
	if (!V.lastWeeksCashIncome) {
		App.UI.DOM.appendNewElement("p", frag, "Financial data currently unavailable.");
	} else {
		App.UI.DOM.appendNewElement("p", frag, App.Budget.table("cash"));
	}

	errors(frag);
	return frag;

	/**
	 * @returns {DocumentFragment}
	 */
	function loans() {
		const frag = new DocumentFragment();

		frag.append(App.Budget.loans());

		return frag;
	}

	/**
	 * @returns {HTMLParagraphElement}
	 */
	function intro() {
		return App.UI.DOM.makeElement("p", `Here you can view many of the financial details of your arcology, ${properTitle()}. Proper cash flow is critical to the success of your long term goals. Find expensive waste here and you can change the right policies or sell off slackers. Find your next profit center and invest in new equipment, advertising, or flesh to maximize your assets.`, ["scene-intro"]);
	}

	/**
	 * @returns {DocumentFragment}
	 */
	function netWorth() {
		const frag = new DocumentFragment();

		frag.append(
			App.UI.DOM.makeElement("h2", `Net worth`),
			`You have a total net worth of ${cashFormat(Math.trunc(App.Utils.totalNetWorth()))}.`
		);

		if (V.debugMode) {
			App.UI.DOM.appendNewElement("div", frag, App.UI.DOM.link(`Recalculate net worth`, () => {
				App.UI.reload();
			}), ['indent']);
		}

		return frag;
	}

	/**
	 * @returns {DocumentFragment}
	 */
	function economy() {
		const frag = new DocumentFragment();

		App.UI.DOM.appendNewElement("h2", frag, `Economy`);

		const p = document.createElement("p");

		App.UI.DOM.appendNewElement("div", p, `The Local Economy score effects some prices in your ecology. The lower the score, the higher the prices. The base score is 100.`, ["detail"]);

		const grid = document.createElement("div");
		grid.className = "grid-2columns-auto";

		App.UI.DOM.appendNewElement("div", grid, "Global Economy", ["cash"]);
		if (V.cheatMode && V.cheatModeM) {
			const div = document.createElement("div");
			div.append(App.UI.DOM.makeTextBox(V.economy, v => {
				V.economy = v;
				V.cheater = 1;
			}, true));
			grid.append(div);
		} else {
			App.UI.DOM.appendNewElement("div", grid, String(V.economy));
		}

		App.UI.DOM.appendNewElement("div", grid, "Local Economy", ["cash"]);
		if (V.cheatMode && V.cheatModeM) {
			const div = document.createElement("div");
			div.append(App.UI.DOM.makeTextBox(V.localEcon, v => {
				V.localEcon = v;
				V.cheater = 1;
			}, true));
			grid.append(div);
		} else {
			App.UI.DOM.appendNewElement("div", grid, String(V.localEcon));
		}

		p.append(grid);

		const r = [];
		r.push("The current score is");
		if (V.localEcon > 100) {
			let econPercent = Math.trunc(1000 - 100000 / V.localEcon) / 10;
			r.push(`reducing prices by <span class="cash inc">${econPercent}%.</span>`);
		} else if (V.localEcon === 100) {
			r.push("equal to the base score. There are no price modifications.");
		} else {
			let econPercent = Math.trunc(100000 / V.localEcon - 1000) / 10;
			r.push(`increasing prices by <span class="cash dec">${econPercent}%.</span>`);
		}
		$(p).append(...App.Events.spaceSentences(r));

		frag.append(p);

		return frag;
	}

	/**
	 * @returns {HTMLParagraphElement}
	 */
	function settings() {
		const p = document.createElement("p");
		App.UI.DOM.appendNewElement("h2", p, `Weekly costs`);

		let options = new App.UI.OptionsGroup();
		options.addOption("", "costsBudget", V.showAllEntries)
			.addValue("Normal", 0).on().addValue("Show Empty Entries", 1);
		p.append(options.render());

		return p;
	}

	/**
	 * @param {DocumentFragment} container
	 */
	function errors(container) {
		if (V.lastWeeksCashErrors.length > 0) {
			const p = document.createElement("p");
			p.append(App.UI.DOM.passageLink("Reset", "Costs Budget",
				() => { V.lastWeeksCashErrors = []; }));
			App.UI.DOM.appendNewElement("div", p, "Errors:", ["error"]);
			for (const error of V.lastWeeksCashErrors) {
				App.UI.DOM.appendNewElement("div", p, error, ["error"]);
			}
			container.append(p);
		}
	}
};

repo_name: Arkerthan/fc

license: mit


code:
App.Budget.loans = function() {
	/** @typedef {('bank'|'shark')} Lender */

	const frag = new DocumentFragment();

	const loan = (/** @type {Lender} */ name) => V.loans.find(loan => loan.name === name);

	App.UI.DOM.appendNewElement("h2", frag, `Loans`);

	if (V.loans.length) {
		frag.append(pay());
	}

	frag.append(
		bank(),
		loanshark(),
	);

	return frag;

	function pay() {
		const div = document.createElement("div");
		const links = [];
		const text = [];

		text.push(`You`);
		if (loan('bank')) {
			text.push(`still owe the bank ${cashFormat(Math.trunc(loan('bank').full))}`);
			if (loan('shark')) {
				text.push(`and`);
			}
		}
		if (loan('shark')) {
			text.push(`have ${years(loan('shark').deadline - V.week)} to pay the shark back ${cashFormat(Math.trunc(loan('shark').full))}`);
		}
		text.push(text.pop() + `.`);
		text.push(`You can pay off your loans here.`);

		if (loan('bank')) {
			const bank = loan('bank');
			if (V.cash > bank.full) {
				links.push(App.UI.DOM.link(`Pay off your bank loan`, () => {
					cashX(forceNeg(bank.full), "loan");
					V.loans.delete(bank);

					App.UI.reload();
				}));
			} else {
				links.push(App.UI.DOM.disabledLink(`Pay off your bank loan`, [
					`You lack the necessary funds to pay off this loan`,
				]));
			}
		}
		if (loan('shark')) {
			const shark = loan('shark');
			if (V.cash > shark.full) {
				links.push(App.UI.DOM.link(`Pay off the loanshark`, () => {
					cashX(forceNeg(shark.full), "loan");
					V.loans.delete(shark);

					App.UI.reload();
				}));
			} else {
				links.push(App.UI.DOM.disabledLink(`Pay off the loanshark`, [
					`You lack the necessary funds to pay off this loan`,
				]));
			}
		}

		div.append(text.join(' '));
		App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);

		return div;
	}

	function bank() {
		const div = document.createElement("div");
		const values = [10000, 100000, 1000000];
		const disabledReasons = [];
		const links = [];

		div.append(`You can take out a loan from one of the credit unions in the Free City, if you have the reputation for them to trust you.`);
		App.UI.DOM.appendNewElement("div", div,
			`If for any reason you lack the credits, sectors of ${V.arcologies[0].name} will be used in lieu of payment.`,
			["note", "indent"]);

		if (V.rep < 10000) {
			disabledReasons.push(`You need at least ${num(10000)} reputation to take a loan from this lender.`);
		}
		if (loan('bank')) {
			disabledReasons.push(`You have already taken out a loan from this lender.`);
		}

		if (disabledReasons.length === 0) {
			for (const amount of values) {
				const loan = createLoan("bank", amount);
				const f = new DocumentFragment();
				f.append(App.UI.DOM.link(cashFormat(amount), () => {
					V.loans.push(loan);
					cashX(amount, "loan");
					App.UI.reload();
				}));
				const info = new DocumentFragment();
				App.Events.addNode(info, [`${cashFormatColor(loan.full)} over ${num(loan.deadline - V.week)} weeks`]);
				f.append(" – ", App.UI.DOM.spanWithTooltip(info, terms("bank", loan)));
				links.push(f);
			}
		} else {
			values.map(val => links.push(App.UI.DOM.disabledLink(cashFormat(val), disabledReasons)));
		}

		App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);

		return div;
	}

	function loanshark() {
		const div = document.createElement("div");
		const values = [10000, 100000, 1000000];
		const disabledReasons = [];
		const links = [];

		div.append(`If you're not quite reputable enough, you can also borrow money from one of the local loan-sharks in the area.`);
		App.UI.DOM.appendNewElement("div", div,
			`If for any reason you miss a payment the lender will send his men to collect – forcibly, if necessary.`,
			["note", "indent"]);

		if (V.rep < 2000) {
			disabledReasons.push(`You need at least ${num(2000)} reputation to take a loan from this lender.`);
		}
		if (loan('shark')) {
			disabledReasons.push(`You have already taken out a loan from this lender.`);
		}

		if (disabledReasons.length === 0) {
			for (const amount of values) {
				const loan = createLoan("shark", amount);
				const f = new DocumentFragment();
				f.append(App.UI.DOM.link(cashFormat(amount), () => {
					V.loans.push(loan);
					cashX(amount, "loan");

					App.UI.reload();
				}));
				const info = new DocumentFragment();
				App.Events.addNode(info, [`${cashFormatColor(loan.full)} after ${num(loan.deadline - V.week)} weeks`]);
				f.append(" – ", App.UI.DOM.spanWithTooltip(info, terms("shark", loan)));
				links.push(f);
			}
		} else {
			values.map(val => links.push(App.UI.DOM.disabledLink(cashFormat(val), disabledReasons)));
		}

		App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);

		return div;
	}

	/**
	 * @param {"bank"|"shark"}lender
	 * @param {number} amount
	 * @returns {FC.Loan}
	 */
	function createLoan(lender, amount) {
		const term = Math.max(amount / 50000, 4);
		const apr = Math.max(Math.abs((V.rep - 20000) / 500), 5);
		let interest;
		let installments;
		if (lender === "shark") {
			interest = (amount * (term / 52) * (apr / 100)) * 3;
			installments = 1;
		} else {
			interest = amount * (term / 52) * (apr / 100);
			installments = term;
		}
		const full = amount + interest;

		return {
			name: 'shark',
			principal: amount,
			deadline: V.week + term,
			installments,
			apr,
			interest,
			full: Math.trunc(full),
		};
	}

	/**
	 * @param {Lender} lender
	 * @param {FC.Loan} loan
	 */
	function terms(lender, loan) {
		if (lender === 'bank') {
			return `You will pay about ${cashFormat(Math.trunc(loan.full / (loan.deadline - V.week)))} per week for ${num(loan.deadline - V.week)} weeks until you have paid off the entire balance. You will end up paying back about ${cashFormat(Math.trunc(loan.full))} after interest.`;
		} else {
			return `You will have ${num(loan.deadline - V.week)} weeks to pay off the full amount. You will end up paying back about ${cashFormat(Math.trunc(loan.full))} after interest.`;
		}
	}
};


code:
/**
 * Reputation budget passage
 * @returns {DocumentFragment}
 */
App.Budget.rep = function() {
	const f = new DocumentFragment();

	// App.UI.DOM.appendNewElement("h1", f, `Reputation Budget`);

	f.append(intro());
	f.append(settings());
	// Table of Totals
	if (!V.lastWeeksRepIncome) {
		App.UI.DOM.appendNewElement("p", f, "Reputation data currently unavailable.");
	} else {
		App.UI.DOM.appendNewElement("p", f, App.Budget.table("rep"));
	}

	errors(f);
	return f;

	/**
	 * @returns {HTMLParagraphElement}
	 */
	function intro() {
		const p = document.createElement("p");
		p.classList.add("scene-intro");
		p.append(`Reputation is a difficult thing to quantify, ${properTitle()}. Here you see an overview of topics that interest people in the arcology, and in turn, reflect on your own reputation. Much like a finance report, you can see here how your choices last week moved you closer to or further from your goals, and head any issues off before they get worse.`);
		if (V.assistant.power >= 3) {
			p.append(" Thanks to the arcology's powerful power computer core it is possible to track reputation changes to an unprecedented degree.");
		}
		return p;
	}

	/**
	 * @returns {HTMLParagraphElement}
	 */
	function settings() {
		const p = document.createElement("p");
		App.UI.DOM.appendNewElement("div", p, "Your weekly reputation changes are as follows:", ["detail"]);

		let options = new App.UI.OptionsGroup();
		options.addOption("", "repBudget", V.showAllEntries)
			.addValue("Normal", 0).on().addValue("Show Empty Entries", 1);
		p.append(options.render());

		return p;
	}

	/**
	 * @param {DocumentFragment} container
	 */
	function errors(container) {
		if (V.lastWeeksRepErrors.length > 0) {
			const p = document.createElement("p");
			p.append(App.UI.DOM.passageLink("Reset", "Rep Budget",
				() => { V.lastWeeksRepErrors = []; }));
			App.UI.DOM.appendNewElement("div", p, "Errors:", "error");
			for (const error of V.lastWeeksRepErrors) {
				App.UI.DOM.appendNewElement("div", p, error, "error");
			}
			container.append(p);
		}
	}
};


code:
App.UI.Cheat.PCCheatMenu = function() {
	const node = new DocumentFragment();

	App.UI.DOM.appendNewElement("h2", node, "Cheat Editing Player Character");

	App.UI.DOM.appendNewElement("div", node, App.UI.DOM.passageLink(
		"Cancel",
		"Manage Personal Affairs",
		() => {
			V.PC = clone(V.backupSlave);
		}
	));

	const tabBar = new App.UI.Tabs.TabBar("PCCheatMenu");
	tabBar.addTab("Player", "player", App.UI.Player.design());
	tabBar.addTab("Family", "family", App.Intro.editFamily(V.PC, true));
	node.append(tabBar.render());

	return node;
};


code:
App.UI.Cheat.PCCheatMenuCheatDatatypeCleanup = function() {
	delete V.backupSlave;

	App.Entity.Utils.PCCheatCleanup();

	return App.Events.makeNode([`You perform the dark rituals, pray to the dark gods, and sell your soul for the power to reshape your body and life at will. What a cheater!`]);
};


code:
App.UI.Cheat.arcologyCheatDatatypeCleanup = function() {
	const node = new DocumentFragment();

	App.Update.playerArcologyDatatypeCleanup();
	App.Update.playerFSDatatypeCleanup();

	App.UI.DOM.appendNewElement("p", node, "The chanting grows louder and louder, CHEAT! CHEAT! CHEAT!");
	App.UI.DOM.appendNewElement("p", node, "The Arcology morphs and changes shape, CHEAT! CHEAT! CHEAT!");
	App.UI.DOM.appendNewElement("p", node, "The Arcology has been changed forever and you forfeit your CHEATING CHEATY SOUL!");

	return node;
};


code:
/**
 * @param {number} num
 * @returns {DocumentFragment}
 */
App.UI.Cheat.arcology = function(num) {
	V.cheater = 1;
	const el = new DocumentFragment();
	const arc = V.arcologies[num];
	if (arc.rival) {
		el.append(`You cannot cheat edit your rival's arcology at the moment.`);
	} else {
		const tabBar = new App.UI.Tabs.TabBar("CheatArcology");
		tabBar.addTab("Cheat", "cheat", cheat());
		tabBar.addTab("Future societies", "fs", fs());
		el.append(tabBar.render());
	}

	if (num === 0) {
		if (FutureSocieties.isActive('FSMaturityPreferentialist', arc)) {
			if (V.idealAge < 30) {
				V.idealAge = 30;
			}
			if (V.targetIdealAge < 30) {
				V.targetIdealAge = 30;
			}
			V.policies.idealAge = 1;
		} else if (FutureSocieties.isActive('FSYouthPreferentialist', arc)) {
			if (V.idealAge >= 30) {
				V.idealAge = 29;
			}
			if (V.targetIdealAge >= 30) {
				V.targetIdealAge = 29;
			}
			if (V.targetIdealAge !== 18) {
				V.policies.idealAge = 1;
			}
		}
	}

	return el;

	function cheat() {
		const el = new DocumentFragment();
		let option;
		const options = new App.UI.OptionsGroup();
		options.addOption("Name", "name", arc).showTextBox();
		option = options.addOption("Direction", "direction", arc);
		const compass = ["east", "north", "northeast", "northwest", "south", "southeast", "southwest", "west"];
		if (num > 0) {
			option.addValueList(compass).pulldown();
		}
		options.addOption("government", "government", arc);
		if (num > 0) {
			option.addValueList(["a committee", "a corporation", "an individual", "an oligarchy", "direct democracy", "elected officials"]);
		}
		options.addOption("Leader ID", "leaderID", arc);
		if (num > 0) {
			option.showTextBox();
		}
		options.addOption("Honeymoon", "honeymoon", arc).showTextBox();
		options.addOption("Prosperity", "prosperity", arc).showTextBox();
		options.addOption("Ownership", "ownership", arc).showTextBox();
		options.addOption("Minority", "minority", arc).showTextBox();
		options.addOption("PC minority", "PCminority", arc).showTextBox();
		options.addOption("Demand factor", "demandFactor", arc).showTextBox();
		options.addOption("Embargo", "embargo", arc).showTextBox();
		option = options.addOption("Embargo target", "embargoTarget", arc)
			.addValue("none", -1).off()
			.addValueList(compass).pulldown();
		if (num !== 0) {
			option.addValue("player", 0);
		}
		option = options.addOption("Influence target", "influenceTarget", arc)
			.addValue("none", -1).off()
			.addValueList(compass).pulldown();
		if (num !== 0) {
			option.addValue("player", 0);
		}
		options.addOption("Influence bonus", "influenceBonus", arc).showTextBox();
		options.addOption("Cyber economic", "CyberEconomic", arc).showTextBox();
		option = options.addOption("Cyber economic target", "CyberEconomicTarget", arc)
			.addValue("none", -1).off()
			.addValueList(compass).pulldown();
		if (num !== 0) {
			option.addValue("player", 0);
		}
		options.addOption("Cyber reputation", "CyberReputation", arc).showTextBox();
		option = options.addOption("Cyber reputation target", "CyberReputationTarget", arc)
			.addValue("none", -1).off()
			.addValueList(compass).pulldown();
		if (num !== 0) {
			option.addValue("player", 0);
		}

		const rivalArc = V.arcologies.find((a) => a.rival === 1);
		if (arc.rival === 0 && rivalArc) {
			options.addCustom(`Sorry, you already have a rival in ${rivalArc.name}`);
		} else {
			options.addOption("Rival", "rival", arc)
				.addValue("Yes", 1).on()
				.addValue("No", 0).off();
		}
		if (arc.hasOwnProperty("childhoodFertilityInducedNCSResearch")) {
			options.addOption("Childhood fertility induced NCS research", "childhoodFertilityInducedNCSResearch", arc).showTextBox();
		}
		el.append(options.render());
		return el;
	}

	function fs() {
		const el = new DocumentFragment();
		let option;
		const options = new App.UI.OptionsGroup();
		for (const FS of Object.keys(App.Data.FutureSociety.records)) {
			if (arc.hasOwnProperty(FS)) {
				option = options.addOption(App.Data.FutureSociety.records[FS].noun, FS, arc)
					.addValue("Unset", null);
				if (typeof arc[FS] === "number") {
					option.showTextBox();
				} else {
					option.addValue("Set", 0);
				}

				if (arc.hasOwnProperty(`${FS}Research`)) {
					options.addOption(`${App.Data.FutureSociety.records[FS].noun} research`, `${FS}Research`, arc)
						.addValue("Yes", 1).on()
						.addValue("No", 0).off();
				}
				if (num === 0) {
					if (arc.hasOwnProperty(`${FS}Decoration`)) {
						options.addOption(`${App.Data.FutureSociety.records[FS].noun} decoration`, `${FS}Decoration`, arc).showTextBox();
					}
				}
				if (FS === "FSSupremacist") {
					option = options.addOption(`Supremacist race`, `FSSupremacistRace`, arc);
					for (const [race, capRace] of App.Data.misc.filterRaces) {
						option.addValue(capRace, race);
					}
				} else if (FS === "FSSubjugationist") {
					option = options.addOption(`Subjugationist race`, `FSSubjugationistRace`, arc);
					for (const [race, capRace] of App.Data.misc.filterRaces) {
						option.addValue(capRace, race);
					}
				}
			}
		}
		el.append(options.render());
		return el;
	}
};

App.UI.Cheat.arcologyPassage = function() {
	const node = new DocumentFragment();
	App.UI.DOM.appendNewElement("h2", node, "Cheating Edit Arcology");

	if (
		(V.economy !== 100) ||
		(V.seeDicks !== 25) ||
		(V.continent !== "North America") ||
		(V.internationalTrade !== 1) ||
		(V.internationalVariety !== 1) ||
		(V.seeRace !== 1) ||
		(V.seeNationality !== 1) ||
		(V.seeExtreme !== 0) ||
		(V.plot !== 1)
	) {
		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
			"Restore defaults",
			App.Intro.arcDefault
		));
	}
	node.append(App.UI.Cheat.arcology(0));

	return node;
};


code:
/** @param {App.Entity.SlaveState} slave */
App.UI.SlaveInteract.cheatEditSlave = function(slave) {
	const el = new DocumentFragment();
	if (!V.tempSlave) {
		V.tempSlave = clone(slave);
	}

	App.UI.DOM.appendNewElement("h1", el, `Cheat edit ${slave.slaveName}`);

	el.append(App.Desc.longSlave(V.tempSlave));

	const tabBar = new App.UI.Tabs.TabBar("CheatEditJS");
	tabBar.addTab("Profile", "profile", App.StartingGirls.profile(V.tempSlave, true));
	tabBar.addTab("Physical", "physical", App.StartingGirls.physical(V.tempSlave, true));
	tabBar.addTab("Upper", "upper", App.StartingGirls.upper(V.tempSlave, true));
	tabBar.addTab("Lower", "lower", App.StartingGirls.lower(V.tempSlave, true));
	if (V.tempSlave.womb.length > 0) {
		tabBar.addTab(V.tempSlave.womb.length > 1 ? 'Fetuses' : 'Fetus', "fetuses", analyzePregnancies(V.tempSlave, true));
	}
	tabBar.addTab("Genes", "genes", genes());
	tabBar.addTab("Mental", "mental", App.StartingGirls.mental(V.tempSlave, true));
	tabBar.addTab("Skills", "skills", App.StartingGirls.skills(V.tempSlave, true));
	tabBar.addTab("Stats", "stats", App.StartingGirls.stats(V.tempSlave, true));
	tabBar.addTab("Porn", "porn", porn());
	tabBar.addTab("Relationships", "family", App.Intro.editFamily(V.tempSlave, true));
	tabBar.addTab("Body Mods", "body-mods", App.UI.bodyModification(V.tempSlave, true));
	tabBar.addTab("Salon", "salon", App.UI.salon(V.tempSlave, true));
	if (V.seeExtreme) {
		tabBar.addTab("Extreme", "extreme", extreme());
	}
	tabBar.addTab("Finalize", "finalize", finalize());
	el.append(tabBar.render());

	return el;

	function genes() {
		const el = new DocumentFragment();
		App.UI.DOM.appendNewElement("h2", el, "Genetic mods");
		el.append(App.UI.SlaveInteract.geneticMods(V.tempSlave, true));
		App.UI.DOM.appendNewElement("h2", el, "Genetic quirks");
		el.append(App.UI.SlaveInteract.geneticQuirks(V.tempSlave, true));
		return el;
	}

	function finalize() {
		const el = new DocumentFragment();
		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
			"Cancel",
			() => {
				delete V.tempSlave;
			},
			[],
			"Slave Interact"
		));
		App.Utils.showSlaveChanges(V.tempSlave, getSlave(V.AS), (val) => App.UI.DOM.appendNewElement("div", el, val), " ");
		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
			"Apply cheat edits",
			() => {
				if (V.tempSlave.devotion !== slave.devotion) {
					V.tempSlave.oldDevotion = V.tempSlave.devotion;
				}
				if (V.tempSlave.trust !== slave.trust) {
					V.tempSlave.oldTrust = V.tempSlave.trust;
				}
				SlaveDatatypeCleanup(V.tempSlave);
				normalizeRelationship();
				V.slaves[V.slaveIndices[slave.ID]] = V.tempSlave;
				ibc.recalculate_coeff_id(slave.ID);
				delete V.tempSlave;
			},
			[],
			"Cheat Edit JS Apply"
		));
		return el;
	}

	function normalizeRelationship() {
		if (V.tempSlave.relationship !== slave.relationship || V.tempSlave.relationshipTarget !== slave.relationshipTarget) {
			if (slave.relationship > 0 && V.tempSlave.relationship <= 0) {
				// broke relationship
				const friend = getSlave(slave.relationshipTarget);
				if (friend) {
					friend.relationship = 0;
					friend.relationshipTarget = 0;
				}
				V.tempSlave.relationshipTarget = 0;
			} else if (V.tempSlave.relationship > 0 && V.tempSlave.relationshipTarget !== slave.relationshipTarget) {
				// new relationship target
				const oldFriend = slave.relationship > 0 ? getSlave(slave.relationshipTarget) : null;
				if (oldFriend) {
					// first break this slave's existing relationship, if she had one
					oldFriend.relationship = 0;
					oldFriend.relationshipTarget = 0;
				}
				const newFriend = getSlave(V.tempSlave.relationshipTarget);
				if (newFriend) {
					// then break the target's existing relationship, if she had one
					const newFriendFriend = newFriend.relationship > 0 ? getSlave(newFriend.relationshipTarget) : null;
					if (newFriendFriend) {
						newFriendFriend.relationship = 0;
						newFriendFriend.relationshipTarget = 0;
					}
					// then make the new relationship bilateral
					newFriend.relationship = V.tempSlave.relationship;
					newFriend.relationshipTarget = V.tempSlave.ID;
				}
			} else if (V.tempSlave.relationship > 0) {
				// same target, new relationship level
				const friend = getSlave(slave.relationshipTarget);
				if (friend) {
					friend.relationship = V.tempSlave.relationship;
				}
			}
		}
	}

	function extreme() {
		const el = new DocumentFragment();
		const options = new App.UI.OptionsGroup();
		options.addOption("Fuckdoll", "fuckdoll", V.tempSlave)
			.addValue("Not a Fuckdoll", 0).addCallback(() => {
				V.tempSlave.clothes = "no clothing";
				V.tempSlave.shoes = "none";
			})
			.addValue("Barely a Fuckdoll", 15).addCallback(() => beginFuckdoll(V.tempSlave))
			.addValue("Slight Fuckdoll", 25).addCallback(() => beginFuckdoll(V.tempSlave))
			.addValue("Basic Fuckdoll", 45).addCallback(() => beginFuckdoll(V.tempSlave))
			.addValue("Intermediate Fuckdoll", 65).addCallback(() => beginFuckdoll(V.tempSlave))
			.addValue("Advanced Fuckdoll", 85).addCallback(() => beginFuckdoll(V.tempSlave))
			.addValue("Total Fuckdoll", 100).addCallback(() => beginFuckdoll(V.tempSlave))
			.showTextBox();
		el.append(options.render());
		return el;
	}

	function porn() {
		const el = new DocumentFragment();
		const porn = V.tempSlave.porn;
		const options = new App.UI.OptionsGroup();
		let option;
		const {him, he} = getPronouns(V.tempSlave);
		options.addOption(`Studio outputting porn of ${him}`, "feed", porn)
			.addValue("off", 0).off()
			.addValue("on", 1).on();
		options.addOption(`Viewer count`, "viewerCount", porn).showTextBox();
		options.addOption(`Spending`, "spending", porn).showTextBox();

		option = options.addOption(`Porn ${he} is known for`, "fameType", porn).addValue("None", "none").pulldown();
		for (const genre of App.Porn.getAllGenres()) {
			option.addValue(genre.uiName(), genre.fameName);
		}

		if (porn.fameType !== "none") {
			options.addOption(`Prestige level`, "prestige", porn)
				.addValueList([
					["Not", 0],
					["Some", 1],
					["Recognized", 2],
					["World renowned", 3],
				]);
			let genre = App.Porn.getGenreByFameName(porn.fameType);
			let desc_auto = '';
			switch (porn.prestige) {
				case 1:
					desc_auto = `$He has a following in slave pornography. ${genre.prestigeDesc1}.`;
					break;
				case 2:
					desc_auto = `He is well known from $his career in slave pornography. ${genre.prestigeDesc2}.`;
					break;
				case 3:
					desc_auto = `$He is world famous for $his career in slave pornography. ${genre.prestigeDesc3}.`;
					break;
			}
			options.addOption(`Prestige Description`, "prestigeDesc", porn)
				.addValue("Disable", 0).off()
				.addValue("Automatic", desc_auto).off()
				.showTextBox();
		}

		option = options.addOption(`Porn the studio focuses on`, "focus", porn).addValue("None", "none").pulldown();
		for (const genre of App.Porn.getAllGenres()) {
			option.addValue(genre.uiName(), genre.focusName);
		}

		for (const genre of App.Porn.getAllGenres()) {
			options.addOption(`Fame level for ${genre.fameName}`, genre.fameVar, porn.fame).addValue("None", "none").showTextBox();
		}

		el.append(options.render());
		return el;
	}
};


code:
:: MOD_Edit Child Cheat Datatype Cleanup New [nobr]

<<set $nextButton = "Continue", $nextLink = "Child Interact">>
/* Cancel Option Check */
<<if $tempSlave == "unset">>
	<<unset $tempSlave>>
	<<goto "Child Interact">>
<</if>>

<<if $customEvalCode>>
	<<if $customEvalCode.charAt(0) != "(" || $nextLink == ")">> /* second condition is only there for sanityCheck */
		<<set $customEvalCode = "(" + $customEvalCode + ")">>
	<</if>>
	<<if typeof eval($customEvalCode) === "function">>
		<<run (eval($customEvalCode))($tempSlave)>>
	<</if>>
<</if>>
<<unset $customEvalCode>>

<<run SlaveDatatypeCleanup($tempSlave)>>
<<set $rep = Number($rep) || 0>>
<<set $cash = Number($cash) || 0>>
<<set $week = Number($week) || 1>>
<<set $tempSlave.preg = Number($tempSlave.preg) || 0>>
<<set $tempSlave.pregType = Number($tempSlave.pregType) || 0>>
<<if $tempSlave.broodmother == 0>>
	<<set WombInit($tempSlave)>> /* just to make sure */
	<<set $tempSlave.womb.length = 0>> /* simple way to delete all fetuses */
	<<set WombImpregnate($tempSlave, $tempSlave.pregType, $tempSlave.pregSource, $tempSlave.preg)>> /* recreates fetuses */
<<else>>
	<<set WombNormalizePreg($tempSlave)>>
<</if>>
<<set $tempSlave.pregWeek = Number($tempSlave.pregWeek) || Math.max($tempSlave.preg, 0)>>
<<if $tempSlave.preg <= 0>>
	<<set $tempSlave.pregKnown = 0>>
<<elseif $tempSlave.preg > 0>>
	<<set $tempSlave.pregKnown = 1>>
<</if>>

/* Dependency Check */

<<if $tempSlave.indenture == -1>>
	<<set $tempSlave.indentureRestrictions = 0>>
<<elseif $tempSlave.indenture < -1>>
	<<set $tempSlave.indenture = -1>>
	<<print "Indenture was smaller than -1, reset to Fulltime Slave">>

<<elseif $tempSlave.indentureRestrictions < 0>>
	<<set $tempSlave.indentureRestrictions = 0>>
	<<print "Indenture restriction was smaller than 0, reset to no restrictions">>

<<elseif $tempSlave.indentureRestrictions > 2>>
	<<set $tempSlave.indentureRestrictions = 2>>
	<<print "Indenture restrictions was larger than 2, reset to full restrictions">>

<</if>>
<<if $tempSlave.weekAcquired < 0>>
	<<set $tempSlave.weekAcquired = 0>>
	<<print "Week Acquired was negative reset to 0">>

<</if>>
<<if ($tempSlave.relationshipTarget == $tempSlave.ID) && ($tempSlave.relationship > 0)>>
	<<set $tempSlave.relationshipTarget = 0>>
	<<set $tempSlave.relationship = -1>>
	<<print "The Slave was in a relation with $himself, reset to emotional Slut">>

<</if>>
<<if ($tempSlave.rivalryTarget == $tempSlave.ID) && ($tempSlave.rivalry > 0)>>
	<<set $tempSlave.rivalryTarget = 0>>
	<<set $tempSlave.rivalry = 0>>
	<<print "Slave was $his own rival, reset to no rivalry">>

<</if>>
<<if $tempSlave.actualAge < 0>>
	<<print "Slave's actual age is set too low, reset to existing actual age">>

	<<set $tempSlave.actualAge = $activeSlave.actualAge>>
<</if>>
<<if $tempSlave.physicalAge < 0>>
	<<print "Slave's physical Age is set too low, reset to existing physical age">>

	<<set $tempSlave.physicalAge = $activeSlave.actualAge>>
<</if>>
<<if $tempSlave.visualAge < 0>>
	<<print "Slave's visual age is set too low, reset to existing visual age">>

	<<set $tempSlave.visualAge = $activeSlave.actualAge>>
<</if>>
<<if $tempSlave.ovaryAge < 0>>
	<<print "Slave's ovary age is set too low, reset to match physical age">>

	<<set $tempSlave.ovaryAge = $tempSlave.physicalAge>>
<</if>>
<<if $tempSlave.chem < 0>>
	<<print "Slave's DNA error is set too low, reset to 0">>

	<<set $tempSlave.chem = 0>>
<</if>>
<<if $tempSlave.face < -100>>
	<<print "Face Beauty was set too low, reset to -100">>

	<<set $tempSlave.face = -100>>
<<elseif $tempSlave.face > 100>>
	<<print "Face Beauty was set too high, reset to 100">>

	<<set $tempSlave.face = 100>>
<</if>>
<<if $tempSlave.hLength < 0>>
	<<print "Slaves Hair length was set too low, reset to 0">>

	<<set $tempSlave.hLength = 0>>
<</if>>
<<if $tempSlave.lips < 0>>
	<<print "Lips size too small, reset to 0">>

	<<set $tempSlave.lips = 0>>
<<elseif $tempSlave.lips > 100>>
	<<print "Lips size too high, reset to 100">>

	<<set $tempSlave.lips = 100>>
<</if>>
<<if $tempSlave.muscles < -100>>
	<<print "Muscle Value too low, reset to -100 (Frail)">>

	<<set $tempSlave.muscles = 0>>
<<elseif $tempSlave.muscles > 100>>
	<<print "Muscle Value too high, reset to 100 (Hugely Muscular)">>

	<<set $tempSlave.muscles = 100>>
<</if>>
<<if $tempSlave.weight < -100>>
	<<print "Weight Value too low, reset to -100 (Emaciated)">>

	<<set $tempSlave.weight = -100>>
<<elseif $tempSlave.weight > 200 >>
	<<print "Weight Value too high, reset to 200 (Extremely Obese)">>

	<<set $tempSlave.weight = 200>>
<</if>>
<<if $tempSlave.waist < -100>>
	<<print "Waist Value too low, reset to -100 (Absurd)">>

	<<set $tempSlave.waist = -100>>
<<elseif $tempSlave.waist > 100>>
	<<print "Waist Value too high, reset to 100 (Masculine)">>

	<<set $tempSlave.waist = 100>>
<</if>>
<<if $tempSlave.height < 1>>
	<<print "Height Value too low, reset to 140 (Seriously its still a Human Being)">>

	<<set $tempSlave.height = 140>>
<</if>>
<<if $tempSlave.bellySag < 0>>
	<<print "Bellysag Value too low, reset 0">>

	<<set $tempSlave.bellySag = 0>>
<</if>>
<<if $tempSlave.boobs < 0>>
	<<print "Breasts Value too low, reset to 200">>

	<<set $tempSlave.boobs = 200>>
<</if>>
<<if $tempSlave.boobsImplant < 0>>
	<<print "Breast Implant Value too low, reset to 0">>

	<<set $tempSlave.boobsImplant = 0>>
<</if>>
<<if $tempSlave.butt < 0>>
	<<print "Butt Value too low, reset to 0">>

	<<set $tempSlave.butt = 0>>
<</if>>
<<if (ndef $tempSlave.pubertyAgeXX) || ($tempSlave.pubertyAgeXX < 0)>>
	<<print "Puberty Age was not set or set too low, reset to Arcology fertility age">>

	<<set $tempSlave.pubertyAgeXX = $fertilityAge>>
<</if>>
<<if (ndef $tempSlave.pubertyXX) && ($tempSlave.physicalAge >= $tempSlave.pubertyAgeXX)>>
	<<set $tempSlave.pubertyXX = 1>>
<<elseif (ndef $tempSlave.pubertyXX) && ($tempSlave.physicalAge < $tempSlave.pubertyAgeXX)>>
	<<set $tempSlave.pubertyXX = 0>>
<</if>>
<<if $tempSlave.dick < 0>>
	<<print "Slave dick value set too low, reset to 0 (no dick)" >>

	<<set $tempSlave.dick = 0>>
<</if>>
<<if $tempSlave.dick == 0>>
	<<set $tempSlave.dickTat = 0>>
<</if>>
<<if ($tempSlave.vagina == -1) >>
	<<set $tempSlave.labia = 0>>
	<<set $tempSlave.clit = 0>>
	<<set $tempSlave.cervixImplant = 0>>
	<<set $tempSlave.ovaries = 0>>
<</if>>
<<if ($tempSlave.clit > 0) && ($tempSlave.dick > 0 )>>
	<<print "No giant clit when dick is present, reset clit to 0 (normal)" >>

	<<set $tempSlave.clit = 0>>
<</if>>
<<if ($tempSlave.ovaries == 0) && ($tempSlave.mpreg == 0) && ($tempSlave.preg > 0)>>
	<<print "Slave has no Ovaries and no Analwomb, Pregnancy reset to 0">>

	<<run WombFlush($tempSlave)>>
<</if>>
<<if ($tempSlave.pubertyXX != 1) && ($tempSlave.physicalAge >= $tempSlave.pubertyAgeXX)>>
	<<print "Slaves physical Age is equal or higher than female Puberty Age, Puberty set to 1 (Post Puberty)" >>

	<<set $tempSlave.pubertyXX = 1>>
<</if>>
<<if ($tempSlave.ovaries == 1) && ($tempSlave.mpreg == 1)>>
	<<print "Slave has a working Vagina, Analwomb got removed" >>

	<<set $tempSlave.mpreg = 0>>
<</if>>
<<if ($tempSlave.pubertyXX == 0) && ($tempSlave.preg > 0)>>
	<<print "Slave is not fertile (Pre-Puberty) and has no Anal Womb, pregnancy aborted!">>

	<<run WombFlush($tempSlave)>>
<</if>>
<<if $tempSlave.balls < 0>>
	<<print "Slave Balls Value set too low, reset to 0 (No Balls)">>

	<<set $tempSlave.balls = 0>>
<</if>>
<<if $tempSlave.foreskin < 0>>
	<<print "Slave Foreskin Value set too low, reset to 0 (None)">>

	<<set $tempSlave.foreskin = 0>>
<</if>>
<<if $tempSlave.scrotum < 0>>
	<<print "Slave Scrotum Value set too low, reset to 0 (No Scrotum)">>

	<<set $tempSlave.scrotum = 0>>
<</if>>
<<if (ndef $tempSlave.pubertyAgeXY) || ($tempSlave.pubertyAgeXY < 0)>>
	<<print "XY Puberty Age was not set or set too low, reset to Arcology Puberty Age">>

	<<set $tempSlave.pubertyAgeXY = $potencyAge>>
<</if>>
<<if (ndef $tempSlave.pubertyXY) && ($tempSlave.physicalAge >= $tempSlave.pubertyAgeXY)>>
	<<set $tempSlave.pubertyXY = 1>>
<<elseif (ndef $tempSlave.pubertyXY) && ($tempSlave.physicalAge < $tempSlave.pubertyAgeXY)>>
	<<set $tempSlave.pubertyXY = 0>>
<</if>>
<<if ($tempSlave.physicalAge >= $tempSlave.pubertyAgeXY) && ($tempSlave.pubertyXY != 1)>>
	<<print "Slaves physical Age is equal or higher than male Puberty Age, Puberty set to 1 (Post Puberty)" >>

	<<set $tempSlave.pubertyXY = 1>>
<</if>>
<<if $tempSlave.skill.oral < 0>>
	<<print "Oral Skill Value set too low, reset to 0">>

	<<set $tempSlave.skill.oral = 0>>
<<elseif $tempSlave.skill.oral > 100>>
	<<print "Oral Skill Value set too high, reset to 100">>

	<<set $tempSlave.skill.oral = 100>>
<</if>>
<<if $tempSlave.skill.vaginal < 0>>
	<<print "Vaginal Skill Value too low, reset to 0">>

	<<set $tempSlave.skill.vaginal = 0>>
<<elseif $tempSlave.skill.vaginal > 100>>
	<<print "Vaginal Skill Value too high, reset to 100">>

	<<set $tempSlave.skill.vaginal = 100>>
<</if>>
<<if $tempSlave.skill.anal < 0>>
	<<print "Anal Skill Value set too low, reset to 0">>

	<<set $tempSlave.skill.anal = 0>>
<<elseif $tempSlave.skill.anal > 100>>
	<<print "Anal Skill Value set too high, reset to 100">>

	<<set $tempSlave.skill.anal = 100>>
<</if>>
<<if $tempSlave.skill.whoring < 0>>
	<<print "Whoring Skill Value set too low, reset to 0">>

	<<set $tempSlave.skill.whoring = 0>>
<<elseif $tempSlave.skill.whoring > 100>>
	<<print "Whoring Skill Value set too high, reset to 100">>

	<<set $tempSlave.skill.whoring = 100>>
<</if>>
<<if $tempSlave.skill.entertainment < 0>>
	<<print "Entertainment Skill Value set too low, reset to 0">>

	<<set $tempSlave.skill.entertainment = 0>>
<<elseif $tempSlave.skill.entertainment > 100>>
	<<print "Entertainment Skill Value set too high, reset to 100">>

	<<set $tempSlave.skill.entertainment = 100>>
<</if>>
<<if $tempSlave.intelligence < -100>>
	<<print "Slave Intelligence Value set too low, reset to -100">>

	<<set $tempSlave.intelligence = -100>>
<<elseif $tempSlave.intelligence > 100>>
	<<print "Slave Intelligence Value set too high, reset to 100">>

	<<set $tempSlave.intelligence = 100>>
<</if>>
<<if $tempSlave.intelligenceImplant < 0>>
	<<print "Slave intelligenceImplant Value set too low, reset to 0">>

	<<set $tempSlave.intelligenceImplant = 0>>
<<elseif $tempSlave.intelligenceImplant > 30>>
	<<print "Slave intelligenceImplant Value set too high, reset to 30">>

	<<set $tempSlave.intelligenceImplant = 30>>
<</if>>
<<if $tempSlave.fetishStrength < 0>>
	<<print "Fetish Strength set too low, reset to 0">>

	<<set $tempSlave.fetishStrength = 0>>
<<elseif $tempSlave.fetishStrength > 100>>
	<<print "Fetish Strength set too high, reset to 100">>

	<<set $tempSlave.fetishStrength = 100>>
<</if>>
<<if $tempSlave.energy < 0>>
	<<print "Sex Drive was set too low, reset to 0">>

	<<set $tempSlave.energy = 0>>
<<elseif $tempSlave.energy > 100>>
	<<print "Sex Drive was set too high, reset to 100">>

	<<set $tempSlave.energy = 100>>
<</if>>
<<if $tempSlave.counter.penetrative < 0>>
	<<print "Penetrative sex Count was set too low, reset to 0">>

	<<set $tempSlave.counter.penetrative = 0>>
<</if>>
<<if $tempSlave.counter.oral < 0>>
	<<print "Oral sex Count set too low, reset to 0">>

	<<set $tempSlave.counter.oral = 0>>
<</if>>
<<if $tempSlave.counter.vaginal < 0>>
	<<print "Vaginal sex Count set too low, reset to 0">>

	<<set $tempSlave.counter.vaginal = 0>>
<</if>>
<<if $tempSlave.counter.anal < 0>>
	<<print "Anal sex Count set too low, reset to 0">>

	<<set $tempSlave.counter.anal = 0>>
<</if>>
<<if $tempSlave.counter.publicUse < 0>>
	<<print "Public sex Count set too low, reset 0">>

	<<set $tempSlave.counter.publicUse = 0>>
<</if>>
<<if $tempSlave.counter.mammary < 0>>
	<<print "Titjobs Count set too low, reset to 0">>

	<<set $tempSlave.counter.mammary = 0>>
<</if>>
<<if $tempSlave.counter.birthsTotal < 0>>
	<<print "Total birth Count set too low, reset to 0">>

	<<set $tempSlave.counter.birthsTotal = 0>>
<</if>>
<<if $tempSlave.prestige < 0>>
	<<print "Prestige set too low, reset to 0">>

	<<set $tempSlave.prestige = 0>>
<<elseif $tempSlave.prestige > 3>>
	<<print "Prestige set too high, reset to 3">>

	<<set $tempSlave.prestige = 3>>
<</if>>
<<if $tempSlave.lactation > 0 && $tempSlave.lactationDuration == 0>>
	<<set $tempSlave.lactationDuration = 2>>
<<elseif $tempSlave.lactation == 0 && $tempSlave.lactationDuration > 0>>
	<<set $tempSlave.lactationDuration = 0>>
<</if>>
<<run SetBellySize($tempSlave)>>

You perform the dark rituals, pray to the dark gods, and sell your soul for the power to change and mold slaves to your will.

This slave has been changed forever and you have lost a bit of your soul, YOU CHEATER!

<<set $activeSlave = clone($tempSlave)>>
<<unset $tempSlave>>
<<set _escn = $cribs.findIndex(function(s) { s.ID === $activeSlave.ID; })>>
<<if def _escn>>
	<<set $cribs[_escn] = clone($activeSlave)>>
<</if>>
<<run ibc.recalculate_coeff_id($activeSlave.ID)>>