code:
/** Find the previous and next slaves' IDs based on the current sort order
 * @param {FC.SlaveState} slave
 * @returns {[FC.HumanID, FC.HumanID]} - previous and next slave ID
 */
App.UI.SlaveInteract.placeInLine = function(slave) {
	const useSlave = assignmentVisible(slave) ? ((s) => assignmentVisible(s)) : ((s) => slave.assignment === s.assignment);
	SlaveSort.slaves();
	const slaveList = getSlaves().filter(useSlave);
	const curSlaveIndex = [...slaveList.keys()].findIndex((ID) => ID === slave.ID);

	let nextIndex;
	if (curSlaveIndex + 1 > slaveList.size - 1) {
		nextIndex = 0; // wrap around to first slave
	} else {
		nextIndex = curSlaveIndex + 1;
	}
	let prevIndex;
	if (curSlaveIndex - 1 < 0) {
		prevIndex = slaveList.size - 1; // wrap around to last slave
	} else {
		prevIndex = curSlaveIndex - 1;
	}

	return [slaveList.atIndex(prevIndex).key, slaveList.atIndex(nextIndex).key];
};

/** @typedef RowItem
 * @type {object}
 * @property {FC.FutureSociety} [FS] - FS requirement, if any
 * @property {string} [text] - link text
 * @property {object} [updateSlave] - properties to be merged onto the slave
 * @property {object} [update] - properties to be merged into global state
 * @property {string} [disabled] - text indicating why the option is unavailable
 * @property {string} [note]
 */

/** Generate a row of choices
 * @param {RowItem[]} array
 * @param {FC.SlaveState} slave
 * @param {string} [category] - should be in the form of slave.category, the thing we want to update.
 * @param {boolean} [accessCheck=false]
 * @param {Function} [refresh]
 * @returns {HTMLSpanElement}
 */
App.UI.SlaveInteract.generateRows = function(array, slave, category, accessCheck = false, refresh) {
	const linkArray = [];
	for (const item of array) {
		let link;
		// Some items will never be in App.Data.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.
		/** @type {boolean|string} */
		let unlocked;
		if (accessCheck === true) {
			const itemName = (category === "chastity") ? item.text.toLowerCase() : item.updateSlave[category]; // Yucky. Category name does not match for chastity (since it sets multiple kinds of chastity at once). Compare using a lowercase name instead.
			unlocked = isItemAccessible.entry(itemName, category, slave);
		}
		if (accessCheck === false || unlocked) {
			// is it just text?
			if (item.disabled) {
				link = App.UI.DOM.disabledLink(item.text, [item.disabled]);
			} else if (typeof unlocked === 'string') {
				link = App.UI.DOM.disabledLink(item.text, [unlocked]);
			} else {
				link = document.createElement('span');

				// Set up the link
				link.appendChild(
					App.UI.DOM.link(
						`${item.text} `,
						() => { click(item); },
						[],
						"",
						item.note ? item.note : ""
					)
				);

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

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

	/** @param {RowItem} arrayOption */
	function click(arrayOption) {
		if (arrayOption.updateSlave) {
			for (const slaveProperty in arrayOption.updateSlave) {
				_.set(slave, slaveProperty, arrayOption.updateSlave[slaveProperty]);
			}
		}
		if (arrayOption.update) {
			Object.assign(V, arrayOption.update);
		}
		refresh();
	}
};


code:
/**
 * @param {FC.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();

		// Industrial Milker slaves are completely stripped naked and cannot have clothing changed.
		if (slave.assignment === Job.DAIRY && V.dairyRestraintsSetting === 2) {
			frag.append(industrialMilker());
		} else 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;
	}

	/**
	 * Adds text for industrial milker to prevent updating clothing.
	 */
	function industrialMilker() {
		const el = document.createElement("p");
		el.append(`${slave.slaveName} has been assigned to the industrial milker and cannot have their clothing changed.`);
		return el;
	}

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

		const niceFilters = new Map([
			[false, "Nice"],
			[true, "Harsh"],
		]);
		el.append(filterButtons(niceFilters, "harsh"));

		const exposureFilters = new Map([
			[0, "Modest"],
			[1, "Normal"],
			[2, "Slutty"],
			[3, "Humiliating"],
			[4, "Practically nude"],
		]);
		el.append(filterButtons(exposureFilters, "exposure"));

		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);
			}
		}
		el.append(filterButtons(FSFilters, "FSLoves"));

		// 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;
	}

	/**
	 * @param {Map<number|boolean, string>} filters
	 * @param {string} filterKey
	 * @returns {HTMLSpanElement}
	 */
	function filterButtons(filters, filterKey) {
		let span = document.createElement("span");
		span.classList.add("button-group");
		for (const [num, string] of filters) {
			const button = App.UI.DOM.makeElement("button", string);
			if (T.filters[filterKey] === num) {
				button.classList.add("selected", "disabled");
			} else {
				button.onclick = () => {
					T.filters[filterKey] = num;
					refresh();
				};
			}
			span.append(button);
		}
		return span;
	}

	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 = ` Glasses and porcelain masks share the same custom 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() {
		if (slave.buttplug === "none") {
			return new DocumentFragment();
		}

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

		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);

		el.append(lowerAttachmentList(App.Data.slaveWear.buttplugAttachment, "buttplugAttachment"));
		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() {
		// TODO: This check should occur when setting the accessory
		if (dildoWidth(slave) === 0) {
			slave.vaginalAttachment = "none";
			return new DocumentFragment();
		}

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

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

		el.append(lowerAttachmentList(App.Data.slaveWear.vaginalAttachment, "vaginalAttachment"));
		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);

		el.append(lowerAttachmentList(App.Data.slaveWear.dickAccessory, "dickAccessory"));
		return el;
	}

	/**
	 * @param {slaveWearCategory} data
	 * @param {string} category
	 * @returns {HTMLDivElement}
	 */
	function lowerAttachmentList(data, category) {
		let keys = [];
		for (const key of data.keys()) {
			if (key === "none") {
				// skip none in set, we set the link elsewhere.
				continue;
			}
			keys.push(key);
		}

		// Sort
		keys = keys.sort((a, b) => (a > b) ? 1 : -1);

		// Options
		const sortedMap = new Map([]);
		for (const name of keys) {
			sortedMap.set(name, data.get(name));
		}
		let links = document.createElement("div");
		links.className = "choices";
		links.appendChild(generateRows(sortedMap, category, true));
		return links;
	}

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

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

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

		label.append(App.UI.DOM.spanWithTooltip(chastityDevice, itemTooltip(chastityDevice, "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 (V.boughtItem.toys.chastity === 0) {
				continue;
			}
			if (object && object.fs && object.fs.unlocks && !(FutureSocieties.isActive(object.fs.unlocks))) { // 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() {
		if (V.aiCachingStrategy === 'reactive') { contentRefresh(); } else {
			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
	 * @returns {string|HTMLElement}
	 */
	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;
					}
				}
				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;
			case "dickAccessory":
				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.");
				}
				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.`);
						}
					}
				}
			}
		}

		if (desc.length === 0) {
			return undefined;
		}
		if (desc.length <= 1) {
			return desc[0];
		}
		const f = document.createElement("ul");
		for (const line of desc) {
			App.UI.DOM.appendNewElement("li", f, line);
		}
		return f;
	}

	/** 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();
			}
		);
	}
};

repo_name: Nithhogg/fc

license: mit


code:
/**
 * @param {FC.SlaveState} slave
 * @param {function():void} refresh
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.work = function(slave, refresh) {
	let el = new DocumentFragment();
	let p;
	const miniSceneId = "mini-scene";
	const fucktoyDiv = document.createElement('div');
	let span;
	const {
		He,
		his, him
	} = getPronouns(slave);

	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 {
		const miniSceneDiv = document.createElement('div');
		miniSceneDiv.id = miniSceneId;
		p.appendChild(miniSceneDiv);
		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 + App.Entity.facilities.servantsQuarters.capacity + 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));
			}
	}
	el.append(span);

	if (slave.assignment === Job.FUCKTOY || slave.assignment === Job.MASTERSUITE || slave.assignment === Job.CONCUBINE) {
		fucktoyDiv.append(fucktoyPref());
		el.append(fucktoyDiv);
	}

	if (slave.assignment === Job.WHORE || slave.assignment === Job.BROTHEL) {
		const 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']));

		el.append(div);

		const links = [];
		links.push({text: "Auto", whoreClass: 0});
		links.push({text: "Lower Class", whoreClass: 1});
		links.push({text: "Middle Class", whoreClass: 2});
		links.push({text: "Upper Class", whoreClass: 3});
		links.push({text: "Millionaires Class", whoreClass: 4});
		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(links.map(generateLink)), ["choices"]);
	}

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

	/**
	 * @param {object} linkDesc
	 * @param {string} linkDesc.text
	 * @param {number} linkDesc.whoreClass
	 * @returns {Node}
	 */
	function generateLink(linkDesc) {
		// Are they already on this whoreClass?
		if (linkDesc.whoreClass === slave.whoreClass) {
			return App.UI.DOM.disabledLink(linkDesc.text, [`${slave.slaveName} is already targeting this class.`]);
		}
		// Set up the link
		return App.UI.DOM.link(
			linkDesc.text,
			() => {
				slave.whoreClass = linkDesc.whoreClass;
				refresh();
			},
		);
	}

	/**
	 * @returns {HTMLDivElement}
	 */
	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;
	}

	/**
	 * @returns {Node}
	 */
	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) => {
			const reason = disableReasons(s);
			if (reason?.length > 0) {
				return App.UI.DOM.disabledLink(s, reason);
			} else {
				return App.UI.DOM.link(s, () => setTutor(s));
			}
		});
		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(array), ["choices"]);

		/**
		 * @param {string} key
		 * @returns {string[]}
		 */
		function disableReasons(key) {
			if (slave.skill[tutorKeyToSkillKey(key)] >= 200) {
				return [`Already fully trainied in this skill.`];
			}
			let tutor = tutorForSlave(slave);
			if (tutor === key) {
				return [`Already being taught this skill.`];
			}
			return null;
		}

		/**
		 * @param {string} tutor
		 */
		function setTutor(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;
	}

	/**
	 * @returns {HTMLDivElement}
	 */
	function useSlaveDisplay() {
		let el = document.createElement('div');

		/**
		 * @typedef {object} SexOption
		 * @property {string} text "Link text"
		 * @property {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 {Array<SexOption>} */
		const sexOptions = [];
		/** @type {Array<SexOption>} */
		const fillFaceOptions = [];
		/** @type {Array<SexOption>} */
		const fillAssOptions = [];
		const dairyNameCaps = capFirstChar(V.dairyName);

		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.experimental.interactions) {
				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 (isPlayerReceptive(slave) || isLeaderP(slave)) {
						if (V.experimental.interactions) {
							sexOptions.push({text: `Suck ${his} dick`, scene: () => App.Interact.fSuckDick(slave)});
						}
						if (canPenetrate(slave)) {
							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 ( // TODO: Make these part-time assignments.
				!V.temp.slaveIdsImpregnanted?.has(slave.ID) &&
					(
						canGetPregnant(slave) ||
						(!fertKnown(slave) && canGetPregnant(slave, true) && !isMenstruating(slave)) // Unknown fertility state
					) &&
					(
						slave.geneticQuirks.superfetation !== 2 || V.geneticMappingUpgrade !== 0 || slave.womb.length < 1
					) &&
					slave.fuckdoll === 0 &&
					V.seePreg !== 0
			) {
				// Sets variable to prevent repeat interactions this week.
				const setImpreg = () => {
					if (!V.temp.slaveIdsImpregnanted) {
						V.temp.slaveIdsImpregnanted = new Set();
					}
					V.temp.slaveIdsImpregnanted.add(slave.ID);
				};

				if (canImpreg(slave, V.PC, !fertKnown(slave)) || canFemImpreg(slave, V.PC, !fertKnown(slave))) {
					sexOptions.push({
						text: `Impregnate ${him} yourself`,
						scene: () => {
							setImpreg();
							return App.Interact.fPCImpreg(slave);
						},
					});
				}
				if (canImpreg(slave, slave, !fertKnown(slave)) || canFemImpreg(slave, slave, !fertKnown(slave))) {
					sexOptions.push({text: `Use ${his} own seed to impregnate ${him}`, scene: () => {
						setImpreg();
						return App.Interact.fSlaveSelfImpreg(slave);
					}});
				}
				// @ts-ignore - ignore scene error as this is handled correctly in click()
				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.seePee) {
				sexOptions.push({text: `Use ${him} as a human toilet`, scene: () => App.Interact.fToilet(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 && getAnimal(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 && getAnimal(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 && getAnimal(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 = getSlaves().filter(s => areRelated(s, slave) && isSlaveAvailable(s));
				if (availableRelatives.size > 1) {
					sexOptions.push({
						text: `Fuck ${him} with one of ${his} close relatives`,
						// @ts-ignore
						scene: new App.Interact.fRelationChoosePartner(slave),
					});
				} else if (availableRelatives.size === 1) {
					const rel0 = availableRelatives.atIndex(0).value;
					sexOptions.push({
						text: `Fuck ${him} with ${his} ${relativeTerm(slave, rel0)} ${SlaveFullName(rel0)}`,
						scene: () => App.Interact.fRelation(slave, rel0),
					});
				} else {
					sexOptions.push({text: `None of ${his} close relatives are available`});
				}
			}
			if (slave.relationship > 0) {
				const lover = getSlave(slave.relationshipTarget);
				if (lover) {
					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.rivalryTarget);
				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)});
				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) { // TODO: Add fert known stuff and fem preg
				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
			 Make this a part-time assignment.
			sexOptions.push({
				text: `Require ${him} to offer penance to Xochiquetzal`,
				goto: `Aztec Slave Sacrifice Penance`,
				note: `This will gain you ${activeSlaveRepSacrifice} reputation`,
			});*/
			if (!FutureSocieties.isActive('FSPaternalist') && (slave.breedingMark === 0 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || !FutureSocieties.isActive('FSRestart'))) {
				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 (fillFaceOptions.length > 0) {
			let fill = document.createElement('div');
			fill.appendChild(document.createTextNode(` Fill ${his} mouth with: `));
			fill.appendChild(generateRows(fillFaceOptions));
			el.appendChild(fill);
		}
		if (fillAssOptions.length > 0) {
			let fill = document.createElement('div');
			fill.appendChild(document.createTextNode(` Fill ${his} ass with: `));
			fill.appendChild(generateRows(fillAssOptions));
			el.appendChild(fill);
		}

		/**
		 * @param {Array<SexOption>} sexArray
		 * @returns {HTMLUListElement}
		 */
		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);

			/**
			 * @param {SexOption} sexOption
			 */
			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, miniSceneId, refresh);
					} else {
						// run the scene first, storing the results
						const output = sexOption.scene();
						// refresh the host passage, to pick up changes to slave
						refresh();
						// display the results
						document.getElementById(miniSceneId).replaceChildren(output);
					}
				} else {
					// just refresh
					refresh();
				}
			}
		}

		return el;
	}

	/**
	 * @returns {DocumentFragment}
	 */
	function fucktoyPref() {
		const el = new DocumentFragment();
		/**
		 * @typedef {object} FucktoyLinkDesc
		 * @property {string} text
		 * @property {FC.ToyHole} [toyHole]
		 * @property {string} [disabled]
		 */

		/** @type {Array<FucktoyLinkDesc>} */
		let links = [];

		/**
		 * @param {string} text
		 * @param {FC.ToyHole} toyHole
		 * @param {boolean} enabled
		 * @param {string} [disabledText] Must be provided if 'enabled' is False
		 */
		function appendLink(text, toyHole, enabled, disabledText) {
			const link = {text: text};
			if (enabled) {
				link.toyHole = toyHole;
			} else {
				link.disabled = disabledText;
			}
			links.push(link);
		}

		el.append(`Fucktoy use preference: `);
		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);

		/**
		 * @param {FucktoyLinkDesc} linkDesc
		 * @returns {Node}
		 */
		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 App.UI.DOM.disabledLink(linkDesc.text, [slave.slaveName + " is already preferring this."]);
			}
			// Set up the link
			span.append(App.UI.DOM.link(
				linkDesc.text,
				() => {
					slave.toyHole = linkDesc.toyHole;
					fucktoyDiv.replaceChildren(fucktoyPref());
				},
			));
			return span;
		}

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

		return el;
	}
};


code:
// cspell:ignore Ooobabooga, KBCPP

/**
 * @param {FC.SlaveState} slave
 * @returns {HTMLParagraphElement}
 */
App.UI.SlaveInteract.chat = function(slave) {
	const p = document.createElement("p");
	p.id = "chat-tab";

	// Inject the chatbox inside this tab
	const chatContainer = document.createElement("div");
	chatContainer.id = "chat-container";
	chatContainer.style.width = "50%";
	chatContainer.style.height = "50%";
	chatContainer.style.background = "rgba(0,0,0,0.8)";
	chatContainer.style.color = "white";
	chatContainer.style.border = "1px solid white";
	chatContainer.style.padding = "10px";
	chatContainer.style.overflow = "hidden";
	chatContainer.style.display = "flex";
	chatContainer.style.flexDirection = "column";
	chatContainer.style.zIndex = "9999";

	// Create the chat history (scrollable)
	const chatHistory = document.createElement("div");
	chatHistory.id = "chat-history";
	chatHistory.style.flexGrow = "1";
	chatHistory.style.overflowY = "auto";
	chatHistory.style.height = "80%";
	chatHistory.style.padding = "5px";
	chatHistory.style.borderBottom = "1px solid white";

	// Create the chat input field
	const chatInput = document.createElement("input");
	chatInput.id = "chat-input";
	chatInput.type = "text";
	chatInput.style.width = "100%";
	chatInput.style.marginTop = "5px";
	chatInput.style.background = "black";
	chatInput.style.color = "white";
	chatInput.style.border = "1px solid white";
	chatInput.style.padding = "5px";

	// Append chat elements inside the tab
	chatContainer.append(chatHistory, chatInput);
	p.append(chatContainer);

	// Extract and Store Description (Directly in Chat Tab)
	let extractedSlaveDescription = "";
	let extractedPlayerDescription = "";
	let extractedFamilyDescription = "";

	function extractSlaveDescription() {
		let slaveLongDescription = App.Desc.longSlave(slave, {noArt: true, links: true});
		if (slaveLongDescription instanceof DocumentFragment) {
			let textParts = [];
			slaveLongDescription.childNodes.forEach(node => {
				if (node.nodeType === Node.TEXT_NODE) {
					textParts.push(node.textContent.trim());
				} else if (node.nodeType === Node.ELEMENT_NODE) {
					textParts.push(node.innerText || node.textContent.trim());
				}
			});
			extractedSlaveDescription = textParts.filter(text => text).join("\n");
		} else if (typeof slaveLongDescription === "string") {
			extractedSlaveDescription = slaveLongDescription.trim();
		}
	}

	function extractPlayerDescription() {
		let playerLongDescription = App.Desc.Player.longDescription(V.PC);
		if (playerLongDescription instanceof DocumentFragment) {
			let textParts = [];
			playerLongDescription.childNodes.forEach(node => {
				if (node.nodeType === Node.TEXT_NODE) {
					textParts.push(node.textContent.trim());
				} else if (node.nodeType === Node.ELEMENT_NODE) {
					textParts.push(node.innerText || node.textContent.trim());
				}
			});
			extractedPlayerDescription = textParts.filter(text => text).join("\n");
		} else if (typeof playerLongDescription === "string") {
			extractedPlayerDescription = playerLongDescription.trim();
		}
	}

	function extractFamilyDescription() {
		let familyLongDescription = App.Desc.family(V.PC, true);
		if (familyLongDescription instanceof DocumentFragment) {
			let textParts = [];
			familyLongDescription.childNodes.forEach(node => {
				if (node.nodeType === Node.TEXT_NODE) {
					textParts.push(node.textContent.trim());
				} else if (node.nodeType === Node.ELEMENT_NODE) {
					textParts.push(node.innerText || node.textContent.trim());
				}
			});
			extractedFamilyDescription = textParts.filter(text => text).join("\n");
		} else if (typeof familyLongDescription === "string") {
			extractedFamilyDescription = familyLongDescription.trim();
		}
	}

	// Call extraction function on tab load
	extractSlaveDescription();
	extractPlayerDescription();
	extractFamilyDescription();

	// Chat Function
	async function sendMessageToAI(userMessage) {
		try {
			const systemMessage = "(role:You are a system that takes user input, game variables, and uses smart sense of the context to write relevant stories to flesh out in-game actions. Immersion is key, do not mention stats or game design elements, only passages that flesh out the users' decisions and the results of their stats/rolls) (Perspective:Always write to the user in first-person, since this is a game in their perspective) (Style:Write one or two paragraphs, regardless of previous replies or user input. Be consistent in length. Sensory details, physical events, and dialogue should be the primary focus of the passages written) [RULES:You will be given details, stat or point increases, and actions the user has made. Write speech ONLY for NPCs, not the user. The user will be roleplaying themselves, do not do it for them! ALWAYS WRITE IN SECOND PERSON PERSPECTIVE ('you do x', 'she does this to you', 'as you x' etc.)]";
			const playerDescriptionContext = extractedPlayerDescription ? `\n\nPlayer Description:\n${extractedPlayerDescription}` : "";
			const familyDescriptionContext = extractedFamilyDescription ? `\n\nFamily Description:\n${extractedFamilyDescription}` : "";
			const slaveDescriptionContext = extractedSlaveDescription ? `\n\nSlave Description:\n${extractedSlaveDescription}` : "";
			const playerTitleContext = `On formal occasions, you are announced as ${PCTitle()}. By slaves, however, you prefer to be called ${properMaster()}.`;

			const response = await fetch(V.aiChatUrl, {
				method: "POST",
				headers: {
					"Content-Type": "application/json"
				},
				body: JSON.stringify({
					model: "mistral-7b",
					messages: [
						{
							role: "system",
							content: familyDescriptionContext + playerDescriptionContext + playerTitleContext + slaveDescriptionContext + systemMessage
						}, // Include extracted description
						{role: "user", content: userMessage}
					],
					max_tokens: 1024,
					temperature: V.aiChatTemp,
					min_p: V.aiChatMinP,
					repetition_penalty: 1.1
				})
			});

			const data = await response.json();
			console.log("AI Response:", data);

			if (data && data.choices && data.choices.length > 0) {
				return data.choices[0].message.content.trim();
			} else {
				return "⚠ AI response invalid! Check your self-hosted LLM backend (Ooobabooga, KBCPP) as well as your IP addresses/permissions";
			}
		} catch (error) {
			console.error("Error communicating with AI:", error);
			return "⚠ Error connecting to AI! ⚠ Check your self-hosted LLM backend (Ooobabooga, KBCPP) as well as your IP addresses/permissions";
		}
	}

	// Attach event listener inside siChat.js
	chatInput.addEventListener("keypress", async function(event) {
		if (event.key === "Enter") {
			let userMessage = chatInput.value.trim();
			if (userMessage) {
				let userMessageDiv = document.createElement("div");
				userMessageDiv.textContent = "Player: " + userMessage;
				chatHistory.append(userMessageDiv);
				chatInput.value = "";
				chatHistory.scrollTop = chatHistory.scrollHeight; // Auto-scroll

				// Send message to AI using local function
				let aiResponse = await sendMessageToAI(userMessage);

				let aiMessageDiv = document.createElement("div");
				aiMessageDiv.textContent = slave.slaveName + " " + slave.slaveSurname + ":" + aiResponse;
				aiMessageDiv.style.color = "lightblue";
				chatHistory.append(aiMessageDiv);
				chatHistory.scrollTop = chatHistory.scrollHeight; // Auto-scroll
			}
		}
	});

	return p;
};


code:
/**
 * @param {FC.HumanID} slaveID
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.mainPage = function(slaveID) {
	const el = new DocumentFragment();
	if (!slaveID) {
		throw new Error(`The last passage ("${lastPassage()}") did not set V.temp.AS properly`);
	}
	const slave = getSlave(slaveID);

	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";
		}
	}

	App.UI.StoryCaption.encyclopedia = either("Costs Summary", "Disease in the Free Cities", "Drugs and Their Effects", "From Rebellious to Devoted", "Gender", "Modern Anal", "Nymphomania", "Slave Couture");
	if (slave.dick > 0) {
		App.UI.StoryCaption.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); }
		}
	];

	if (V.aiChat === 1) {
		tabs.push({
			title: "Chat",
			id: "chat-tab",
			get node() { return App.UI.SlaveInteract.chat(slave); }
		});
	}

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

	return el;

	/** @returns {DocumentFragment} */
	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.temp.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(
			`Rein ${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:
/** @returns {DocumentFragment} */
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 + App.Entity.facilities.servantsQuarters.capacity + 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("Should slaves have permission to assign themselves to facilities when choosing their assignment", "universalRulesAssignsSelfFacility")
				.addValue("Allow", 1).on()
				.addValue(`Stop`, 0).off();
			if (V.universalRulesAssignsSelfFacility === 1 && V.farmyard !== 0) {
				options.addOption("Should slaves be allowed to assign themselves to the farm", "farmyardSlavesAssignThemselves")
					.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 getSlaves().values()) {
					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 (FutureSocieties.isActive('FSDegradationist')) {
					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);
				}
				if (V.arcologies[0].FSAntebellumRevivalist > 20) {
					option.addValue('Antebellum', 10);
				}
			}
		}
		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 = getSlave(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 if (V.universalRulesImpregnation === "Slaves") {
			r.push(`Fertile slaves will be systematically inseminated wth your chattels' collected cum; who knows who the father will be?`);
		} else if (V.universalRulesImpregnation === "Citizens") {
			r.push(`Fertile slaves will be systematically impregnated by ${V.arcologies[0].name}'s citizens.`);
		} 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 (isVirile(V.PC)) {
			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();
		}
		option.addValue(`Slaves`, "Slaves", () => { V.universalRulesSuperfetationImpregnation = 0; V.universalHGImpregnateMasterSuiteToggle = 0; });
		option.addValue(`Citizens`, "Citizens", () => { V.universalRulesSuperfetationImpregnation = 0; V.universalHGImpregnateMasterSuiteToggle = 0; });
		if (["Slaves", "Citizens"].includes(V.universalRulesImpregnation)) {
			options.addOption(`${V.universalRulesImpregnation} are permitted to impregnate your harem`, "universalHGImpregnateMasterSuiteToggle")
				.addValue("Yes", 1).on()
				.addValue("No", 0).off();
		}

		if (["HG", "PC", "Stud"].includes(V.universalRulesImpregnation)) {
			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 - (tankSlaveCount() + 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 (V.nurseryCribs - (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: auto;
  max-height: 32em;
  margin: 1em 0;
  display: flex;
  flex-direction: column-reverse;
}