code:
/**
 * @param {App.Entity.SlaveState} slave
 * @param {function():void} contentRefresh
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
	const {
		him,
		his,
	} = getPronouns(slave);

	const T = State.temporary;
	if (!T.filters) {
		T.filters = {};
	}

	return contents();

	function contents() {
		const frag = new DocumentFragment();

		if (slave.fuckdoll === 0) {
			frag.append(filtersDOM());
			frag.append(chooseHerOwn());
			frag.append(clothes());
			frag.append(mask());
			frag.append(mouth());
			if (slave.armAccessory !== "none" || hasAnyArms(slave)) {
				frag.append(armAccessory());
			}
			frag.append(shoes());
			frag.append(legAccessory());
			frag.append(bellyAccessory());
			frag.append(buttplug());
			frag.append(buttplugAttachment());
			if (slave.vagina > -1) {
				frag.append(vaginalAccessory());
				frag.append(vaginalAttachment());
			}
			if (slave.dick > 0) {
				frag.append(dickAccessory());
			}
			frag.append(chastity());
		} else {
			frag.append(clothes());
		}

		App.UI.DOM.appendNewElement("h3", frag, `Shopping`);
		frag.append(shopping());

		return frag;
	}

	function filtersDOM() {
		const el = document.createElement("p");
		el.classList.add("filter-row");
		el.append("Filters: ");

		const niceFilters = new Map([
			[false, "Nice"],
			[true, "Harsh"],
		]);
		let span = document.createElement("span");
		span.classList.add("button-group");
		for (const [bool, string] of niceFilters) {
			const button = App.UI.DOM.makeElement("button", string);
			if (T.filters.harsh === bool) {
				button.classList.add("selected", "disabled");
			} else {
				button.onclick = () => {
					T.filters.harsh = bool;
					refresh();
				};
			}
			span.append(button);
		}
		el.append(span);

		const exposureFilters = new Map([
			[0, "Modest"],
			[1, "Normal"],
			[2, "Slutty"],
			[3, "Humiliating"],
			[4, "Practically nude"],
		]);
		span = document.createElement("span");
		span.classList.add("button-group");
		for (const [num, string] of exposureFilters) {
			const button = App.UI.DOM.makeElement("button", string);
			if (T.filters.exposure === num) {
				button.classList.add("selected", "disabled");
			} else {
				button.onclick = () => {
					T.filters.exposure = num;
					refresh();
				};
			}
			span.append(button);
		}
		el.append(span);

		const FSFilters = new Map([]);
		for (const FS of App.Data.FutureSociety.fsNames) {
			if (V.arcologies[0][FS] > 0) {
				FSFilters.set(FS, App.Data.FutureSociety.records[FS].noun);
			}
		}
		span = document.createElement("span");
		span.classList.add("button-group");
		for (const [num, string] of FSFilters) {
			const button = App.UI.DOM.makeElement("button", string);
			if (T.filters.FSLoves === num) {
				button.classList.add("selected", "disabled");
			} else {
				button.onclick = () => {
					T.filters.FSLoves = num;
					refresh();
				};
			}
			span.append(button);
		}
		el.append(span);

		// clear filters
		const resetButton = App.UI.DOM.makeElement("button", "Reset Filters");
		resetButton.onclick = () => {
			T.filters = {};
			refresh();
		};
		App.UI.DOM.appendNewElement("span", el, resetButton, "button-group");
		return el;
	}

	function chooseHerOwn() {
		const el = document.createElement('div');
		const linkArray = [];

		const label = document.createElement('div');
		label.append(`Slave selects ${his} own outfits: `);

		App.UI.DOM.appendNewElement("span", label, slave.choosesOwnClothes ? "Allowed" : "Forbidden", ["bold"]);

		el.appendChild(label);

		linkArray.push(
			App.UI.DOM.link(
				`Allow`,
				() => {
					slave.choosesOwnClothes = 1;
					refresh();
				},
			)
		);

		linkArray.push(
			App.UI.DOM.link(
				`Forbid`,
				() => {
					slave.choosesOwnClothes = 0;
					refresh();
				},
			)
		);

		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(linkArray), "choices")
			.append(App.UI.DOM.makeElement("span", " Increases or greatly reduces devotion based on whether the slave is obedient (devotion at accepting or higher).", "note"));

		return el;
	}

	function clothes() {
		const clothingDiv = document.createElement('div');
		let links;
		let label;
		if (slave.fuckdoll === 0) {
			// First Row
			label = document.createElement('div');
			label.append(`Clothes: `, App.UI.DOM.spanWithTooltip(slave.clothes, itemTooltip(slave.clothes, "clothes"), ["bold"]));

			if (slave.fuckdoll !== 0 || slave.clothes === "restrictive latex" || slave.clothes === "a latex catsuit" || slave.clothes === "a cybersuit" || slave.clothes === "a comfortable bodysuit") {
				if (V.seeImages === 1 && V.imageChoice === 1) {
					// Color options
					label.appendChild(colorOptions("clothingBaseColor"));
				}
			}

			clothingDiv.appendChild(label);
			links = App.UI.DOM.appendNewElement("div", clothingDiv, clothingSelection());
			links.id = "clothing-selection";
		}

		label = document.createElement('div');
		label.append(`Collar: `, App.UI.DOM.spanWithTooltip(slave.collar, itemTooltip(slave.collar, "collar"), ["bold"]));
		// Choose her own
		if (slave.collar !== `none`) {
			label.append(noneLink("collar"));
		}
		clothingDiv.appendChild(label);

		links = App.UI.DOM.appendNewElement("div", clothingDiv, collar());
		links.id = "collar-selection";

		return clothingDiv;

		function clothingSelection() {
			const el = new DocumentFragment();
			/** @type {FC.Clothes[]} */
			let array = [];

			for (const [key, object] of App.Data.clothes) {
				if (T.filters.hasOwnProperty("exposure") && T.filters.exposure !== object.exposure) {
					continue;
				}
				if (T.filters.hasOwnProperty("harsh") && ((T.filters.harsh === false && object.harsh) || (T.filters.harsh === true && !object.harsh))) {
					continue;
				}
				if (T.filters.hasOwnProperty("FSLoves") && !(object.fs && object.fs.loves && object.fs.loves.has(T.filters.FSLoves))) {
					continue;
				}
				if (object.fuckdoll) { // skip displaying fuckdoll outfits
					continue;
				}
				array.push(key);
			}

			// Sort
			array = array.sort((a, b) => (App.Data.clothes.get(a).name > App.Data.clothes.get(b).name) ? 1 : -1);
			const sortedMap = new Map([]);
			for (const name of array) {
				sortedMap.set(name, App.Data.clothes.get(name));
			}
			const list = generateRows(sortedMap, "clothes", true);
			if (list.children.length > 0) {
				App.UI.DOM.appendNewElement("div", el, list, "choices");
			} else {
				App.UI.DOM.appendNewElement("div", el, "No available clothing meets your criteria", ["note", "choices"]);
			}
			return el;
		}

		function collar() {
			const el = new DocumentFragment();
			let array = [];

			for (const [key, object] of App.Data.slaveWear.collar) {
				if (T.filters.hasOwnProperty("harsh") && ((T.filters.harsh === false && object.harsh) || (T.filters.harsh === true && !object.harsh))) {
					continue;
				}
				array.push(key);
			}

			// Sort
			array = array.sort((a, b) => (App.Data.slaveWear.collar.get(a).name > App.Data.slaveWear.collar.get(b).name) ? 1 : -1);
			const sortedMap = new Map([]);
			for (const name of array) {
				sortedMap.set(name, App.Data.slaveWear.collar.get(name));
			}
			const list = generateRows(sortedMap, "collar", true);
			if (list.children.length > 0) {
				App.UI.DOM.appendNewElement("div", el, list, "choices");
			} else {
				App.UI.DOM.appendNewElement("div", el, "No available collar meets your criteria", ["note", "choices"]);
			}
			return el;
		}
	}

	function mask() {
		const el = document.createElement('div');

		const label = document.createElement('div');
		label.append(`Mask: `, App.UI.DOM.spanWithTooltip(slave.faceAccessory, itemTooltip(slave.faceAccessory, "faceAccessory"), ["bold"]));

		// Choose her own
		if (slave.faceAccessory !== `none`) {
			label.append(noneLink("faceAccessory"));
		}

		el.appendChild(label);

		let array = Array.from(App.Data.slaveWear.faceAccessory.keys());

		// Sort
		array = array.sort((a, b) => (App.Data.slaveWear.faceAccessory.get(a).name > App.Data.slaveWear.faceAccessory.get(b).name) ? 1 : -1);
		const sortedMap = new Map([]);
		for (const name of array) {
			sortedMap.set(name, App.Data.slaveWear.faceAccessory.get(name));
		}

		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(generateRows(sortedMap, "faceAccessory", true));
		el.appendChild(links);

		if (slave.eyewear === "corrective glasses" || slave.eyewear === "glasses" || slave.eyewear === "blurring glasses" || slave.faceAccessory === "porcelain mask") {
			// Color options
			links = document.createElement('div');
			links.className = "choices";
			links.append(`Color: `);
			links.appendChild(colorOptions("glassesColor"));
			let note = document.createElement('span');
			note.className = "note";
			note.textContent = ` Only glasses and porcelain masks support a custom color. If both are worn, they will share the same color.`;
			links.appendChild(note);
			el.appendChild(links);
		}

		return el;
	}

	function mouth() {
		const el = document.createElement('div');

		const label = document.createElement('div');
		label.append(`Gag: `, App.UI.DOM.spanWithTooltip(slave.mouthAccessory, itemTooltip(slave.mouthAccessory, "mouthAccessory"), ["bold"]));

		// Choose her own
		if (slave.mouthAccessory !== `none`) {
			label.append(noneLink("mouthAccessory"));
		}

		el.appendChild(label);

		let array = Array.from(App.Data.mouthAccessory.keys());

		// Sort
		array = array.sort((a, b) => (App.Data.mouthAccessory.get(a).name > App.Data.mouthAccessory.get(b).name) ? 1 : -1);
		const sortedMap = new Map([]);
		for (const name of array) {
			sortedMap.set(name, App.Data.mouthAccessory.get(name));
		}

		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(generateRows(sortedMap, "mouthAccessory", true));
		el.appendChild(links);

		return el;
	}

	function armAccessory() {
		const el = document.createElement('div');
		// App.Desc.armwear(slave)

		const label = document.createElement('div');
		label.append(`Arm accessory: `, App.UI.DOM.spanWithTooltip(slave.armAccessory, itemTooltip(slave.armAccessory, "armAccessory"), ["bold"]));

		// Choose her own
		if (slave.armAccessory !== "none") {
			label.append(noneLink("armAccessory"));
		}

		el.appendChild(label);

		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(generateRows(App.Data.slaveWear.armAccessory, "armAccessory", false));
		el.appendChild(links);

		return el;
	}

	function shoes() {
		const el = document.createElement('div');
		const label = App.UI.DOM.appendNewElement("div", el, `Shoes: `);

		label.append(App.UI.DOM.spanWithTooltip(slave.shoes, itemTooltip(slave.shoes, "shoes"), ["bold"]));

		/* We have "barefoot" in App.Data.slaveWear.slaveWear to cover for this
			// Choose her own
			if (slave.shoes !== `none`) {
				let choiceOptionsMap = [];
				choiceOptionsMap.push({text: `None`, updateSlave: {shoes: `none`}});
				label.appendChild(generateRows(choiceOptionsMap, "shoes", false));
			}
		*/

		let array = Array.from(App.Data.shoes.keys());

		// Sort
		// No sort here since we want light -> advanced. optionsArray = optionsArray.sort((a, b) => (a.text > b.text) ? 1 : -1);

		// Options
		let links = document.createElement('div');
		links.className = "choices";
		const sortedMap = new Map([]);
		for (const name of array) {
			sortedMap.set(name, App.Data.shoes.get(name));
		}
		links.appendChild(generateRows(sortedMap, "shoes", true));
		el.appendChild(links);

		if (V.seeImages === 1 && V.imageChoice === 1 && slave.shoes !== "none") {
			// Color options
			links = App.UI.DOM.appendNewElement("div", el, `Color: `, "choices");
			links.appendChild(colorOptions("shoeColor"));
		}

		return el;
	}

	function legAccessory() {
		const el = document.createElement('div');
		const label = App.UI.DOM.appendNewElement("div", el, `Leg accessory: `);

		label.append(App.UI.DOM.spanWithTooltip(slave.legAccessory, itemTooltip(slave.legAccessory, "legAccessory"), ["bold"]));

		// Choose her own
		if (slave.legAccessory !== "none") {
			label.append(noneLink("legAccessory"));
		}

		App.UI.DOM.appendNewElement("div", el, generateRows(App.Data.slaveWear.legAccessory, "legAccessory", false), "choices");

		return el;
	}

	function bellyAccessory() {
		/** @type {Array<FC.BellyAccessory>} */
		let array = [];
		/** @type {Array<FC.BellyAccessory>} */
		let empathyArray = [];

		for (const [key, object] of App.Data.bellyAccessory) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			if (object.hasOwnProperty("empathyBelly")) {
				empathyArray.push(key);
			} else {
				array.push(key);
			}
		}

		// Sort
		// No sort here since we want small -> large.optionsArray = optionsArray.sort((a, b) => (a.text > b.text) ? 1 : -1);

		let el = document.createElement('div');

		let label = App.UI.DOM.appendNewElement("div", el, `Belly accessory: `);

		label.append(App.UI.DOM.spanWithTooltip(slave.bellyAccessory, itemTooltip(slave.bellyAccessory, "bellyAccessory"), ["bold"]));

		// Choose her own
		if (slave.bellyAccessory !== `none`) {
			label.append(noneLink("bellyAccessory"));
		}

		// Options
		let sortedMap = new Map([]);
		for (const name of array) {
			sortedMap.set(name, App.Data.bellyAccessory.get(name));
		}
		App.UI.DOM.appendNewElement("div", el, generateRows(sortedMap, "bellyAccessory", true), "choices");

		sortedMap = new Map([]);
		for (const name of empathyArray) {
			sortedMap.set(name, App.Data.bellyAccessory.get(name));
		}
		App.UI.DOM.appendNewElement("div", el, generateRows(sortedMap, "bellyAccessory", true), "choices");

		if (slave.pregKnown === 1) {
			App.UI.DOM.appendNewElement("div", el, ` Extreme corsets will endanger the life within ${him}.`, ["note", "choices"]);
		}

		return el;
	}

	function buttplug() {
		// App.Desc.buttplug(slave)
		const el = document.createElement('div');

		const label = document.createElement('div');
		label.append(`Anal accessory: `, App.UI.DOM.spanWithTooltip(slave.buttplug, itemTooltip(slave.buttplug, "buttplug"), ["bold"]));

		if (slave.buttplug !== `none`) {
			label.append(noneLink("buttplug"));
		}
		el.appendChild(label);

		let normalArray = [];
		let longArray = [];

		for (const [key, object] of App.Data.buttplug) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			if (object.length > 1) {
				longArray.push(key);
			} else {
				normalArray.push(key);
			}
		}

		// Sort
		// No sort here since we want small -> large. optionsArray = optionsArray.sort((a, b) => (a.text > b.text) ? 1 : -1);

		// Options
		let sortedMap = new Map([]);
		for (const name of longArray) {
			sortedMap.set(name, App.Data.buttplug.get(name));
		}
		App.UI.DOM.appendNewElement("div", el, generateRows(sortedMap, "buttplug", true), "choices");

		sortedMap = new Map([]);
		for (const name of normalArray) {
			sortedMap.set(name, App.Data.buttplug.get(name));
		}
		App.UI.DOM.appendNewElement("div", el, generateRows(sortedMap, "buttplug", true), "choices");

		if (V.customItem.buttplug.size > 0) {
			App.UI.DOM.appendNewElement("div", el, generateRows(V.customItem.buttplug, "buttplug", false), "choices");
		}

		return el;
	}

	function buttplugAttachment() {
		const el = document.createElement('div');
		if (slave.buttplug === "none") {
			return el;
		}

		const label = document.createElement('div');
		label.append(`Anal accessory attachment: `, App.UI.DOM.spanWithTooltip(slave.buttplugAttachment, itemTooltip(slave.buttplugAttachment, "buttplugAttachment"), ["bold"]));

		if (slave.buttplugAttachment !== `none`) {
			label.append(noneLink("buttplugAttachment"));
		}
		el.appendChild(label);

		let array = [];

		for (const key of App.Data.slaveWear.buttplugAttachment.keys()) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			array.push(key);
		}

		// Sort
		array = array.sort((a, b) => (a > b) ? 1 : -1);
		const sortedMap = new Map([]);
		for (const name of array) {
			sortedMap.set(name, App.Data.slaveWear.buttplugAttachment.get(name));
		}

		// Options
		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(generateRows(sortedMap, "buttplugAttachment", true));
		el.appendChild(links);

		return el;
	}

	function vaginalAccessory() {
		const el = document.createElement('div');

		const label = document.createElement('div');
		label.append(`Vaginal accessory: `, App.UI.DOM.spanWithTooltip(slave.vaginalAccessory, itemTooltip(slave.vaginalAccessory, "vaginalAccessory"), ["bold"]));

		if (slave.vaginalAccessory !== `none`) {
			label.append(noneLink("vaginalAccessory"));
		}
		el.appendChild(label);

		let bulletArray = [];
		let normalArray = [];
		let longArray = [];

		for (const [key, object] of App.Data.vaginalAccessory) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			if (object.width === 0) {
				bulletArray.push(key);
			} else if (object.length > 1) {
				longArray.push(key);
			} else {
				normalArray.push(key);
			}
		}

		// Sort
		// No sort here since we want small -> large. optionsArray = optionsArray.sort((a, b) => (a.text > b.text) ? 1 : -1);

		// Options
		let sortedMap = new Map([]);
		for (const name of bulletArray) {
			sortedMap.set(name, App.Data.vaginalAccessory.get(name));
		}
		App.UI.DOM.appendNewElement("div", el, generateRows(sortedMap, "vaginalAccessory", true), "choices");

		sortedMap = new Map([]);
		for (const name of normalArray) {
			sortedMap.set(name, App.Data.vaginalAccessory.get(name));
		}
		App.UI.DOM.appendNewElement("div", el, generateRows(sortedMap, "vaginalAccessory", true), "choices");

		sortedMap = new Map([]);
		for (const name of longArray) {
			sortedMap.set(name, App.Data.vaginalAccessory.get(name));
		}
		App.UI.DOM.appendNewElement("div", el, generateRows(sortedMap, "vaginalAccessory", true), "choices");
		if (V.customItem.vaginalAccessory.size > 0) {
			App.UI.DOM.appendNewElement("div", el, generateRows(V.customItem.vaginalAccessory, "vaginalAccessory", false), "choices");
		}
		return el;
	}

	function vaginalAttachment() {
		let el = document.createElement('div');
		if (dildoWidth(slave) === 0) {
			slave.vaginalAttachment = "none";
			return el;
		}

		let label = App.UI.DOM.appendNewElement("div", el, `Vaginal accessory attachment: `);

		label.append(App.UI.DOM.spanWithTooltip(slave.vaginalAttachment, itemTooltip(slave.vaginalAttachment, "vaginalAttachment"), ["bold"]));

		if (slave.vaginalAttachment !== `none`) {
			label.append(noneLink("vaginalAttachment"));
		}

		let array = [];

		for (const key of App.Data.slaveWear.vaginalAttachment.keys()) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			array.push(key);
		}

		// Sort
		array = array.sort((a, b) => (a > b) ? 1 : -1);
		const sortedMap = new Map([]);
		for (const name of array) {
			sortedMap.set(name, App.Data.slaveWear.vaginalAttachment.get(name));
		}

		// Options
		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(generateRows(sortedMap, "vaginalAttachment", true));
		el.appendChild(links);

		return el;
	}

	function dickAccessory() {
		const el = document.createElement('div');

		const label = document.createElement('div');
		label.append(`Dick accessory: `, App.UI.DOM.spanWithTooltip(slave.dickAccessory, itemTooltip(slave.dickAccessory, "dickAccessory"), ["bold"]));

		if (slave.dickAccessory !== `none`) {
			label.append(noneLink("dickAccessory"));
		}
		el.appendChild(label);

		let array = [];

		for (const key of App.Data.slaveWear.dickAccessory.keys()) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			array.push(key);
		}

		// Sort
		// No sort here since we want small -> large. optionsArray = optionsArray.sort((a, b) => (a.text > b.text) ? 1 : -1);

		// Options
		const sortedMap = new Map([]);
		for (const name of array) {
			sortedMap.set(name, App.Data.slaveWear.dickAccessory.get(name));
		}
		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(generateRows(sortedMap, "dickAccessory", true));
		el.appendChild(links);

		return el;
	}

	function chastity() {
		const el = document.createElement('div');

		const label = document.createElement('div');
		label.append(`Chastity devices: `);

		/** @type {string} */
		let chasCho;
		if (slave.choosesOwnChastity === 1) {
			chasCho = `choosing ${his} own chastity`;
		} else if (slave.chastityAnus === 1 && slave.chastityPenis === 1 && slave.chastityVagina === 1) {
			chasCho = `full chastity`;
		} else if (slave.chastityPenis === 1 && slave.chastityVagina === 1) {
			chasCho = `genital chastity`;
		} else if (slave.chastityAnus === 1 && slave.chastityPenis === 1) {
			chasCho = `combined chastity cage`;
		} else if (slave.chastityAnus === 1 && slave.chastityVagina === 1) {
			chasCho = `combined chastity belt`;
		} else if (slave.chastityVagina === 1) {
			chasCho = `chastity belt`;
		} else if (slave.chastityPenis === 1) {
			chasCho = `chastity cage`;
		} else if (slave.chastityAnus === 1) {
			chasCho = `anal chastity`;
		} else if (slave.chastityAnus === 0 && slave.chastityPenis === 0 && slave.chastityVagina === 0) {
			chasCho = `none `;
		} else {
			chasCho = `THERE HAS BEEN AN ERROR `;
		}

		label.append(App.UI.DOM.spanWithTooltip(chasCho, itemTooltip(chasCho, "chastity"), ["bold"]));

		if (slave.chastityAnus !== 0 || slave.chastityPenis !== 0 || slave.chastityVagina !== 0) {
			label.append(
				App.UI.DOM.link(
					` None`,
					() => {
						Object.assign(
							slave,
							{
								choosesOwnChastity: 0,
								chastityAnus: 0,
								chastityPenis: 0,
								chastityVagina: 0
							});
						refresh();
					}
				)
			);
		}
		el.appendChild(label);

		let optionsArray = [];

		for (const key of App.Data.chastity.keys()) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			optionsArray.push(key);
		}

		// Options
		const linkArray = [];
		for (const name of optionsArray) {
			const object = App.Data.chastity.get(name);
			if (object && object.fs && object.fs.unlocks && V.arcologies[0][object.fs.unlocks] === "unset") { // Really simple requirements check here since FS is the only way to unlock chastity options
				continue;
			}
			if (object.updateSlave.chastityPenis && slave.dick === 0) {
				continue;
			}
			if (object.updateSlave.chastityVagina && slave.vagina < 0) {
				continue;
			}
			linkArray.push(
				App.UI.DOM.link(
					object.name,
					() => {
						Object.assign(slave, object.updateSlave);
						refresh();
					},
					[],
					"",
					object.note
				)
			);
		}
		let links = document.createElement('div');
		links.className = "choices";
		links.append(App.UI.DOM.generateLinksStrip(linkArray));
		el.appendChild(links);

		return el;
	}

	function shopping() {
		return App.UI.DOM.passageLink(
			`Go shopping for more options`,
			"Wardrobe"
		);
	}

	/**
	 * @param {string} update
	 * @returns {Node}
	 */
	function colorOptions(update) {
		const el = new DocumentFragment();
		const colorChoice = App.UI.DOM.colorInput(
			slave[update],
			v => {
				slave[update] = v;
				refresh();
			}
		);
		el.appendChild(colorChoice);

		if (slave[update]) {
			el.appendChild(
				App.UI.DOM.link(
					` Reset`,
					() => {
						delete slave[update];
						refresh();
					},
				)
			);
		}
		return el;
	}

	function refresh() {
		App.Events.refreshEventArt(slave);
		contentRefresh();
	}

	/**
	 * Figure out a tooltip text to use based on clothing name.
	 * Described effects are mainly from saClothes.js some are from saLongTermMentalEffects.js or saLongTermPhysicalEffects.js
	 * Potential fetish revelations are not mentioned.
	 * Chastity options could mention that at least fucktoys can appreciate maintaining their virginity but I assume just choosing a hole to focus on has the same effect so it's not really a clothing effect.
	 * what's the word for below 20 devotion slaves? Disobedient?
	 * Also accepting is a bit weird for ones above, I think I've seen obedient being used instead.
	 * @param {string} itemName
	 * @param {string} category
	 */
	function itemTooltip(itemName, category) {
		if (itemName === "none" || ["armAccessory", "legAccessory"].includes(category)) {
			return "No effect one way or another.";
		}
		let desc = [];
		let item;
		if (App.Data[category]) {
			item = App.Data[category].get(itemName);
		} else {
			item = App.Data.slaveWear[category].get(itemName);
		}
		if (!item && V.customItem[category]) {
			item = V.customItem[category].get(itemName);
		}

		switch (category) {
			case "clothes":
				if (itemName === "no clothing") {
					desc.push("Increases devotion for resistant slaves, humiliation fetishists, submissives and nymphos.");
				} else if (item) {
					switch (item.exposure) {
						case 4:
							desc.push("Might as well be naked.");
							break;
						case 3:
							desc.push("Humiliating.");
							break;
						case 2:
							desc.push("Slutty.");
							break;
						case 1:
							desc.push("Normal.");
							break;
						case 0:
							desc.push("Modest.");
							break;
						default:
							if (!item.harsh) {
								desc.push("Only nice (meaning non-obedients lose devotion and fear while obedients gain devotion and trust)");
							}
					}
				}
				break;
			case "collar":
				if (item.harsh) {
					desc.push("Increases fear for non-obedient slaves.");
				} else {
					desc.push("On obedient slaves reduces fear, on non-obedient ones reduces fear a lot and devotion somewhat.");
				}
				break;
			case "shoes":
				if (item.heelHeight === 0) {
					desc.push("Have no effect one way or another.");
				} else if (item.heelHeight > 20) {
					desc.push(`Increases height, slaves with natural legs who are resistant resent and fear wearing them while non-resistant ones become more fearful(unless masochistic) and obedient. Heel height: ${item.heelHeight}cm.`);
				} else {
					desc.push(`Increases height, resistant slaves with natural legs resent wearing them. Heel height: ${item.heelHeight}cm.`);
				}
				if (item.platformHeight > 0) {
					desc.push(`Platform height: ${item.platformHeight}cm.`);
				}
				break;
			case "bellyAccessory":
				if (item.empathyBelly) {
					desc.push("Strengthens or removes(a weak) pregnancy fetish and affects devotion in various ways depending on devotion, fertility and having a pregnancy fetish or breeder flaw.");
				}
				break;
			case "vaginalAccessory":
				if (item.vibrates >= 2) {
					desc.push("Increases devotion and affects a specific fetish, attraction or sex drive.");
				} else if (item.vibrates === 1) {
					desc.push("Increases devotion but weakens fetish and libido.");
				} else if (item.width >= 3) {
					desc.push("Stretches vagina into a cavernous one, on smaller vaginas size queens get much more devoted, masochists and submissives much more devoted and fearful and anyone else becomes much less devoted and trusting. Might cause miscarriage.");
				} else if (item.width === 2) {
					desc.push("Stretches vagina into a loose one, on a tight vagina increases obedience and fear.");
				} else if (item.width === 1) {
					if (item.length >= 2) {
						desc.push("Stretches vagina from virgin to tight, might remove hatred of penetration. Makes size queens happy while others less trusting.");
					} else {
						desc.push("Stretches vagina from virgin to tight, might remove hatred of penetration.");
					}
				}
				break;
			case "buttplug":
				if (item.width >= 3) {
					desc.push("Stretches butthole into a cavernous one, on smaller buttholes size queens get much more devoted, masochists and submissives much more devoted and fearful and anyone else becomes much less devoted and trusting.");
				} else if (item.width === 2) {
					desc.push("Stretches butthole into a loose one, on a tight butthole increases obedience and fear.");
				} else if (item.width === 1) {
					if (item.length >= 2) {
						desc.push("Stretches butthole from virgin to tight, might remove hatred of penetration. Makes size queens happy.");
					} else {
						desc.push("Stretches butthole from virgin to tight, might remove hatred of anal.");
					}
				}
				break;
			case "buttplugAttachments":
				if (itemName !== "none" && App.Data.slaveWear.buttplugAttachment.get(itemName)) {
					desc.push("Makes it more scary to wear a plug but might give humiliation fetish,");
				}
				break;
		}

		if (item) {
			if (item.note) {
				desc.push(item.note);
			}
			if (item.fs) {
				const reactions = new Map([
					["loves", "like"],
					["tolerates", "tolerate"],
					["hates", "hate"],
				]);
				for (const [key, value] of reactions) {
					if (item.fs[key]) {
						const lovers = [];
						for (const FS of item.fs[key]) {
							lovers.push(App.Data.FutureSociety.records[FS].noun);
						}
						if (lovers.length > 0) {
							desc.push(`${toSentence(lovers)} will ${value} this look.`); // TODO: Please move tooltip to array when that becomes possible
						}
					}
				}
			}
		}
		return desc.join(" ");
	}

	/** Generate a row of choices
	 * @param {Map<string, slaveWear>} map
	 * @param {string} [category] - should be in the form of slave.category, the thing we want to update.
	 * @param {boolean} [accessCheck=false]
	 * @returns {HTMLUListElement}
	 */
	function generateRows(map, category, accessCheck = false) {
		const linkArray = [];
		for (const [name, item] of map) {
			let link;
			// Some items will never be in App.Data.slaveWear.slaveWear, especially "none" if it falls in between harsh and nice data sets. Trying to look it up would cause an error, which is what access check works around.
			const unlocked = (accessCheck === true) ? isItemAccessible.entry(name, category, slave) : false;
			if (accessCheck === false || unlocked) {
				if (typeof unlocked === 'string') { // is it just text?
					link = App.UI.DOM.disabledLink(item.name, [unlocked]);
				} else {
					link = document.createElement('span');

					// Set up the link
					link.appendChild(
						App.UI.DOM.link(
							`${item.name} `,
							() => {
								slave[category] = name;
								if (category === "clothes") {
									slave.choosesOwnClothes = 0;
								}
								refresh();
							},
							[],
							"",
							itemTooltip(name, category)
						)
					);

					if (item.fs && item.fs.unlocks) {
						link.append(App.UI.DOM.spanWithTooltip(`FS`, FutureSocieties.displayAdj(item.fs.unlocks), ["note"]));
					}
				}
				linkArray.push(link);
			}
		}

		return App.UI.DOM.generateLinksStrip(linkArray);
	}

	function noneLink(slaveSlot) {
		return App.UI.DOM.link(
			` None`,
			() => {
				slave[slaveSlot] = "none";
				refresh();
			}
		);
	}
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @param {function():void} refresh
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.work = function(slave, refresh) {
	let el = new DocumentFragment();
	let p;
	let div;
	let span;
	const links = [];
	const {
		He,
		his, him
	} = getPronouns(slave);

	function appendLink(text, whoreClass, enabled, disabledText) {
		const link = {text: text};
		if (enabled) {
			link.whoreClass = whoreClass;
		} else {
			link.disabled = disabledText;
		}
		links.push(link);
	}

	p = document.createElement('p');
	if (slave.assignment === Job.AGENT) {
		const arc = V.arcologies.find((a) => a.leaderID === slave.ID);
		p.className = "scene-intro";
		p.textContent = `${He} is serving as your Agent${arc ? ` leading ${arc.name}` : ` but is not currently assigned to an arcology`}. `;
		p.appendChild(App.UI.DOM.link(`Recall and reenslave ${him}`, () => {
			removeJob(slave, slave.assignment, false);
			refresh();
		}));
	} else if (slave.assignment === Job.AGENTPARTNER) {
		const agent = getSlave(slave.relationshipTarget);
		const arc = agent ? V.arcologies.find((a) => a.leaderID === agent.ID) : null;
		p.className = "scene-intro";
		p.textContent = `${He} is living with your Agent ${SlaveFullName(agent)}${arc ? ` in ${arc.name}` : ``}. `;
		p.appendChild(App.UI.DOM.link(`Recall ${him}`, () => {
			removeJob(slave, slave.assignment, false);
			refresh();
		}));
	} else {
		div = document.createElement('div');
		div.id = "mini-scene";
		p.appendChild(div);
		p.appendChild(useSlaveDisplay());
	}
	el.append(p);

	span = document.createElement('span');
	span.className = "note";
	switch (slave.assignment) {
		case Job.BODYGUARD:
			span.textContent = `${He} is your Bodyguard and is not available for other work`;
			break;
		case Job.MADAM:
			span.textContent = `${He} is the Madam and is not available for other work`;
			break;
		case Job.DJ:
			span.textContent = `${He} is the DJ and is not available for other work`;
			break;
		case Job.MILKMAID:
			span.textContent = `${He} is the Milkmaid and is not available for other work`;
			break;
		case Job.FARMER:
			span.textContent = `${He} is the Farmer and is not available for other work`;
			break;
		case Job.STEWARD:
			span.textContent = `${He} is the Stewardess and is not available for other work`;
			break;
		case Job.HEADGIRL:
			span.textContent = `${He} is your Head Girl and is not available for other work`;
			break;
		case Job.RECRUITER:
			span.textContent = `${He} is recruiting slaves and is not available for other work`;
			break;
		case Job.NURSE:
			span.textContent = `${He} is the Nurse and is not available for other work`;
			break;
		case Job.ATTENDANT:
			span.textContent = `${He} is the Attendant of the spa and is not available for other work`;
			break;
		case Job.MATRON:
			span.textContent = `${He} is the Matron of the nursery and is not available for other work`;
			break;
		case Job.TEACHER:
			span.textContent = `${He} is the Schoolteacher and is not available for other work`;
			break;
		case Job.CONCUBINE:
			span.textContent = `${He} is your Concubine and is not available for other work`;
			break;
		case Job.WARDEN:
			span.textContent = `${He} is the Wardeness and is not available for other work`;
			break;
		default:
			// CAN BE REASSIGNED
			span.classList.remove("note");
			// span.id = "assignmentLinks";
			span.appendChild(assignmentBlock());

			if ((V.brothel + V.club + V.dairy + V.farmyard + V.servantsQuarters + V.masterSuite + V.spa + V.nursery + V.clinic + V.schoolroom + V.cellblock + V.arcade + V.HGSuite) > 0) {
				span.append(`Transfer to: `);
				span.appendChild(App.UI.jobLinks.transfersFragment(slave.ID));
			}

			div = document.createElement('div');
			div.id = "fucktoy-pref";
			div.append(fucktoyPref());
			span.appendChild(div);
	}
	el.append(span);

	if (slave.assignment === Job.WHORE || slave.assignment === Job.BROTHEL) {
		div = document.createElement('div');
		div.textContent = `Whoring Target: `;
		span = document.createElement('span');
		span.style.fontWeight = "bold";

		span.id = "whoreClass";
		if (!slave.whoreClass) {
			span.textContent = `auto`;
		} else if (slave.whoreClass === 1) {
			span.textContent = `the lower class`;
		} else if (slave.whoreClass === 2) {
			span.textContent = `the middle class`;
		} else if (slave.whoreClass === 3) {
			span.textContent = `the upper class`;
		} else if (slave.whoreClass === 4) {
			span.textContent = `millionaires`;
		} else {
			span.textContent = `THERE HAS BEEN AN ERROR`;
		}
		div.append(span);
		div.append(`. `);

		div.append(App.UI.DOM.makeElement('span', `This is the highest class they are allowed to service, when eligible `, 'note'));

		appendLink(`Auto`, 0, true);
		appendLink(`Lower Class`, 1, true);
		appendLink(`Middle Class`, 2, true);
		appendLink(`Upper Class`, 3, true);
		appendLink(`Millionaires Class`, 4, true);
		div.appendChild(App.UI.DOM.generateLinksStrip(links.map(generateLink)));

		el.append(div);
	}

	el.append(tutorBlock());
	return el;

	function generateLink(linkDesc) {
		const span = document.createElement("span");
		// is it just text?
		if (linkDesc.disabled) { return App.UI.DOM.disabledLink(linkDesc.text, [linkDesc.disabled]); }
		// Are they already on this whoreClass?
		if (linkDesc.whoreClass === slave.whoreClass) { return document.createTextNode(linkDesc.text); }
		// Set up the link
		span.append(App.UI.DOM.link(
			linkDesc.text,
			() => {
				slave.whoreClass = linkDesc.whoreClass;
				refresh();
			},
		));

		// add a note node if required
		if (linkDesc.note) {
			span.append(" ");
			App.UI.DOM.appendNewElement("span", span, linkDesc.note, ["note"]);
		}
		return span;
	}

	function assignmentBlock() {
		let el = document.createElement('div');
		let title = document.createElement('div');
		const linkArray = [];
		title.append(`Assignment: `);

		let assign = document.createElement('span');
		assign.style.fontWeight = "bold";
		if (slave.sentence) {
			assign.textContent = `${slave.assignment} for ${years(slave.sentence)}. `;
		} else {
			assign.textContent = `${slave.assignment}. `;
		}
		title.appendChild(assign);
		if (V.assignmentRecords[slave.ID] && V.assignmentRecords[slave.ID] !== slave.assignment) {
			title.append(`Previously: `);
			assign = document.createElement('span');
			assign.style.fontWeight = "bold";
			assign.textContent = `${V.assignmentRecords[slave.ID]}. `;
			title.appendChild(assign);
		}
		if (slave.assignment === Job.SUBORDINATE) {
			const target = getSlave(slave.subTarget);
			let linkText;
			if (target) {
				title.appendChild(document.createTextNode(`Serving ${target.slaveName} exclusively. `));
				linkText = `Change`;
			} else if (slave.subTarget === -1) {
				title.appendChild(document.createTextNode(`Serving as a Stud. `));
				linkText = `Change role`;
			} else {
				title.appendChild(document.createTextNode(`Serving all your other slaves. `));
				linkText = `Choose a specific slave to submit to`;
			}
			linkArray.push(App.UI.DOM.passageLink(linkText, "Subordinate Targeting", () => { V.returnTo = "Slave Interact"; }));
		}
		if (slave.assignment !== Job.CHOICE) {
			linkArray.push(
				App.UI.DOM.link(
					`Stay on this assignment for another month`,
					() => {
						slave.sentence += 4;
						refresh();
					},
				)
			);
		}
		title.append(App.UI.DOM.generateLinksStrip(linkArray));
		el.appendChild(title);

		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(
			App.UI.jobLinks.assignmentsFragment(
				slave.ID, passage(),
				(slave, assignment) => {
					assignJob(slave, assignment);
				}
			)
		);
		el.appendChild(links);
		return el;
	}

	function tutorBlock() {
		let el = App.UI.DOM.makeElement("div");
		let title = App.UI.DOM.appendNewElement("div", el, `Private tutoring: `);
		let tutor = tutorForSlave(slave);

		if (tutor === null) {
			App.UI.DOM.appendNewElement("span", title, `none.`, ["bold"]);
		} else {
			App.UI.DOM.appendNewElement("span", title, tutor + `.`, ["bold"]);
		}

		if (tutor != null) {
			App.UI.DOM.appendNewElement("span", title, ` To progress slave needs to be assigned to: "` + Job.CLASSES + `" or "` + Job.SCHOOL + `".`, ["note"]);
		}

		let list = ["None"];
		for (const keys of Object.keys(V.slaveTutor)) {
			list.push(keys);
		}
		const array = list.map((s) => {
			if (shouldBeEnabled(slave, s)) {
				return App.UI.DOM.link(s, () => setTutorForSlave(slave, s));
			} else {
				let reason = ["Already being taught this skill."];
				return App.UI.DOM.disabledLink(s, reason);
			}
		});
		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(array));

		function shouldBeEnabled(slave, key) {
			let tutor = tutorForSlave(slave);
			if (tutor === null) {
				return "None";
			}
			return (tutor !== key);
		}

		function setTutorForSlave(slave, tutor) {
			const cur = tutorForSlave(slave);

			if (tutor !== cur && cur != null) {
				V.slaveTutor[cur].delete(slave.ID);
			}

			if (cur !== tutor && tutor !== "None") {
				V.slaveTutor[tutor].push(slave.ID);
			}
			refresh();
		}

		return el;
	}

	function useSlaveDisplay() {
		// Goal: Be able to write the entire "use her" block with only dom fragments.
		let el = document.createElement('div');

		/**
		 * @typedef {Object} sexOption
		 * @property {string} text "Link text"
		 * @property {string|App.Interact.BaseChoosePartnerRenderer|function():Node} [scene]
		 * @property {string} [goto] if another passage is needed
		 * @property {Object} [updateSlave] update slave itself if needed, like {trust: 2},
		 * @property {Object} [update] updates V.,
		 * @property {string} [note] if a note must appear after the link
		 * @property {string} [disabled]
		 */
		/** @type {sexOption[]} */
		const sexOptions = [];
		const fillFaceOptions = [];
		const fillAssOptions = [];
		const dairyNameCaps = capFirstChar(V.dairyName);
		// if no scene, it's just text, no link. Italicize it.

		if (slave.fuckdoll === 0) {
			if (slave.vagina > -1) {
				if (canDoVaginal(slave)) {
					sexOptions.push({text: `Fuck ${him}`, scene: () => App.Interact.fVagina(slave)});
					if (canDoAnal(slave)) {
						sexOptions.push({text: `Use ${his} holes`, scene: () => App.Interact.fButt(slave)});
					}
				} else {
					sexOptions.push({
						text: `Fuck ${him}`,
						disabled: `Remove ${his} chastity belt if you wish to fuck ${him}`
					});
				}
			}
			if (slave.bellyPreg >= 300000) {
				if (canDoVaginal(slave) || canDoAnal(slave)) {
					sexOptions.push({text: `Fuck ${him} on ${his} belly`, scene: () => App.Interact.fBellyFuck(slave)});
					if (V.pregInventions >= 1) {
						sexOptions.push({text: `Fuck ${him} in ${his} maternity swing`, scene: () => App.Interact.fMaternitySwing(slave)});
						sexOptions.push({
							text: `Fuck ${him} with the help of ${his} assistants`,
							scene: () => App.Interact.fAssistedSex(slave)
						});
						sexOptions.push({text: `Fuck ${him} in your goo pool`, scene: () => App.Interact.fPoolSex(slave)});
					}
				}
			}

			if (canDoAnal(slave)) {
				sexOptions.push({text: `Fuck ${his} ass`, scene: () => App.Interact.fAnus(slave)});
			} else {
				sexOptions.push({
					text: `Fuck ${his} ass`,
					disabled: `Remove ${his} chastity belt if you wish to fuck ${his} ass`
				});
			}
			sexOptions.push({text: `Use ${his} mouth`, scene: () => App.Interact.fLips(slave)});
			sexOptions.push({text: `Kiss ${him}`, scene: () => App.Interact.fKiss(slave)});
			if (hasAnyLegs(slave)) {
				sexOptions.push({text: `Have ${him} dance for you`, scene: () => App.Interact.fDance(slave)});
			}

			sexOptions.push({text: `Play with ${his} tits`, scene: () => App.Interact.fBoobs(slave)});

			sexOptions.push({text: `Caress ${him}`, scene: () => App.Interact.fCaress(slave)});

			sexOptions.push({text: `Give ${him} a hug`, scene: () => App.Interact.fEmbrace(slave)});
			if (V.cheatMode === 1) {
				sexOptions.push({text: `Pat ${his} head`, scene: () => App.Interact.fPat(slave)});
			}
			if (slave.race === "catgirl") {
				sexOptions.push({text: `Pet ${him}`, scene: () => App.Interact.fPet(slave)});
			}

			sexOptions.push({text: `Grope ${his} boobs`, scene: () => App.Interact.fondleBoobs(slave)});
			if (slave.nipples === "fuckable" && V.PC.dick > 0) {
				sexOptions.push({text: `Fuck ${his} nipples`, scene: () => App.Interact.fNippleFuck(slave)});
			}
			if (slave.lactation > 0 && slave.boobs >= 2000 && slave.belly < 60000 && hasAnyArms(slave)) {
				sexOptions.push({text: `Drink ${his} milk`, scene: () => App.Interact.fSuckle(slave)});
			}

			if (canDoAnal(slave)) {
				sexOptions.push({text: `Grope ${his} butt`, scene: () => App.Interact.fondleButt(slave)});
			}

			if (slave.vagina > -1) {
				if (canDoVaginal(slave)) {
					sexOptions.push({text: `Grope ${his} pussy`, scene: () => App.Interact.fondleVagina(slave)});
					sexOptions.push({text: `Eat ${him} out`, scene: () => App.Interact.fLickPussy(slave)});
				}
			}

			if (slave.dick > 0) {
				if (!(slave.chastityPenis)) {
					sexOptions.push({text: `Grope ${his} dick`, scene: () => App.Interact.fondleDick(slave)});
					if (canPenetrate(slave)) {
						if (V.policies.sexualOpenness === 1 || slave.toyHole === "dick") {
							sexOptions.push({text: `Ride ${his} dick`, scene: () => App.Interact.fDick(slave)});
						}
					}
				} else {
					sexOptions.push({
						text: `Use ${his} dick`,
						disabled: `Remove ${his} dick chastity belt if you wish to play with ${his} cock`
					});
				}
			}

			if (hasAnyLegs(slave) && V.PC.dick > 0) {
				sexOptions.push({text: `Get a footjob`, scene: () => App.Interact.fFeet(slave)});
			}

			if (canGetPregnant(slave) && (slave.geneticQuirks.superfetation !== 2 || V.geneticMappingUpgrade !== 0) && (slave.fuckdoll === 0) && V.seePreg !== 0) {
				if (canImpreg(slave, V.PC)) {
					sexOptions.push({
						text: `Impregnate ${him} yourself`,
						scene: () => App.Interact.fPCImpreg(slave),
					});
				}
				if (canImpreg(slave, slave)) {
					sexOptions.push({text: `Use ${his} own seed to impregnate ${him}`, scene: () => App.Interact.fSlaveSelfImpreg(slave)});
				}
				sexOptions.push({text: `Use another slave to impregnate ${him}`, scene: new App.Interact.fSlaveImpregChoosePartner(slave)});
			}
			if (slave.assignment !== Job.DAIRY && slave.assignment !== Job.ARCADE && slave.assignment !== Job.CELLBLOCK) {
				if (V.dairyPiping === 1) {
					if ((V.milkPipeline > 88 && V.milkPipeline !== 0) || V.arcologies[0].FSPastoralistLaw === 1) {
						if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
							if (slave.inflationType === "milk" || slave.inflationType === "none") {
								fillAssOptions.push({
									text: `Milk`,
									scene: () => App.Interact.fillUpButt(slave),
									updateSlave: {inflationType: "milk", inflationMethod: 2}
								});
								fillFaceOptions.push({
									text: `Milk`,
									scene: () => App.Interact.fillUpFace(slave),
									updateSlave: {inflationType: "milk", inflationMethod: 1}
								});
							}
						}
					} else {
						fillAssOptions.push({
							text: `Milk`,
							disabled: `${dairyNameCaps} is not producing enough milk to pump through the pipes`
						});
						fillFaceOptions.push({
							text: `Milk`,
							disabled: `${dairyNameCaps} is not producing enough milk to pump through the pipes`
						});
					}
					if ((V.cumPipeline > 88 && V.cumPipeline !== 0) || V.arcologies[0].FSPastoralistLaw === 1) {
						if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
							if (slave.inflationType === "cum" || slave.inflationType === "none") {
								fillAssOptions.push({
									text: `Cum`,
									scene: () => App.Interact.fillUpButt(slave),
									updateSlave: {inflationType: "cum", inflationMethod: 2}
								});
								fillFaceOptions.push({
									text: `Cum`,
									scene: () => App.Interact.fillUpFace(slave),
									updateSlave: {inflationType: "cum", inflationMethod: 1}
								});
							}
						}
					} else {
						fillAssOptions.push({
							text: `Cum`,
							disabled: `${dairyNameCaps} is not producing enough cum to pump through the pipes`
						});
						fillFaceOptions.push({
							text: `Cum`,
							disabled: `${dairyNameCaps} is not producing enough cum to pump through the pipes`
						});
					}
				} /* dairyPiping === 1 */
				if (V.boughtItem.toys.enema === 1) {
					if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
						if (slave.inflationType === "water" || slave.inflationType === "none") {
							fillAssOptions.push({
								text: `Water`,
								scene: () => App.Interact.fillUpButt(slave),
								updateSlave: {inflationType: "water", inflationMethod: 2}
							});
						}
						if (V.boughtItem.toys.medicalEnema === 1) {
							if (slave.inflationType === "aphrodisiac" || slave.inflationType === "none") {
								fillAssOptions.push({
									text: `Aphrodisiacs`,
									scene: () => App.Interact.fillUpButt(slave),
									updateSlave: {inflationType: "aphrodisiac", inflationMethod: 2}
								});
							}
							if (slave.inflationType === "curative" || slave.inflationType === "none") {
								fillAssOptions.push({
									text: `Curatives`,
									scene: () => App.Interact.fillUpButt(slave),
									updateSlave: {inflationType: "curative", inflationMethod: 2}
								});
							}
							if (slave.inflationType === "tightener" || slave.inflationType === "none") {
								fillAssOptions.push({
									text: `Rectal tighteners`,
									scene: () => App.Interact.fillUpButt(slave),
									updateSlave: {inflationType: "tightener", inflationMethod: 2}
								});
							}
						}
					} /* inflation < 3 */
				} /* enema === 1 */
				if (V.wcPiping === 1) {
					if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
						if (slave.inflationType === "urine" || slave.inflationType === "none") {
							fillAssOptions.push({
								text: `Urine`,
								scene: () => App.Interact.fillUpButt(slave),
								updateSlave: {inflationType: "urine", inflationMethod: 2}
							});
						}
					}
				} /* wcPiping === 1 */
			} /* assigned to dairy or arcade */
			if (slave.inflation === 0 && slave.bellyImplant < 1500) {
				if (slave.assignment !== Job.DAIRY && slave.assignment !== Job.ARCADE && slave.assignment !== Job.CELLBLOCK) {
					if (V.boughtItem.toys.buckets === 1) {
						fillFaceOptions.push({
							text: `Two liters of slave food`,
							scene: () => App.Interact.forceFeeding(slave),
							updateSlave: {inflation: 1, inflationType: "food", inflationMethod: 1}
						});
						if (slave.pregKnown === 0) {
							fillFaceOptions.push({
								text: `A gallon of slave food`,
								scene: () => App.Interact.forceFeeding(slave),
								updateSlave: {inflation: 2, inflationType: "food", inflationMethod: 1}
							});
							fillFaceOptions.push({
								text: `Two gallons of slave food`,
								scene: () => App.Interact.forceFeeding(slave),
								updateSlave: {inflation: 3, inflationType: "food", inflationMethod: 1}
							});
						}
					}
					fillFaceOptions.push({text: `Get another slave to do it`, goto: `SlaveOnSlaveFeeding`});
				}
			}
			if (canDoVaginal(slave)) {
				sexOptions.push({text: `Have another slave fuck ${his} pussy`, scene: new App.Interact.fSlaveSlaveVagChoosePartner(slave)});
			}
			if (canDoAnal(slave)) {
				sexOptions.push({text: `Have another slave fuck ${his} ass`, scene: new App.Interact.fSlaveSlaveAssChoosePartner(slave)});
			}
			if (canPenetrate(slave)) {
				sexOptions.push({text: `Have another slave ride ${his} cock`, scene: new App.Interact.fSlaveSlaveDickChoosePartner(slave)});
			} else if (slave.clit >= 4) {
				sexOptions.push({text: `Have another slave ride ${his} clit-dick`, scene: new App.Interact.fSlaveSlaveDickChoosePartner(slave)});
			}
			if (V.seeBestiality) {
				/** @type {FC.SlaveActs} */
				let act;

				if ([Job.FUCKTOY, Job.MASTERSUITE].includes(slave.assignment)) {
					if (slave.toyHole === "pussy") {
						act = "vaginal";
					} else if (slave.toyHole === "ass") {
						act = "anal";
					} else if (slave.toyHole === "mouth") {
						act = "oral";
					} else {
						if (canDoVaginal(slave)) {
							act = "vaginal";
						} else if (canDoAnal(slave)) {
							act = "anal";
						} else {
							act = "oral";
						}
					}
				} else if (canDoVaginal(slave)) {
					act = "vaginal";
				} else if (canDoAnal(slave)) {
					act = "anal";
				} else {
					act = "oral";
				}

				if (V.farmyardKennels > 0 && V.active.canine) {
					sexOptions.push({
						text: `Have ${getAnimal(V.active.canine).articleAn} ${V.active.canine} mount ${him}`,
						scene: () => App.Interact.fAnimal(slave, "canine", act),
					});
				}
				if (V.farmyardStables > 0 && V.active.hooved) {
					sexOptions.push({
						text: `Let ${getAnimal(V.active.hooved).articleAn} ${V.active.hooved} mount ${him}`,
						scene: () => App.Interact.fAnimal(slave, "hooved", act),
					});
				}
				if (V.farmyardCages > 0 && V.active.feline) {
					sexOptions.push({
						text: `Have ${getAnimal(V.active.feline).articleAn} ${V.active.feline} mount ${him}`,
						scene: () => App.Interact.fAnimal(slave, "feline", act),
					});
				}
			}
			sexOptions.push({text: `Abuse ${him}`, scene: () => App.Interact.fAbuse(slave)});
			if (V.seeIncest === 1 && totalRelatives(slave) > 0) {
				const availableRelatives = V.slaves.filter(s => areRelated(s, slave) && isSlaveAvailable(s));
				if (availableRelatives.length > 1) {
					sexOptions.push({
						text: `Fuck ${him} with one of ${his} close relatives`,
						scene: new App.Interact.fRelationChoosePartner(slave),
					});
				} else if (availableRelatives.length === 1) {
					sexOptions.push({
						text: `Fuck ${him} with ${his} ${relativeTerm(slave, availableRelatives[0])} ${SlaveFullName(availableRelatives[0])}`,
						scene: () => App.Interact.fRelation(slave, availableRelatives[0]),
					});
				} else {
					sexOptions.push({text: `None of ${his} close relatives are available`});
				}
			}
			if (slave.relationship > 0) {
				const lover = getSlave(slave.relationshipTarget);
				if (isSlaveAvailable(lover)) {
					sexOptions.push({
						text: `Fuck ${him} with ${his} ${relationshipTermShort(slave)} ${SlaveFullName(lover)}`,
						scene: () => App.Interact.fRelation(slave, lover)
					});
				} else if (lover.assignment === Job.AGENT) {
					if (slave.broodmother < 2) {
						sexOptions.push({
							text: `Send ${him} to live with your agent ${SlaveFullName(lover)}`,
							goto: `Agent Company`
						});
					} else {
						sexOptions.push({text: `A hyper-broodmother cannot be sent to live with your agent`});
					}
				} else {
					sexOptions.push({text: `${SlaveFullName(lover)} is unavailable`});
				}
			}
			if (slave.rivalryTarget !== 0 && hasAllLimbs(slave)) {
				const rival = getSlave(slave.relationshipTarget);
				if (isSlaveAvailable(rival) && hasAnyLegs(rival)) {
					sexOptions.push({text: `Abuse ${his} rival with ${him}`, scene: () => App.Interact.fRival(slave)});
				}
			}
			if (slave.fetish !== Fetish.MINDBROKEN && slave.accent < 4 && ((canTalk(slave)) || hasAnyArms(slave))) {
				sexOptions.push({text: `Ask ${him} about ${his} feelings`, scene: () => App.Interact.fFeelings(slave)});
				if (V.PC.dick > 0) {
					sexOptions.push({text: `Make ${him} beg`, scene: () => App.Interact.fBeg(slave)});
				}
			}
			if (slave.devotion >= 100 && slave.relationship < 0 && slave.relationship > -3) {
				sexOptions.push({
					text: `Talk to ${him} about relationships`,
					goto: `Matchmaking`
				});
			}
			if ((V.policies.mixedMarriage === 1 || V.cheatMode === 1) && slave.relationship !== 5 && slave.relationship !== -3) {
				if (V.marrying.includes(slave.ID)) {
					sexOptions.push({text: `Marry ${him}`, disabled: `You are already marrying ${him} this weekend`});
				} else {
					sexOptions.push({text: `Marry ${him}`, goto: "FMarry"});
				}
			}
		} else {
			/* IS A FUCKDOLL */
			sexOptions.push({text: `Fuck ${his} face hole`, scene: () => App.Interact.fFuckdollOral(slave)});
			if (canDoVaginal(slave)) {
				sexOptions.push({text: `Fuck ${his} front hole`, scene: () => App.Interact.fFuckdollVaginal(slave)});
			}
			if (canGetPregnant(slave) && (slave.geneticQuirks.superfetation !== 2 || V.geneticMappingUpgrade !== 0) && V.seePreg !== 0) {
				if (canImpreg(slave, V.PC)) {
					sexOptions.push({text: `Put a baby in ${him}`, scene: () => App.Interact.fFuckdollImpreg(slave)});
				}
			}
			if (canDoAnal(slave)) {
				sexOptions.push({text: `Fuck ${his} rear hole`, scene: () => App.Interact.fFuckdollAnal(slave)});
			}
		}
		const activeSlaveRepSacrifice = repGainSacrifice(slave, V.arcologies[0]);
		if (activeSlaveRepSacrifice > 0) {
			/*
			TODO: fix this interaction. Consider that it "takes a week" but is not tied to endweek. Consider how easy it is to cheese for rep gains.
			 should probably be an assignment more than an interaction. - Pregmodder
			sexOptions.push({
				text: `Require ${him} to offer penance to Xochiquetzal`,
				goto: `Aztec Slave Sacrifice Penance`,
				note: `This will gain you ${activeSlaveRepSacrifice} reputation`,
			});*/
			if (V.arcologies[0].FSPaternalist === "unset" && (slave.breedingMark === 0 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
				sexOptions.push({
					text: `Sacrifice ${him} on the altar`,
					goto: `Aztec Slave Sacrifice Life`,
					note: `This will kill ${him} and gain you ${activeSlaveRepSacrifice} reputation`,
				});
			}
		}
		el.append(`Use ${him}: `);
		el.appendChild(generateRows(sexOptions));

		if (V.experimental.sexOverhaul) {
			el.appendChild(App.UI.SlaveInteract.useSlave(slave));
		}

		if (!jQuery.isEmptyObject(fillFaceOptions)) {
			let fill = document.createElement('div');
			fill.appendChild(document.createTextNode(` Fill ${his} mouth with: `));
			fill.appendChild(generateRows(fillFaceOptions));
			el.appendChild(fill);
		}
		if (!jQuery.isEmptyObject(fillAssOptions)) {
			let fill = document.createElement('div');
			fill.appendChild(document.createTextNode(` Fill ${his} ass with: `));
			fill.appendChild(generateRows(fillAssOptions));
			el.appendChild(fill);
		}

		function generateRows(sexArray) {
			const linkArray = [];
			for (let i = 0; i < sexArray.length; i++) {
				let link;

				// is it just text?
				if (sexArray[i].disabled) {
					link = App.UI.DOM.disabledLink(sexArray[i].text, [sexArray[i].disabled]);
				} else {
					// Set up the link
					link = App.UI.DOM.link(
						sexArray[i].text,
						() => { click(sexArray[i]); }
					);

					// add a note node if required
					if (sexArray[i].note) {
						link = App.UI.DOM.combineNodes(
							link,
							" ",
							(App.UI.DOM.makeElement('span', sexArray[i].note, 'note'))
						);
					}
				}
				linkArray.push(link);
			}

			return App.UI.DOM.generateLinksStrip(linkArray);

			function click(sexOption) {
				if (sexOption.updateSlave) {
					Object.assign(slave, sexOption.updateSlave);
				}
				if (sexOption.update) {
					Object.assign(V, sexOption.update);
				}

				if (sexOption.goto) {
					// just play the passage, no need to refresh anything here
					Engine.play(sexOption.goto);
				} else if (sexOption.scene) {
					if (sexOption.scene instanceof App.Interact.BaseChoosePartnerRenderer) {
						App.Interact.choosePartner(sexOption.scene, "#mini-scene", refresh);
					} else {
						let frag;
						if (typeof sexOption.scene === "string") {
							// Run scene and store render results temporarily
							frag = App.UI.DOM.renderPassage(sexOption.scene);
						} else if (typeof sexOption.scene === "function") {
							frag = sexOption.scene();
						}
						// Refresh (clears scene display)
						refresh();

						// Display scene render results
						$("#mini-scene").append(frag);
					}
				} else {
					// just refresh
					refresh();
				}
			}
		}

		return el;
	}

	function fucktoyPref() {
		const el = document.createElement('div');
		let links = [];

		function appendLink(text, toyHole, enabled, disabledText) {
			const link = {text: text};
			if (enabled) {
				link.toyHole = toyHole;
			} else {
				link.disabled = disabledText;
			}
			links.push(link);
		}

		if ((slave.assignment === App.Data.Facilities.penthouse.jobs.fucktoy.assignment) || (slave.assignment === App.Data.Facilities.masterSuite.jobs.fucktoy.assignment) || (slave.assignment === App.Data.Facilities.masterSuite.manager.assignment)) {
			App.UI.DOM.appendNewElement("span", el, "Fucktoy use preference:", "story-label");
			el.append(` `);

			const hole = App.UI.DOM.appendNewElement('span', el, `${slave.toyHole}. `);
			hole.style.fontWeight = "bold";

			appendLink('Mouth', 'mouth', true);
			appendLink('Tits', 'boobs', true);
			if (slave.vagina >= 0) {
				appendLink('Pussy', 'pussy', slave.vagina > 0 && canDoVaginal(slave), `Take ${his} virginity before giving ${his} pussy special attention`);
			}
			appendLink('Ass', 'ass', (slave.anus > 0) && canDoAnal(slave), `Take ${his} anal virginity before giving ${his} ass special attention`);
			if (slave.dick > 0 && canPenetrate(slave)) {
				appendLink('Dick', 'dick', true);
			}
			appendLink('No Preference', "all her holes", true);
		}

		function generateLink(linkDesc) {
			const span = document.createElement("span");
			// is it just text?
			if (linkDesc.disabled) { return App.UI.DOM.disabledLink(linkDesc.text, [linkDesc.disabled]); }
			// Are they already on this toyHole?
			if (linkDesc.toyHole === slave.toyHole) { return document.createTextNode(linkDesc.text); }
			// Set up the link
			span.append(App.UI.DOM.link(
				linkDesc.text,
				() => {
					slave.toyHole = linkDesc.toyHole;
					jQuery('#fucktoy-pref').empty().append(fucktoyPref());
				},
			));

			// add a note node if required
			if (linkDesc.note) {
				span.append(" ");
				App.UI.DOM.appendNewElement("span", span, linkDesc.note, ["note"]);
			}
			return span;
		}

		el.appendChild(App.UI.DOM.generateLinksStrip(links.map(generateLink)));

		return el;
	}
};

repo_name: DontAskDontTell/fc

license: mit


code:
App.UI.SlaveInteract.mainPage = function(slave) {
	const el = new DocumentFragment();

	if (!assignmentVisible(slave)) {
		switch (slave.assignment) {
			case "work in the brothel":
			case "be the Madam":
				V.nextLink = "Brothel";
				break;
			case "be confined in the arcade":
				V.nextLink = "Arcade";
				break;
			case "serve in the club":
			case "be the DJ":
				V.nextLink = "Club";
				break;
			case "work in the dairy":
			case "be the Milkmaid":
				V.nextLink = "Dairy";
				break;
			case "work as a farmhand":
			case "be the Farmer":
				V.nextLink = "Farmyard";
				break;
			case "rest in the spa":
			case "be the Attendant":
				V.nextLink = "Spa";
				break;
			case "work as a nanny":
			case "be the Matron":
				V.nextLink = "Nursery";
				break;
			case "learn in the schoolroom":
			case "be the Schoolteacher":
				V.nextLink = "Schoolroom";
				break;
			case "work as a servant":
			case "be the Stewardess":
				V.nextLink = "Servants' Quarters";
				break;
			case "serve in the master suite":
			case "be your Concubine":
				V.nextLink = "Master Suite";
				break;
			case "be confined in the cellblock":
			case "be the Wardeness":
				V.nextLink = "Cellblock";
				break;
			case "get treatment in the clinic":
			case "be the Nurse":
				V.nextLink = "Clinic";
				break;
			case "live with your Head Girl":
				V.nextLink = "Head Girl Suite";
				break;
			case "be your agent":
			case "live with your agent":
				V.nextLink = "Neighbor Interact";
		}
	}

	V.encyclopedia = either("Costs Summary", "Disease in the Free Cities", "Drugs and Their Effects", "From Rebellious to Devoted", "Gender", "Independent Slaves", "Modern Anal", "Nymphomania", "Slave Couture");
	if (slave.dick > 0) {
		V.encyclopedia = "Gender";
	}
	App.Utils.scheduleSidebarRefresh();

	el.append(App.UI.SlaveInteract.navigation(slave));

	/**
	 * @typedef {Object} siCategory
	 * @property {string} title
	 * @property {string} id
	 * @property {DocumentFragment|HTMLElement} node
	 */

	/** @type {Array<siCategory>} */
	const tabs = [
		{
			title: "Description",
			id: "description",
			get node() { return App.UI.SlaveInteract.description(slave); }
		},
		{
			title: "Modify",
			id: "modify",
			get node() { return App.UI.SlaveInteract.modify(slave); }
		},
		{
			title: "Work",
			id: "work",
			get node() { return App.UI.SlaveInteract.work(slave, refresh); }
		},
		{
			title: "Appearance",
			id: "appearance",
			get node() { return App.UI.SlaveInteract.wardrobe(slave, refresh); }
		},
		{
			title: "Physical Regimen",
			id: "physical-regimen",
			get node() { return App.UI.SlaveInteract.physicalRegimen(slave, refresh); }
		},
		{
			title: "Rules",
			id: "rules",
			get node() { return App.UI.SlaveInteract.rules(slave, refresh); }
		},
		{
			title: "Records",
			id: "records",
			get node() { return App.UI.SlaveInteract.records(slave, refresh); }
		},
		{
			title: "Customize",
			id: "customize",
			get node() { return App.UI.SlaveInteract.custom(slave, refresh); }
		},
		{
			title: "Family",
			id: "family-tab",
			get node() { return App.UI.SlaveInteract.family(slave); }
		}
	];

	const contentHolder = document.createElement("span");
	contentHolder.append(content());
	el.append(contentHolder);

	return el;

	function content() {
		if (V.slaveInteractLongForm) {
			return displayWithoutTabs();
		} else {
			return displayWithTabs();
		}
	}

	function refresh() {
		jQuery(contentHolder).empty().append(content());
	}

	/**
	 * @returns {DocumentFragment}
	 */
	function displayWithTabs() {
		const tabBar = new App.UI.Tabs.TabBar("SlaveInteract");
		const f = new DocumentFragment();
		App.Events.drawEventArt(f, slave);
		tabBar.customNode = f;

		for (const tab of tabs) {
			tabBar.addTab(tab.title, tab.id, tab.node);
		}
		return tabBar.render();
	}

	/**
	 * @returns {DocumentFragment}
	 */
	function displayWithoutTabs() {
		const el = new DocumentFragment();
		App.Events.drawEventArt(el, slave);
		for (const tab of tabs) {
			App.UI.DOM.appendNewElement("h2", el, tab.title);
			el.append(tab.node);
		}
		return el;
	}
};


code:
App.UI.subordinateTargeting = function() {
	const node = new DocumentFragment();
	const slave = getSlave(V.AS);
	let r = [];
	const {
		he, him, himself
	} = getPronouns(slave);

	if (slave.subTarget === slave.ID) {
		slave.subTarget = 0;
	} else if (slave.subTarget === -1) {
		if (V.universalRulesImpregnation !== "Stud" || slave.ID !== V.StudID) {
			slave.subTarget = 0;
		}
	}
	if (slave.assignment !== Job.SUBORDINATE) {
		assignJob(slave, Job.SUBORDINATE);
	}
	const st = getSlave(slave.subTarget);
	r.push(slave.slaveName);
	if (st) {
		r.push(`will submit to <span class='slave-name'>${st.slaveName}</span> this week.`);
	} else if (slave.subTarget === -1) {
		r.push(`is acting as your Stud and is tasked with keeping your chattel pregnant.`);
	} else {
		r.push(`has not been given to any particular slave, so ${he} will have to offer ${himself} to everyone this week.`);
		slave.subTarget = 0;
	}
	App.Events.addParagraph(node, r);

	if (slave.subTarget === -1) {
		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
			`Reign ${him} in`,
			() => {
				slave.subTarget = 0;
				V.StudID = 0;
				App.UI.reload();
			}
		));
	} else {
		if (V.universalRulesImpregnation === "Stud" && V.StudID === 0) {
			App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
				"Stud",
				() => {
					slave.subTarget = -1;
					V.StudID = slave.ID;
					App.UI.reload();
				}
			));
		}
		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
			"None",
			() => {
				slave.subTarget = 0;
				App.UI.reload();
			}
		));
		App.UI.DOM.appendNewElement("h2", node, `Select a slave for ${him} to submit to, sexually:`);
		node.append(App.UI.SlaveList.slaveSelectionList(
			s => s.devotion >= -20 && s.fuckdoll === 0 && s.ID !== slave.ID &&
			(!isAmputee(slave) || !isAmputee(s)),
			(s) => App.UI.DOM.passageLink(SlaveFullName(s), 'Subordinate Targeting', () => { slave.subTarget = s.ID; }),
		));
	}
	return node;
};


code:
App.UI.universalRules = function() {
	const node = new DocumentFragment();
	let option;
	const tabBar = new App.UI.Tabs.TabBar("CheatArcology");
	tabBar.addTab("Permissions", "permissions", permissions());
	tabBar.addTab("Slave mods", "mods", mods());
	tabBar.addTab("Names", "names", names());
	if (V.seePreg) {
		tabBar.addTab("Babies", "babies", babies());
	}
	node.append(
		// App.UI.DOM.makeElement("h1", `Universal Rules`),
		tabBar.render()
	);
	return node;

	function permissions() {
		const frag = new DocumentFragment();
		App.UI.DOM.appendNewElement("h2", frag, `Permissions for slaves`);

		let options = new App.UI.OptionsGroup();

		options.addOption("Get consent before fucking other slaves", "universalRulesConsent")
			.addValue("Yes", 1).on()
			.addValue(`No`, 0).off();

		options.addOption("Slave requests for body mods are", "modRequestsAllowed")
			.addValue("Allowed", 1).on()
			.addValue(`Forbidden`, 0).off();

		options.addOption("Slave requests for growth hormones are", "expansionRequestsAllowed")
			.addValue("Allowed", 1).on()
			.addValue(`Forbidden`, 0).off();

		if (V.brothel + V.club + V.dairy + V.farmyard + V.servantsQuarters + V.arcade + V.schoolroom + V.spa + V.nursery + V.clinic + V.masterSuite + V.cellblock > 0) {
			options.addOption("Slaves cooperate with staffed facilities to improve their performance", "universalRulesFacilityWork")
				.addValue("Allow", 1).on()
				.addValue(`Stop`, 0).off();

			options.addOption("Slave permission to assign themselves to facilities when choosing their assignment", "universalRulesAssignsSelfFacility")
				.addValue("Allow", 1).on()
				.addValue(`Stop`, 0).off();
		}

		options.addOption("Immobile slaves maintain their muscles rather than allow themselves to become soft", "universalRulesImmobileSlavesMaintainMuscles")
			.addValue("Mandatory workouts", 1).on()
			.addValue(`Stop`, 0).off();

		frag.append(options.render());

		App.UI.DOM.appendNewElement("h2", frag, `New slaves`);
		options = new App.UI.OptionsGroup();
		options.addOption(`Apply Rules Assistant to newly acquired slaves`, "universalRulesNewSlavesRA")
			.addValue("Yes", 1).on()
			.addValue("No", 0).off();
		frag.append(options.render());
		return frag;
	}

	function mods() {
		const frag = new DocumentFragment();
		App.UI.DOM.appendNewElement("h2", frag, `Branding for slaves`);
		frag.append(App.UI.brandSelect("official"));

		App.UI.DOM.appendNewElement("h2", frag, `Scarring for slaves`);
		frag.append(App.UI.scarSelect("official"));

		return frag;
	}

	function names() {
		const frag = new DocumentFragment();
		App.UI.DOM.appendNewElement("h2", frag, `Names for slaves`);

		const options = new App.UI.OptionsGroup();

		options.addOption("Slaves keep surnames", "surnamesForbidden")
			.addValue("Yes", 0).on()
			.addValue(`No going forward`, 1).off()
			.customButton(`No and strip all current surnames`, () => {
				for (const slave of V.slaves) {
					slave.slaveSurname = 0;
				}
				V.surnamesForbidden = 1;
			}, passage());

		if (V.surnamesForbidden === 0) {
			options.addOption("Surname convention", "surnameScheme")
				.addValue("Family (Patrilineal)", 0)
				.addValue("Family (Matrilineal)", 1)
				.addValue("Norse (Patronymic)", 2)
				.addValue("Norse (Matronymic)", 3)
				.addValue("Hadesha (Patronymic)", 4)
				.addValue("Hadesha (Matronymic)", 5)
				.addValue("Double-barreled", 6)
				.addValue("Hispanic", 7)
				.addValue("Lusitanic", 8)
				.addComment((() => {
					const start = "If Adam Smith and Betty Jones have a daughter, Charlotte, and a son, Daniel, they will be named ";
					switch (V.surnameScheme) {
						case 0:
							return start + "Charlotte Smith and Daniel Smith";
						case 1:
							return start + "Charlotte Jones and Daniel Jones";
						case 2:
							return start + "Charlotte Adamsdóttir and Daniel Adamsson";
						case 3:
							return start + "Charlotte Bettysdóttir and Daniel Bettysson";
						case 4:
							return start + "Charlotte Adam and Daniel Adam";
						case 5:
							return start + "Charlotte Betty and Daniel Betty";
						case 6:
							return start + "Charlotte Smith-Jones and Daniel Smith-Jones";
						case 7:
							return start + "Charlotte Smith Jones and Daniel Smith Jones";
						case 8:
							return start + "Charlotte Jones Smith and Daniel Jones Smith";
					}
				})());
			if ([0, 1, 6, 7, 8].includes(V.surnameScheme)) {
				options.addOption("Grant this family name to children born without one", "surnameArcology").showTextBox();
			}
			if ([0, 1, 2, 3, 4, 5].includes(V.surnameScheme)) {
				options.addOption("Override gender preference for your own children", "surnamePCOverride")
					.addValue("Follow conventions", 0).on().addValue("Prefer using your name", 1).addValue("Avoid using your name", 2);
			}
		}

		options.addOption("Slave nicknames", "nicknamesAllowed")
			.addValue("Allowed", 1).on()
			.addValue(`Forbidden`, 0).off();

		if (V.FSAnnounced > 0) {
			options.addOption("Future society names for new slaves", "useFSNames")
				.addValue("Applied", 1).on()
				.addValue(`Not applied`, 0).off();

			if (V.useFSNames !== 0) {
				option = options.addOption("Future society names for new slaves", "FSNamePref")
					.addValue(`No direction`, 0);

				if (V.arcologies[0].FSChattelReligionist >= 20) {
					option.addValue(`Devotional`, 1);
				}
				if (V.arcologies[0].FSRomanRevivalist > 20) {
					option.addValue(`Roman`, 2);
				}
				if (V.arcologies[0].FSAztecRevivalist > 20) {
					option.addValue(`Aztec`, 3);
				}
				if (V.arcologies[0].FSEgyptianRevivalist > 20) {
					option.addValue(`Egyptian`, 4);
				}
				if (V.arcologies[0].FSEdoRevivalist > 20) {
					option.addValue(`Japanese`, 5);
				}
				if (V.arcologies[0].FSDegradationist !== "unset") {
					option.addValue(`Degrading`, 6);
				}
				if (V.arcologies[0].FSPaternalist >= 20) {
					option.addValue(`Paternalist`, 7);
				}
				if (V.arcologies[0].FSIntellectualDependency >= 20) {
					option.addValue(`Simple Bimbo`, 8);
				}
				if (V.arcologies[0].FSPastoralist >= 20) {
					option.addValue(`Pastoralist`, 9);
				}
			}
		}
		frag.append(options.render());
		return frag;
	}

	function babies() {
		const frag = new DocumentFragment();
		const r = [];
		App.UI.DOM.appendNewElement("h2", frag, `Pregnant slaves`);
		let options = new App.UI.OptionsGroup();

		if (V.universalRulesImpregnation === "HG") {
			r.push(`Fertile slaves will be systematically impregnated by your Head Girl,`);
			const {he} = getPronouns(S.HeadGirl ? S.HeadGirl : {pronoun: App.Data.Pronouns.Kind.plural});
			if (!V.HeadGirlID) {
				r.push(`once you designate one capable of the act.`);
			} else {
				r.push(`if ${he} is able to do so.`);
			}
		} else if (V.universalRulesImpregnation === "Stud") {
			if (!V.StudID) {
				r.push(`Fertile slaves will be systematically impregnated by a Stud, once you designate a subordinate slave to act as such.`);
			} else {
				const stud = slaveStateById(V.StudID);
				const {he} = getPronouns(stud ? stud : {pronoun: App.Data.Pronouns.Kind.plural});
				r.push(`Fertile slaves will be systematically impregnated by your Stud, if ${he} is able to do so.`);
			}
		} else if (V.universalRulesImpregnation === "PC") {
			r.push(`Fertile slaves will be systematically impregnated by you.`);
		} else {
			r.push(`Fertile slaves will not be systematically impregnated.`);
		}
		option = options.addOption(r.join(" "), "universalRulesImpregnation");
		option.addValue(`No regimen`, "none", () => V.universalHGImpregnateMasterSuiteToggle = 0);
		if (V.PC.dick > 0) {
			option.addValue(`Inseminate them yourself`, "PC", () => V.universalHGImpregnateMasterSuiteToggle = 0);
		}
		if (V.seeDicks !== 0) {
			option.addValue(`Head Girl`, "HG");
		}
		option.addValue(`Stud`, "Stud");

		if (["HG", "Stud"].includes(V.universalRulesImpregnation)) {
			options.addOption(`${V.universalRulesImpregnation}, if able, will impregnate slaves in the Master Suite`, "universalHGImpregnateMasterSuiteToggle")
				.addValue("Yes", 1).on()
				.addValue("No", 0).off();
		}

		if (V.universalRulesImpregnation !== "none") {
			options.addOption(`Further impregnate already-pregnant slaves with superfetation quirk.`, `universalRulesSuperfetationImpregnation`)
				.addValue("Yes", 1).on()
				.addValue("No", 0).off();
			if (V.universalRulesSuperfetationImpregnation === 1) {
				option.addComment(`Already-pregnant slaves with the superfetation quirk will be included in systematic impregnation.`);
			} else {
				option.addComment(`Already-pregnant slaves with the superfetation quirk will be ignored in systematic impregnation.`);
			}
		}

		option = options.addOption(`Scan heavily pregnant slaves daily`, "universalRulesBirthing")
			.addValue("Yes", 1).on()
			.addValue("No", 0).off();
		if (V.universalRulesBirthing === 1) {
			option.addComment(`Heavily pregnant slaves will be scanned daily for signs of labor and moved to a birthing area immediately if any are detected.`);
		} else {
			option.addComment(`Heavily pregnant slaves will be required to work right up until they feel contractions. This will increase upkeep costs for these slaves`);
		}

		option = options.addOption(`Slave birth`, "universalRulesCSec")
			.addValue("C-sections", 1)
			.addValue("Naturally", 0);

		if (V.universalRulesCSec === 1) {
			option.addComment(`Pregnant slaves will be given Caesarean sections rather than allowed to give birth naturally in order to minimize health issues and to preserve their vaginas.`);
		} else {
			option.addComment(`Pregnant slaves will give birth naturally unless health issues pose a threat. Will lessen medical costs, at the expense of scarring`);
		}
		frag.append(options.render());

		App.UI.DOM.appendNewElement("h2", frag, `Babies`);
		options = new App.UI.OptionsGroup();

		option = options.addOption(`Babies will be sent to`, "DefaultBirthDestination")
			.addValue("Decide individually", "individually decided fates", () => V.universalRulesChildrenBecomeBreeders = 0)
			.addValue("Slave orphanage", "an orphanage", () => V.universalRulesChildrenBecomeBreeders = 0)
			.addValue("Citizen school", "a citizen school", () => V.universalRulesChildrenBecomeBreeders = 0)
			.addValue("Private school", "a private school", () => V.universalRulesChildrenBecomeBreeders = 0);
		if (V.incubator.capacity > 0) {
			if (App.Entity.facilities.incubator.capacity - (V.incubator.tanks.length + FetusGlobalReserveCount("incubator")) > 0) {
				option.addValue(`${capFirstChar(V.incubator.name)}`, "the incubator", () => V.universalRulesChildrenBecomeBreeders = 0);
			} else {
				frag.append(`${capFirstChar(V.incubator.name)} has no empty tanks. Either build more or reduce reservations.`);
			}
		}
		if (V.nursery > 0) {
			if (App.Entity.facilities.nursery.capacity - (V.nurseryChildren + FetusGlobalReserveCount("nursery")) > 0) {
				option.addValue(`${capFirstChar(V.nurseryName)}`, "the nursery", () => V.universalRulesChildrenBecomeBreeders = 0);
			} else {
				frag.append(`${capFirstChar(V.nurseryName)} is full.`);
			}
		}

		if (V.policies.cash4Babies > 0) {
			option.addValue("Market", "the market");
		}
		if (V.arcologies[0].FSRepopulationFocus > 40) {
			option.addValue("Breeder schools", "breeder schools", () => V.universalRulesChildrenBecomeBreeders = 1)
				.addComment(`Will require a ${cashFormat(50)} donation per week once enrollment begins`);
		}
		option.addComment(`Children reserved for the Incubator or Nursery will not be affected`);
		frag.append(options.render());
		return frag;
	}
};


code:
#use-slave-container {
	overflow: scroll;
	max-height: 32em;
	margin: 1em 0;
	display: flex;
	flex-direction: column-reverse;
}


code:
/**
 * Creates a new sex scene.
 * @param {App.Entity.SlaveState} slave
 * @returns {HTMLDivElement}
 */
App.UI.SlaveInteract.useSlave = function(slave) {
	// Declarations
	const playerState = new App.UI.SlaveInteract.CharacterState();
	const slaveState = new App.UI.SlaveInteract.CharacterState();
	setUpClothing(playerState, slaveState);

	const PC = V.PC;
	const tempSlave = new App.UI.SlaveInteract.Clone(slave)
		.assign(slaveState)
		.getSlave();

	const {he, him, his} = getPronouns(slave);

	/** @type {FC.UseSlave.Option[]} */
	const log = [];
	/** @enum {string} */
	const none = "none";

	let introShown = false;

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

	div.id = 'use-slave-container';
	div.append(main());

	return div;

	// Main Loop
	function main() {
		const div = document.createElement("div");

		if (playerState.lust < 25 * V.PC.sexualEnergy) {
			if (introShown) {
				div.append(options());
			} else {
				div.append(intro());
			}
		} else {
			if (introShown) {
				div.append(`You are out of stamina.`);
			} else {
				App.UI.DOM.appendNewElement("span", div, `You have recently had sex with ${him}`, ["note"]);
			}
		}

		return div;
	}

	// Text Functions
	function intro() {
		introShown = true;

		const mainSpan = App.UI.DOM.makeElement("span", `Use ${him} and take control: `);

		if (tempSlave.devotion > 50) {
			const intro = App.UI.DOM.makeElement("div", `You pull ${tempSlave.slaveName} in close and tell ${him} that you want to make love. With ${tempSlave.mouthAccessory === none
				? `a ${Beauty(tempSlave) > 150 && tempSlave.face > 10 ? `pretty` : `quick`} smile`
				: `as much of a smile as ${his} ${tempSlave.mouthAccessory} will allow`
			}, ${he} makes it clear that your advances are not unwanted.`);

			intro.append(options());
			mainSpan.append(App.UI.DOM.linkReplace(
				`Have sex with ${him}`,
				intro
			));

			return mainSpan;
		} else if (tempSlave.devotion > 20) {
			const intro = App.UI.DOM.makeElement("div", `You tell ${tempSlave.slaveName} that you want to fuck ${him}. Though ${he} seems hesitant, your tone and the stern look in your eye make it clear that ${he} has no choice, and ${he} reluctantly agrees.`);

			intro.append(options());
			mainSpan.append(App.UI.DOM.linkReplace(
				`Fuck ${him}`,
				intro
			));

			return mainSpan;
		} else {
			const intro = App.UI.DOM.makeElement("div", `You tell ${tempSlave.slaveName} that you're going to fuck ${him}, whether ${he} likes it or not. The daggers ${he} glares at you feel almost physically tangible, but ${he} knows that ${he} ultimately has no choice.`);

			intro.append(options());
			mainSpan.append(App.UI.DOM.linkReplace(
				`Rape ${him}`,
				intro
			));

			return mainSpan;
		}
	}

	// Generator Functions
	function options() {
		const optionsDiv = document.createElement("div");
		const options = App.UI.SlaveInteract.useSlave.options(PC, tempSlave, slave, playerState, slaveState);

		const contextual = generateOptions(options.contextual, optionsDiv);
		const face = generateOptions(options.face, optionsDiv);
		const chest = generateOptions(options.chest, optionsDiv);
		const crotch = generateOptions(options.crotch, optionsDiv);
		const general = generateOptions(options.general, optionsDiv);
		const clothing = generateOptions(options.clothing, optionsDiv);

		optionsDiv.append(contextual, face, chest, crotch, general, clothing);

		return optionsDiv;
	}

	/**
	 * @param {Array<FC.UseSlave.Option>} arr
	 * @param {HTMLDivElement} div
	 * @returns {HTMLDivElement}
	 */
	function generateOptions(arr, div) {
		const mainDiv = document.createElement("div");

		const availableOptions = arr.filter(option => option.prereq());

		mainDiv.append(generateLinks(availableOptions, div));

		return mainDiv;
	}

	/**
	 * @param {Array<FC.UseSlave.Option>} available
	 * @param {HTMLDivElement} div
	 */
	function generateLinks(available, div) {
		const links = [];

		available.forEach((e) => links.push(App.UI.DOM.link(e.link, () => {
			div.innerHTML = e.desc;
			div.append(App.UI.DOM.makeElement("div", e.reaction, ['indent', 'italics']));

			log.push(new App.UI.SlaveInteract.Option(e.link, e.desc, e.tooltip, e.prereq, e.effect, e.reaction));

			e.effect();

			if (V.debugMode) {
				console.log(
					playerState,
					slaveState,
					log,
				);
			}

			div.append(main());

			div.scrollTop = div.scrollHeight - div.clientHeight;
		}, null, '', e.tooltip)));

		return App.UI.DOM.generateLinksStrip(links);
	}

	// Helper functions

	// Adjust characters' clothing state before scene is shown
	function setUpClothing(playerState, slaveState) {
		// Assume the PC is fully clothed. Remove the bra if boobs are small/nonexistent.
		if (V.PC.boobs < 300) {
			playerState.clothing.bra = false;
		}
		// If slave boobs are small/nonexistent, default to no bra.
		if (slave.boobs < 300) {
			slaveState.clothing.bra = false;
		}
		// Adjust slave clothing state based on their outfit.
		const nakedOutfits = ["no clothing", "body oil"];
		const pantyOutfits = ["a skimpy loincloth", "a thong", "panties", "striped panties"];
		const braOutfits = ["a bra", "a sports bra", "a striped bra"];
		const pantyBraOutfits = ["a slutty outfit", "attractive lingerie", "attractive lingerie for a pregnant woman", "kitty lingerie", "panties and pasties", "striped underwear"];
		const noUnderwearOutfits = ["a burkini", "a comfortable bodysuit", "a tight Imperial bodysuit", "a cybersuit", "a fallen nuns habit", "a leotard", "a latex catsuit", "a monokini", "a one-piece swimsuit", "a scalemail bikini", "a slutty klan robe", "a slutty maid outfit", "a slutty nurse outfit", "a slutty pony outfit", "a slutty qipao", "a slutty schutzstaffel uniform", "a string bikini", "a succubus outfit", "a toga", "an apron", "chains", "clubslut netting", "harem gauze", "overalls", "restrictive latex", "shibari ropes", "slutty business attire", "slutty jewelry", "uncomfortable straps"]; // ex. swimsuits, slutty jewelry, bodysuits
		const noBottomOutfits = ["a button-up shirt", "a sweater", "a t-shirt", "a tank-top", "a tube top", "an oversized t-shirt"];
		const noTopOutfits = ["boyshorts", "cutoffs", "jeans", "leather pants", "sport shorts"];

		if (nakedOutfits.includes(slave.clothes)) {
			slaveState.clothing.top.isOff = true;
			slaveState.clothing.bottom.isOff = true;
			slaveState.clothing.bra = false;
			slaveState.clothing.underwear = false;
		} else if (pantyOutfits.includes(slave.clothes)) {
			slaveState.clothing.top.isOff = true;
			slaveState.clothing.bottom.isOff = true;
			slaveState.clothing.bra = false;
		} else if (braOutfits.includes(slave.clothes)) {
			slaveState.clothing.top.isOff = true;
			slaveState.clothing.bottom.isOff = true;
			slaveState.clothing.underwear = false;
		} else if (pantyBraOutfits.includes(slave.clothes)) {
			slaveState.clothing.top.isOff = true;
			slaveState.clothing.bottom.isOff = true;
		} else if (noUnderwearOutfits.includes(slave.clothes)) {
			slaveState.clothing.bra = false;
			slaveState.clothing.underwear = false;
		} else if (noBottomOutfits.includes(slave.clothes)) {
			slaveState.clothing.bottom.isOff = true;
			slaveState.clothing.underwear = false;
		} else if (noTopOutfits.includes(slave.clothes)) {
			slaveState.clothing.top.isOff = true;
			slaveState.clothing.bra = false;
		}
	}
};


code:
/** @enum {boolean} */
App.UI.SlaveInteract.Position = {
	STANDING: true,
	KNEELING: false,
	LAYING: false,
};

/** @enum {string} */
App.UI.SlaveInteract.SexAct = {
	ANAL: 'anal',
	ORAL: 'oral',
	PENETRATING: 'penetrating',
	RECEIVING: 'receiving',
	VAGINAL: 'vaginal',
};

/** @enum {string} */
App.UI.SlaveInteract.Action = {
	FINGERING: 'fingering',
	KISSING: 'kissing',
	RECEIVING: 'receiving',
	TOUCHING: 'touching',
};

App.UI.SlaveInteract.Option = class Option {
	/**
	 * @param {string} link
	 * @param {string} desc
	 * @param {string} tooltip
	 * @param {() => boolean} prereq
	 * @param {() => void} effect
	 * @param {string} reaction
	 */
	constructor(link, desc, tooltip, prereq, effect, reaction) {
		this.link = link;
		this.desc = desc;
		this.tooltip = tooltip;
		this.prereq = prereq;
		this.effect = effect;
		this.reaction = reaction;
	}
};

/** A class containing the different temporary variables and states for each participant. */
App.UI.SlaveInteract.CharacterState = class CharacterState {
	constructor() {
		/** @type {number} The character's lust. */
		this.lust = 0;
		/** @type {number} The lust level when the character's last orgasm occurred */
		this.previousOrgasm = 0;
		/** @type {App.UI.SlaveInteract.Action} Which primary sex act the character is currently doing. */
		this.sexAct = null;
		/** @type {App.UI.SlaveInteract.Action} What other actions the character is currently doing. */
		this.action = null;
		/** @type {App.UI.SlaveInteract.Position} Whether the character is standing, kneeling, or laying down. */
		this.position = null;
		/** @type {boolean} Whether the character is pressed up close to the other actor. */
		this.close = false;
		/** @type {boolean} Whether the character is on the other actor's lap. */
		this.onLap = false;
		/** Properties relating to the character's clothing. */
		this.clothing = {
			/** Properties relating to the character's clothing top. */
			top: {
				/** @type {boolean} Whether the top is pulled up. */
				pulledUp: false,
				/** @type {boolean} Whether the top is off. */
				isOff: false,
			},
			/** Properties relating to the character's clothing bottom. */
			bottom: {
				/**
				 * @type {boolean}
				 * Whether the bottom is pulled down.
				 *
				 * If the clothing is a dress, whether the bottom is pulled up over the character's waist. */
				pulledDown: false,
				/** @type {boolean} Whether the bottom is off. */
				isOff: false,
			},
			/** @type {boolean} Whether the character is wearing a bra. */
			bra: true,
			/** @type {boolean} Whether the character is wearing underwear. */
			underwear: true,
			/** @type {boolean} Whether the character is wearing a strapon. */
			strapon: false,
		};
		/** @type {boolean} Whether the character has performed or received oral sex or not. */
		this.hasDoneOral = false;
		/** @type {boolean} Whether the character has performed or received vaginal sex or not. */
		this.hasDoneVaginal = false;
		/** @type {boolean} Whether the character has performed or received anal sex or not. */
		this.hasDoneAnal = false;
	}

	/** @param {App.UI.SlaveInteract.Clone} actor The actor to assign the state to. */
	assign(actor) {
		actor.state = this;

		return this;
	}

	/** @type {boolean} Whether the character is standing up. */
	get isStanding() {
		return this.position === App.UI.SlaveInteract.Position.STANDING;
	}

	/** @type {boolean} Whether the character is kneeling. */
	get isKneeling() {
		return this.position === App.UI.SlaveInteract.Position.KNEELING;
	}

	/** @type {boolean} Whether the character is laying down. */
	get isLaying() {
		return this.position === App.UI.SlaveInteract.Position.LAYING;
	}

	/** @type {boolean} Whether the character is completely naked. */
	get isNaked() {
		return this.clothing.top.isOff &&
			this.clothing.bottom.isOff &&
			!this.clothing.bra &&
			!this.clothing.underwear;
	}

	/** @type {boolean} Whether the character's chest is accessible. */
	get topFree() {
		return this.clothing.top.isOff || this.clothing.top.pulledUp;
	}

	/** @type {boolean} Whether the character's crotch is accessible. */
	get bottomFree() {
		return this.clothing.bottom.isOff || this.clothing.bottom.pulledDown;
	}
};

/**
 * A class for creating a temporary clone of the slave.
 * Not to be confused with SlaveState's `.clone` property.
 */
App.UI.SlaveInteract.Clone = class Clone {
	/** @param {App.Entity.SlaveState} slave The slave to clone. */
	constructor(slave) {
		this.slave = _.cloneDeep(slave);
		/** @type {App.UI.SlaveInteract.CharacterState} */
		this.state = null;
	}

	/** @param {App.UI.SlaveInteract.CharacterState} state The state to assign to the clone. */
	assign(state) {
		this.state = state;

		return this;
	}

	/** @returns {App.Entity.SlaveState} */
	getSlave() {
		return this.slave;
	}
};