code:
/**
 * @param {App.Entity.SlaveState} slave
 * @param {function():void} refresh
 * @returns {HTMLDivElement}
 */
App.UI.SlaveInteract.custom = function(slave, refresh) {
	const {
		He, His,
		he, his, him,
	} = getPronouns(slave);

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

	el.append(intro());

	App.UI.DOM.appendNewElement("h3", el, `Art`);
	el.append(
		customSlaveImage(),
		customHairImage(),
		artSeed(),
		aiPrompts(),
		customAIPose()
	);

	App.UI.DOM.appendNewElement("h3", el, `Names`);
	el.append(
		playerTitle(),
		slaveFullName(),
		pronouns()
	);

	App.UI.DOM.appendNewElement("h3", el, `Description`);
	el.append(
		hair(),
		eyeColor(),
		customTattoo(),
		customOriginStory(),
		customDescription(),
		customSlaveTitle(),
		customLabel()
	);

	return el;

	function intro() {
		const frag = new DocumentFragment();
		let p;
		p = document.createElement('p');
		p.id = "rename";
		frag.append(p);

		p = document.createElement('p');
		p.className = "scene-p";
		p.append(`You may enter custom descriptors for your slave's hair color, hair style, tattoos, or anything else here. After typing, press `);
		p.appendChild(App.UI.DOM.makeElement("kbd", "enter"));
		p.append(` to commit your change. These custom descriptors will appear in descriptions of your slave, but will have no gameplay effect. Changing them is free.`);
		frag.append(p);
		return frag;
	}

	function playerTitle() {
		let playerTitle = document.createElement('p');
		const label = document.createElement('div');
		let result;
		if (slave.devotion >= -50) {
			if (slave.custom.title !== "") {
				label.textContent = `You have instructed ${him} to always refer to you as ${slave.custom.title}, which, should ${he} lisp, comes out as ${slave.custom.titleLisp}.`;
			} else {
				label.textContent = `You expect ${him} to refer to you as all your other slaves do.`;
			}
			result = document.createElement('div');
			result.id = "result";
			result.className = "choices";

			let hiddenTextBox = document.createElement('span');
			let shownTextBox = document.createElement('span');
			if (slave.custom.title === "") {
				hiddenTextBox.appendChild(
					App.UI.DOM.link(
						`Set a custom title for ${him} to address you as`,
						() => {
							jQuery('#result').empty().append(shownTextBox);
						}
					)
				);
				result.appendChild(hiddenTextBox);
				shownTextBox.textContent = `Custom title: `;
				shownTextBox.append(
					App.UI.DOM.makeTextBox(
						"",
						v => {
							slave.custom.title = v;
							slave.custom.titleLisp = lispReplace(slave.custom.title);
							refresh();
						}
					)
				);
			} else {
				result.append(`${He}'s trying ${his} best to call you `);
				result.append(
					App.UI.DOM.makeTextBox(
						slave.custom.title,
						v => {
							slave.custom.title = v;
							slave.custom.titleLisp = lispReplace(slave.custom.title);
							refresh();
						}
					)
				);
				result.appendChild(
					App.UI.DOM.link(
						` Stop using a custom title`,
						() => {
							slave.custom.title = "";
							slave.custom.titleLisp = "";
							refresh();
						}
					)
				);
			}
			label.appendChild(result);
		} else {
			label.textContent = `You must break ${his} will further before ${he} will refer to you by a new title. `;
			if (SlaveStatsChecker.checkForLisp(slave)) {
				if (slave.custom.titleLisp && slave.custom.titleLisp !== "") {
					label.textContent += `For now, ${he} intends to keep calling you "${slave.custom.titleLisp}."`;
				}
			} else {
				if (slave.custom.title && slave.custom.title !== "") {
					label.textContent += `For now, ${he} intends to keep calling you "${slave.custom.title}."`;
				}
			}
		}
		playerTitle.appendChild(label);
		return playerTitle;
	}

	function slaveFullName() {
		let slaveFullNameNode = document.createElement('span');
		if (
			((slave.devotion >= -50 || slave.trust < -20) && (slave.birthName !== slave.slaveName)) ||
			(slave.devotion > 20 || slave.trust < -20)
		) {
			slaveFullNameNode.appendChild(slaveName());
			slaveFullNameNode.appendChild(slaveSurname());
		} else {
			slaveFullNameNode.textContent = `You must break ${his} will further before you can successfully force a new name on ${him}.`;
			slaveFullNameNode.className = "note";
		}

		return slaveFullNameNode;

		function slaveName() {
			const oldName = slave.slaveName;
			const oldSurname = slave.slaveSurname;
			// Slave Name
			let slaveNameNode = document.createElement('p');
			let label = document.createElement('div');
			let result = document.createElement('div');
			result.id = "result";
			result.className = "choices";
			const linkArray = [];

			label.append(`Change ${his} given name`);
			if (slave.birthName !== slave.slaveName) {
				if (slave.birthName === "") {
					label.append(` (${his} birth name is not known)`);
				} else {
					label.append(` (${his} birth name was ${slave.birthName})`);
				}
			}
			label.append(`: `);

			label.append(
				App.UI.DOM.makeTextBox(
					slave.slaveName,
					v => {
						slave.slaveName = v;
						updateName(slave, {oldName: oldName, oldSurname: oldSurname});
					},
					false,
				)
			);

			slaveNameNode.appendChild(label);

			if (slave.slaveName === slave.birthName) {
				linkArray.push(App.UI.DOM.disabledLink(`${He} has ${his} birth name`, [`Nothing to reset`]));
			} else if (slave.birthName === "") {
				linkArray.push(App.UI.DOM.disabledLink(`${He} has no birth name to restore`, [`Nothing to reset`]));
			} else {
				linkArray.push(App.UI.DOM.link(
					`Restore ${his} birth name`,
					() => {
						slave.slaveName = slave.birthName;
						updateName(slave, {oldName: oldName, oldSurname: oldSurname});
					}
				));
			}

			if (FutureSocieties.isActive('FSPastoralist')) {
				if (slave.lactation > 0) {
					linkArray.push(chooseThreeNames(`Choose a random cow name for ${him}`, App.Data.misc.cowSlaveNames));
				}
			}
			if (FutureSocieties.isActive('FSIntellectualDependency')) {
				if (slave.intelligence + slave.intelligenceImplant < -10) {
					linkArray.push(chooseThreeNames(`Give ${him} a random stripper given name`, App.Data.misc.bimboSlaveNames));
				}
			}
			if (FutureSocieties.isActive('FSChattelReligionist')) {
				linkArray.push(chooseThreeNames(`Give ${him} a random devotional given name`, App.Data.misc.chattelReligionistSlaveNames));
			}
			if (slave.race === "catgirl") {
				linkArray.push(chooseThreeNames(`Give ${him} a random cat name`, App.Data.misc.catSlaveNames));
			}
			result.append(App.UI.DOM.generateLinksStrip(linkArray));
			slaveNameNode.appendChild(result);
			return slaveNameNode;

			/**
			 * @param {string} title
			 * @param {string[]} array
			 */
			function chooseThreeNames(title, array) {
				const el = document.createElement("span");
				el.append(
					App.UI.DOM.link(
						title,
						() => {
							linkGuts();
						}
					)
				);
				return el;

				function linkGuts() {
					// Pick three random names, not including the slave's current name
					const names = array.filter(n => n !== slave.slaveName).pluckMany(3);

					// return the three names as links
					const nameLinks = names.map(n =>
						App.UI.DOM.link(n, () => {
							slave.slaveName = n;
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						})
					);
					nameLinks.push(
						App.UI.DOM.link("...", () => {
							linkGuts();
						})
					);
					jQuery(el).empty().append(App.UI.DOM.generateLinksStrip(nameLinks));
				}
			}
		}

		function slaveSurname() {
			// Slave Surname
			const oldName = slave.slaveName;
			const oldSurname = slave.slaveSurname;
			const linkArray = [];
			let slaveSurnameNode = document.createElement('p');
			let label = document.createElement('div');
			let result = document.createElement('div');
			result.id = "result";
			result.className = "choices";

			label.append(`Change ${his} surname`);
			if (slave.birthSurname !== slave.slaveSurname) {
				if (slave.birthSurname === "") {
					label.append(` (${his} birth surname is not known)`);
				} else {
					label.append(` (${his} birth surname was ${slave.birthSurname})`);
				}
			}
			label.append(`: `);

			label.append(
				App.UI.DOM.makeTextBox(
					slave.slaveSurname,
					v => {
						slave.slaveSurname = v;
						updateName(slave, {oldName: oldName, oldSurname: oldSurname});
					},
					false,
				)
			);

			slaveSurnameNode.appendChild(label);

			if (slave.slaveSurname === slave.birthSurname) {
				linkArray.push(
					App.UI.DOM.disabledLink(
						`Restore ${his} birth surname`,
						[`${He} has ${his} birth surname`]
					)
				);
			} else if (slave.birthSurname === "") {
				linkArray.push(
					App.UI.DOM.disabledLink(
						`Restore ${his} birth surname`,
						[`${He} has no birth surname to restore`]
					)
				);
			} else {
				linkArray.push(App.UI.DOM.link(
					`Restore ${his} birth surname`,
					() => {
						slave.slaveSurname = slave.birthSurname;
						updateName(slave, {oldName: oldName, oldSurname: oldSurname});
					}
				));
			}
			if (getSlave(slave.mother) || getSlave(slave.father) || slave.mother === -1 || slave.father === -1) {
				linkArray.push(App.UI.DOM.link(
					`Regenerate ${his} surname based on current Universal Rules`,
					() => {
						regenerateSurname(slave);
						updateName(slave, {oldName: oldName, oldSurname: oldSurname});
					}
				));
			}
			if (V.surnameArcology) {
				linkArray.push(App.UI.DOM.link(
					`Give ${him} the arcology surname ${V.surnameArcology}`,
					() => {
						slave.slaveSurname = V.surnameArcology;
						updateName(slave, {oldName: oldName, oldSurname: oldSurname});
					}
				));
			}
			if (slave.slaveSurname) {
				linkArray.push(App.UI.DOM.link(
					`Take ${his} surname away`,
					() => {
						slave.slaveSurname = 0;
						updateName(slave, {oldName: oldName, oldSurname: oldSurname});
					}
				));
			}
			if (slave.relationship >= 5) {
				const spouse = getSlave(slave.relationshipTarget);
				if (spouse.slaveSurname && slave.slaveSurname !== spouse.slaveSurname) {
					const wifePronouns = getPronouns(spouse);
					linkArray.push(
						App.UI.DOM.link(
							`Give ${him} ${his} ${wifePronouns.wife}'s surname`,
							() => {
								slave.slaveSurname = spouse.slaveSurname;
								updateName(slave, {oldName: oldName, oldSurname: oldSurname});
							}
						)
					);
				}
			}
			if (slave.relationship === -3) {
				if (V.PC.slaveSurname && slave.slaveSurname !== V.PC.slaveSurname) {
					linkArray.push(
						App.UI.DOM.link(
							`Give ${him} your surname`,
							() => {
								slave.slaveSurname = V.PC.slaveSurname;
								updateName(slave, {oldName: oldName, oldSurname: oldSurname});
							}
						)
					);
				}
			}
			if (FutureSocieties.isActive('FSRomanRevivalist')) {
				linkArray.push(
					App.UI.DOM.link(
						`Give ${him} a random full Roman name`,
						() => {
							slave.slaveName = App.Data.misc.romanSlaveNames.random();
							slave.slaveSurname = App.Data.misc.romanSlaveSurnames.random();
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						}
					)
				);
			} else if (FutureSocieties.isActive('FSAztecRevivalist')) {
				linkArray.push(
					App.UI.DOM.link(
						`Give ${him} a random full Aztec name`,
						() => {
							slave.slaveName = App.Data.misc.aztecSlaveNames.random();
							slave.slaveSurname = 0;
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						}
					)
				);
			} else if (FutureSocieties.isActive('FSEgyptianRevivalist')) {
				linkArray.push(
					App.UI.DOM.link(
						`Give ${him} a random full ancient Egyptian name`,
						() => {
							slave.slaveName = App.Data.misc.ancientEgyptianSlaveNames.random();
							slave.slaveSurname = 0;
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						}
					)
				);
			} else if (FutureSocieties.isActive('FSEdoRevivalist')) {
				linkArray.push(
					App.UI.DOM.link(
						`Give ${him} a random full feudal Japanese name`,
						() => {
							slave.slaveName = App.Data.misc.edoSlaveNames.random();
							slave.slaveSurname = App.Data.misc.edoSlaveSurnames.random();
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						}
					)
				);
			} else if (FutureSocieties.isActive('FSChineseRevivalist')) {
				linkArray.push(
					App.UI.DOM.link(
						`Give ${him} a random full Ancient Chinese name`,
						() => {
							slave.slaveName = App.Data.misc.chineseSlaveNames.random();
							slave.slaveSurname = App.Data.misc.chineseRevivalistSlaveSurnames.random();
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						}
					)
				);
			} else if (FutureSocieties.isActive('FSAntebellumRevivalist')) {
				linkArray.push(
					App.UI.DOM.link(
						`Give ${him} a random full old Southern name`,
						() => {
							slave.slaveName = App.Data.misc.antebellumSlaveNames.random();
							slave.slaveSurname = App.Data.misc.antebellumSlaveSurnames.random();
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						}
					)
				);
			}
			if (FutureSocieties.isActive('FSDegradationist')) {
				linkArray.push(
					App.UI.DOM.link(
						`Give ${him} a degrading full name`,
						() => {
							DegradingName(slave);
							updateName(slave, {oldName: oldName, oldSurname: oldSurname});
						}
					)
				);
			}
			result.append(App.UI.DOM.generateLinksStrip(linkArray));
			slaveSurnameNode.appendChild(result);
			return slaveSurnameNode;
		}

		function updateName(slave, {oldName: oldName, oldSurname: oldSurname}) {
			let genepoolRec = V.genePool.find(s => s.ID === slave.ID);
			genepoolRec.slaveName = slave.slaveName;
			genepoolRec.slaveSurname = slave.slaveSurname;
			App.UI.SlaveInteract.rename(slave, {oldName: oldName, oldSurname: oldSurname});
			refresh();
		}
	}

	function pronouns() {
		const pnNode = new DocumentFragment();

		const grp = new App.UI.OptionsGroup(); // note that OptionsGroup's default refresh will reload the entire page, which is desirable in this case
		grp.addOption("Pronouns to use:", "pronoun", slave)
			.addValue("Feminine", App.Data.Pronouns.Kind.female)
			.addValue("Masculine", App.Data.Pronouns.Kind.male)
			.addValue("Neuter/Toy", App.Data.Pronouns.Kind.neutral)
			// .addValue("Epicene/Plural", App.Data.Pronouns.Kind.epicene) - TODO: epicene pronouns have verb tense problems ("they is...")
			// .addValue("Custom", App.Data.Pronouns.Kind.custom) - TODO: custom pronoun mechanism is incomplete/broken right now
			.addComment(`${slave.slaveName} will be addressed as "${he}/${him}."`);

		pnNode.append(grp.render());

		return pnNode;
	}

	function hair() {
		let hairNode = new DocumentFragment();
		hairNode.appendChild(hairStyle());
		hairNode.appendChild(hairColor());
		return hairNode;

		function hairStyle() {
			let hairStyleNode = document.createElement('p');
			let label = document.createElement('div');
			label.append(`Custom hair description: `);

			label.append(
				App.UI.DOM.makeTextBox(
					slave.hStyle,
					v => {
						slave.hStyle = v;
						refresh();
					}
				)
			);

			switch (slave.hStyle) {
				case "tails":
				case "dreadlocks":
				case "drills":
				case "cornrows":
				case "bangs":
					label.append(` "${His} hair is in ${slave.hStyle}."`);
					break;
				case "ponytail":
					label.append(` "${His} hair is in a ${slave.hStyle}."`);
					break;
				case "hime":
					label.append(` "${His} hair is in a ${slave.hStyle} cut."`);
					break;
				default:
					label.append(` "${His} hair is ${slave.hStyle}."`);
					break;
			}
			hairStyleNode.appendChild(label);

			let choices = document.createElement('div');
			choices.className = "choices";
			choices.appendChild(App.UI.DOM.makeElement('span', ` For best results, use a short, uncapitalized and unpunctuated description; for example: 'back in a ponytail'`, 'note'));
			hairStyleNode.appendChild(choices);
			return hairStyleNode;
		}

		function hairColor() {
			let hairStyleNode = document.createElement('p');
			let label = document.createElement('div');
			label.append(`Custom hair color: `);
			label.append(
				App.UI.DOM.makeTextBox(
					slave.hColor,
					v => {
						slave.hColor = v;
						refresh();
					}
				)
			);
			label.append(` "${His} hair is ${slave.hColor}."`);
			hairStyleNode.appendChild(label);

			let choices = document.createElement('div');
			choices.className = "choices";
			choices.appendChild(App.UI.DOM.makeElement('span', ` For best results, use a short, uncapitalized and unpunctuated description; for example: 'black with purple highlights'`, 'note'));
			hairStyleNode.appendChild(choices);
			return hairStyleNode;
		}
	}

	function eyeColor() {
		let eyeColorNode = document.createElement('p');
		let label = document.createElement('div');
		if (getLenseCount(slave) > 0) {
			label.textContent = `${He} is wearing ${App.Desc.eyesColor(slave, "", "lense", "lenses")}.`;
		} else {
			label.textContent = `${He} has ${App.Desc.eyesColor(slave)}.`;
		}
		eyeColorNode.appendChild(label);

		let choices = document.createElement('div');
		choices.className = "choices";

		let eye;

		if (hasLeftEye(slave)) {
			eye = document.createElement('div');
			eye.append(`Custom left eye color: `);
			eye.append(
				App.UI.DOM.makeTextBox(
					slave.eye.left.iris,
					v => {
						slave.eye.left.iris = v;
						refresh();
					}
				)
			);
			choices.appendChild(eye);
		}
		if (hasRightEye(slave)) {
			eye = document.createElement('div');
			eye.append(`Custom right eye color: `);
			eye.append(
				App.UI.DOM.makeTextBox(
					slave.eye.right.iris,
					v => {
						slave.eye.right.iris = v;
						refresh();
					}
				)
			);
			choices.appendChild(eye);
		}
		choices.appendChild(App.UI.DOM.makeElement('span', `For best results, use a short, uncapitalized and unpunctuated description; for example: 'blue'`, 'note'));
		eyeColorNode.appendChild(choices);
		return eyeColorNode;
	}

	function customTattoo() {
		let el = document.createElement('p');
		el.append(`Change ${his} custom tattoo: `);
		el.appendChild(App.UI.DOM.makeTextBox(
			slave.custom.tattoo,
			v => {
				slave.custom.tattoo = v;
				refresh();
			}));

		let choices = document.createElement('div');
		choices.className = "choices";
		choices.appendChild(App.UI.DOM.makeElement('span', `For best results, use complete sentences; for example: '${He} has blue stars tattooed along ${his} cheekbones.'`, 'note'));
		el.appendChild(choices);

		return el;
	}

	function customOriginStory() {
		let el = document.createElement('p');
		el.append(`Change ${his} origin story: `);
		el.appendChild(App.UI.DOM.makeTextBox(
			slave.origin,
			v => {
				slave.origin = v;
				refresh();
			}));

		let choices = document.createElement('div');
		choices.className = "choices";
		choices.appendChild(App.UI.DOM.makeElement('span', ` For best results, use complete, capitalized and punctuated sentences; for example: '${He} followed you home from the pet store.'`, 'note'));
		el.appendChild(choices);

		return el;
	}

	function customDescription() {
		let el = document.createElement('p');
		el.append(`Change ${his} custom description: `);
		el.appendChild(
			App.UI.DOM.makeTextBox(
				pronounsForSlaveProp(slave, slave.custom.desc),
				v => {
					slave.custom.desc = v;
					refresh();
				}
			)
		);

		let choices = document.createElement('div');
		choices.className = "choices";
		choices.appendChild(App.UI.DOM.makeElement('span', ` For best results, use complete, capitalized and punctuated sentences; for example: '${He} has a beauty mark above ${his} left nipple.'`, 'note'));
		el.appendChild(choices);

		return el;
	}

	function customSlaveTitle() {
		let el = document.createElement('p');
		el.append(`Change ${his} custom slave title: `);
		el.appendChild(
			App.UI.DOM.makeTextBox(slave.custom.name,
				v => {
					slave.custom.name = v;
					refresh();
				}
			)
		);

		let choices = document.createElement('div');
		choices.className = "choices";
		choices.appendChild(App.UI.DOM.makeElement('span', ` For best results, should fit into a sentence like: '${SlaveFullName(slave)} is a "slave title"'`, 'note'));
		el.appendChild(choices);

		return el;
	}

	function customLabel() {
		let el = document.createElement('p');
		el.append(`Change ${his} custom label: `);
		el.appendChild(App.UI.DOM.makeTextBox(
			slave.custom.label,
			v => {
				slave.custom.label = v;
				refresh();
			}));

		let choices = document.createElement('div');
		choices.className = "choices";
		choices.appendChild(App.UI.DOM.makeElement('span', ` For best results, use a short phrase; for example: 'Breeder.'`, 'note'));
		el.appendChild(choices);

		return el;
	}

	function artSeed() {
		const frag = new DocumentFragment();
		if (V.imageChoice === 4 || V.imageChoice === 6) { // webGL and AI art only right now
			App.UI.DOM.appendNewElement("p", frag, `Some rendering methods use a "seed value" to make small changes to the appearance of your slaves. If you're dissatisfied with this slave's appearance and correcting ${his} physical parameters doesn't seem to help, you can try replacing the seed value. Slaves with identical seeds will look identical; the game carefully preserves this value for clones and identical twins, but if you change it here it becomes your responsibility.`);

			const setArtSeed = (/** @type {number} */ num) => {
				slave.natural.artSeed = num;
				refresh();
				App.Events.refreshEventArt(slave);
			};
			const button = App.UI.DOM.makeElement("button", "Randomize");
			button.onclick = () => setArtSeed(jsRandom(0, 10 ** 14));
			const textbox = App.UI.DOM.makeTextBox(slave.natural.artSeed, (num) => setArtSeed(num), true);
			textbox.style.maxWidth = "12em";
			textbox.style.minWidth = "12em";
			App.UI.DOM.appendNewElement("p", frag, App.UI.DOM.combineNodes(
				"Art seed: ",
				textbox,
				button
			));
		}
		return frag;
	}

	function aiPrompts() {
		function posePrompt() {
			let el = document.createElement('p');
			el.append(`Override ${his} pose prompt: `);
			el.appendChild(
				App.UI.DOM.makeTextBox(
					slave.custom.aiPrompts.pose,
					v => {
						slave.custom.aiPrompts.pose = v;
						$(promptDiv).empty().append(genAIPrompt());
					}
				)
			);

			let choices = document.createElement('div');
			choices.className = "choices";
			choices.appendChild(App.UI.DOM.makeElement('span', ` This prompt will replace the default body pose prompts. Example: 'kneeling, arms behind back'. If you are using OpenPose, make sure your selected pose and pose prompt agree.`, 'note'));
			el.appendChild(choices);
			return el;
		}

		function positivePrompt() {
			let el = document.createElement('p');
			el.append(`Add positive prompts: `);
			el.appendChild(
				App.UI.DOM.makeTextBox(
					slave.custom.aiPrompts.positive,
					v => {
						slave.custom.aiPrompts.positive = v;
						$(promptDiv).empty().append(genAIPrompt());
					}
				)
			);

			let choices = document.createElement('div');
			choices.className = "choices";
			choices.appendChild(App.UI.DOM.makeElement('span', ` Prompts specified here will be appended to the end of the dynamic positive prompt; specify things you want to see in the rendered image.`, 'note'));
			el.appendChild(choices);
			return el;
		}

		function negativePrompt() {
			let el = document.createElement('p');
			el.append(`Add negative prompts: `);
			el.appendChild(
				App.UI.DOM.makeTextBox(
					slave.custom.aiPrompts.negative,
					v => {
						slave.custom.aiPrompts.negative = v;
						$(promptDiv).empty().append(genAIPrompt());
					}
				)
			);

			let choices = document.createElement('div');
			choices.className = "choices";
			choices.appendChild(App.UI.DOM.makeElement('span', ` Prompts specified here will be appended to the end of the dynamic negative prompt; specify things you don't want to see in the rendered image.`, 'note'));
			el.appendChild(choices);
			return el;
		}

		const frag = new DocumentFragment();

		// Debug information for AI art, or prompt suggestions for custom images
		const promptDiv = App.UI.DOM.makeElement('div');
		if ((V.imageChoice === 6 && (V.debugMode === 1 || slave.custom.aiPrompts)) || (V.seeCustomImagesOnly && V.aiCustomImagePrompts)) {
			promptDiv.append(genAIPrompt());
		} else if (V.imageChoice === 6) {
			promptDiv.append(App.UI.DOM.link("Show AI Prompts", f => {
				$(promptDiv).empty().append(genAIPrompt());
			}));
		}
		frag.append(promptDiv);

		// Custom prompt parts
		const customDiv = App.UI.DOM.makeElement('div');
		if (V.imageChoice === 6 || (V.seeCustomImagesOnly && V.aiCustomImagePrompts)) {
			if (slave.custom.aiPrompts) {
				customDiv.append(
					posePrompt(),
					positivePrompt(),
					negativePrompt(),
				);
				customDiv.append(App.UI.DOM.link("Disable Prompt Customization", f => {
					delete slave.custom.aiPrompts;
					refresh();
				}));
			} else {
				customDiv.append(App.UI.DOM.link("Customize AI Prompts", f => {
					slave.custom.aiPrompts = new App.Entity.SlaveCustomAIPrompts();
					refresh();
				}));
			}
		}
		frag.append(customDiv);
		return frag;
	}

	function customSlaveImage() {
		let el = document.createElement('p');
		el.append(`Assign ${him} a custom image: `);

		const textbox = document.createElement("input");
		textbox.value = slave.custom.image === null ? "" : slave.custom.image.filename;
		el.appendChild(textbox);

		let kbd = document.createElement('kbd');
		const select = document.createElement('select');
		select.style.border = "none";

		[
			"png",
			"jpg",
			"gif",
			"webm",
			"webp",
			"mp4"
		].forEach((fileType) => {
			const el = document.createElement('option');
			el.value = fileType;
			el.text = fileType.toUpperCase();
			select.add(el);
		});
		select.value = slave.custom.image ? slave.custom.image.format : "png";

		kbd.append(`.`);
		kbd.appendChild(select);
		el.appendChild(kbd);

		el.appendChild(
			App.UI.DOM.link(
				` Reset`,
				() => {
					slave.custom.image = null;
					refresh();
					App.Events.refreshEventArt(slave);
				},
			)
		);

		let choices = document.createElement('div');
		choices.className = "choices";
		let note = document.createElement('span');
		note.className = "note";
		note.append(`Place file in the `);
		note.appendChild(App.UI.DOM.makeElement('kbd', 'resources'));
		note.append(` folder. Choose the extension from the menu first, then enter the filename in the space and press enter. For example, for a file with the path `);
		note.appendChild(App.UI.DOM.makeElement('kbd', `\\bin\\resources\\headgirl.png`));
		note.append(`, choose `);
		note.appendChild(App.UI.DOM.makeElement('kbd', 'png'));
		note.append(` then enter `);
		note.appendChild(App.UI.DOM.makeElement('kbd', 'headgirl'));
		note.append(`.`);

		choices.appendChild(note);
		el.appendChild(choices);

		textbox.onchange = () => {
			const c = slave.custom;
			if (textbox.value.length === 0) {
				c.image = null;
			} else {
				if (c.image === null) {
					c.image = {
						filename: textbox.value,
						format: /** @type {FC.ImageFormat} */ (select.value)
					};
				} else {
					c.image.filename = textbox.value;
				}
				App.Events.refreshEventArt(slave);
			}
		};
		select.onchange = () => {
			if (slave.custom.image !== null) {
				slave.custom.image.format = /** @type {FC.ImageFormat} */ (select.value);
			}
		};

		return el;
	}

	function customHairImage() {
		let el = document.createElement('p');
		if (V.seeImages === 1 && V.imageChoice === 1) {
			if (!slave.custom.hairVector) {
				slave.custom.hairVector = 0;
			}
			el.append(`Assign ${him} a custom hair SVG image: `);

			el.appendChild(App.UI.DOM.makeTextBox(
				slave.custom.hairVector,
				v => {
					slave.custom.hairVector = v;
					refresh();
				}));

			el.appendChild(
				App.UI.DOM.link(
					` Reset`,
					() => {
						slave.custom.hairVector = 0;
						refresh();
						App.Events.refreshEventArt(slave);
					},
				)
			);
		}

		return el;
	}

	function genAIPrompt() {
		let el = document.createElement('p');

		el.appendChild(document.createElement('h4')).textContent = `Image generation AI (eg. Stable Diffusion):`;

		let prompt = buildPrompt(slave);
		el.appendChild(document.createElement('h5')).textContent = `Positive prompt`;
		el.appendChild(document.createElement('kbd')).textContent = prompt.positive();
		el.appendChild(document.createElement('h5')).textContent = `Negative prompt`;
		el.appendChild(document.createElement('kbd')).textContent = prompt.negative();

		return el;
	}

	function customAIPose() {
		if (V.imageChoice !== 6 || !V.aiOpenPose) {
			return new DocumentFragment();
		}

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

		let el = document.createElement('p');
		el.append(`Assign ${him} a custom pose using OpenPose: `);

		const select = document.createElement('select');
		[
			"PNG",
			"JSON",
			"Library",
		].forEach((type) => {
			const el = document.createElement('option');
			el.value = type;
			el.text = type;
			select.add(el);
		});
		select.value = slave.custom.aiPose?.type || "Library";
		el.appendChild(select);

		if (["PNG", "JSON"].includes(slave.custom.aiPose?.type)) {
			const textbox = document.createElement("input");
			textbox.value = slave.custom.aiPose?.filename;
			el.appendChild(textbox);

			let choices = document.createElement('div');
			choices.className = "choices";
			let note = document.createElement('span');
			note.className = "note";
			note.append(`Place OpenPose file in the `);
			note.appendChild(App.UI.DOM.makeElement('kbd', 'resources\\poses'));
			note.append(` folder. Enter the filename without extension in the space provided and press enter. For example, for a file with the path `);
			note.appendChild(App.UI.DOM.makeElement('kbd', `\\bin\\resources\\poses\\standing_devoted.png`));
			note.append(`, choose `);
			note.appendChild(App.UI.DOM.makeElement('kbd', 'PNG'));
			note.append(` then enter `);
			note.appendChild(App.UI.DOM.makeElement('kbd', 'standing_devoted'));
			note.append(`.`);

			choices.appendChild(note);
			el.appendChild(choices);

			let error = document.createElement('div');
			error.className = "error";
			el.append(error);

			textbox.onchange = () => {
				const c = slave.custom;
				if (textbox.value.length === 0) {
					c.aiPose = null;
				} else {
					fetch(`resources/poses/${textbox.value}.${c.aiPose.type.toLowerCase()}`)
						.then(r => {
							error.textContent = "";
						})
						.catch(r => {
							error.textContent = "Unable to fetch the requested resource. Your browser may prohibit local file access, or you may have mistyped the filename.";
						});
					if (!c.aiPose) {
						c.aiPose = new App.Entity.SlaveCustomAIPose();
						c.aiPose.type = /** @type {"PNG"|"JSON"|"Library"} */ (select.value);
						c.aiPose.filename = textbox.value;
					} else {
						c.aiPose.filename = textbox.value;
					}
					App.Events.refreshEventArt(slave);
				}
			};
		} else {
			const poseSel = document.createElement('select');
			const def = document.createElement('option');
			def.value = "";
			def.text = "(Default)";
			poseSel.add(def);
			Object.keys(App.Data.Art.Poses).forEach((pose) => {
				const el = document.createElement('option');
				el.value = pose;
				el.text = pose;
				poseSel.add(el);
			});
			poseSel.value = slave.custom.aiPose?.name || "";
			el.appendChild(poseSel);

			poseSel.onchange = () => {
				const c = slave.custom;
				if (poseSel.value.length === 0) {
					c.aiPose = null;
				} else {
					if (!c.aiPose) {
						c.aiPose = new App.Entity.SlaveCustomAIPose();
						c.aiPose.type = /** @type {"PNG"|"JSON"|"Library"} */ (select.value);
						c.aiPose.name = poseSel.value;
					} else {
						c.aiPose.name = poseSel.value;
					}
					App.Events.refreshEventArt(slave);
				}
			};
		}
		select.onchange = () => {
			if (select.value !== "Library" && !slave.custom.aiPose) {
				slave.custom.aiPose = new App.Entity.SlaveCustomAIPose();
			}
			if (slave.custom.aiPose) {
				slave.custom.aiPose.type = /** @type {"PNG"|"JSON"|"Library"} */ (select.value);
			}
			refresh();
		};

		el.appendChild(
			App.UI.DOM.link(
				` Reset`,
				() => {
					slave.custom.aiPose = null;
					refresh();
					App.Events.refreshEventArt(slave);
				},
			)
		);

		container.append(el);
		return container;
	}
};

repo_name: Arkerthan/fc

license: mit


code:
App.UI.SlaveInteract.description = function(slave) {
	const el = new DocumentFragment();
	const descriptionLink = document.createElement("div");
	descriptionLink.style.fontStyle = "italic";
	descriptionLink.id = "description-link";

	if (V.seeDetails === 1) {
		const descriptionOptions = document.createElement("div");

		descriptionOptions.id = "description-options";
		el.append(descriptionOptions);

		el.append(App.Desc.longSlave(slave, {noArt: true, links: true}));

		descriptionLink.append(showOptions());
		el.append(descriptionLink);
	} else {
		descriptionLink.append(
			App.UI.DOM.link(
				"Show descriptions",
				() => {
					V.seeDetails = 1;
				},
				[],
				"Slave Interact"
			)
		);
		el.append(descriptionLink);
	}

	el.append(App.Reminders.slaveDisplay(slave.ID));

	return el;

	function showOptions() {
		return App.UI.DOM.link(
			"Description Options",
			() => {
				jQuery("#description-link").empty().append(hideOptions());
				jQuery("#description-options").empty().append(App.UI.descriptionOptions());
			}
		);
	}

	function hideOptions() {
		return App.UI.DOM.link(
			"Description Options",
			() => {
				jQuery("#description-link").empty().append(showOptions());
				jQuery("#description-options").empty().append();
			}
		);
	}
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @returns {HTMLParagraphElement}
 */
App.UI.SlaveInteract.family = function(slave) {
	const p = document.createElement("p");
	p.id = "family";
	p.append(renderFamilyTree(V.slaves, slave.ID));

	return p;
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.modify = function(slave) {
	const {he, his} = getPronouns(slave);
	let el = new DocumentFragment();

	const isAgent = [Job.AGENT, Job.AGENTPARTNER].includes(slave.assignment);

	App.UI.DOM.appendNewElement('p', el, isAgent ? "Recall your agent to modify them." : "Take slave to another room.", ["scene-intro"]);

	if (isAgent) {
		return el;
	}

	el.append(rooms());

	return el;

	/**
	 * Makes a paragraph of link to send slaves to specific rooms
	 * @returns {HTMLParagraphElement}
	 */
	function rooms() {
		const p = document.createElement("p");

		makeRoomLink(p, "Auto salon", "Salon", ' Modify hair (color, length, style), nails, and even skin color.');

		makeRoomLink(p, "Body mod studio", "Body Modification", ' Mark your slave with piercings, tattoos, brands or even scars.');

		makeRoomLink(p, "Remote surgery", "Remote Surgery", ` Surgically modify your slave with state of the art plastic surgery and more. Alter ${his} senses, skeletal structure, organs, and even more.`);

		// Prosthetics
		if (V.prostheticsUpgrade > 0) {
			makeRoomLink(p, "Configure cybernetics", "Prosthetics Configuration", ` Configure prosthetics, if ${he} has been surgically implanted with interfaces that support it.`,
				() => {
					V.prostheticsConfig = "main";
				}
			);
		}

		// Analyze Pregnancy
		if (V.pregnancyMonitoringUpgrade > 0) {
			makeRoomLink(p, "Internal scan", "Analyze Pregnancy", ` Full scan of abdomen and reproductive organs.`);
		}

		return p;
	}

	/**
	 * Create a link with a note to send a slave to a specific room
	 * @param {Node} c
	 * @param {string} caption
	 * @param {string} passage
	 * @param {string} note
	 * @param {function ():void} [handler]
	 */
	function makeRoomLink(c, caption, passage, note, handler = () => {}) {
		const res = document.createElement('div');
		c.appendChild(res);
		res.appendChild(App.UI.DOM.link(caption, handler, [], passage));
		App.UI.DOM.appendNewElement('span', res, note, "note");
		return res;
	}
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.navigation = function(slave) {
	const f = new DocumentFragment();

	if (V.cheatMode) {
		App.UI.DOM.appendNewElement("div", f,
			App.UI.DOM.passageLink("Cheat Edit Slave", "Cheat Edit JS", () => {
				V.cheater = 1;
				delete V.tempSlave;
			}),
			"cheat-menu"
		);
	}

	const p = document.createElement("p");
	p.classList.add("center");

	const placeInLine = App.UI.SlaveInteract.placeInLine(slave);
	const div = App.UI.DOM.appendNewElement("div", p, null);
	const previous = App.UI.DOM.makeElement("span", null, ['margin-right']);
	const next = App.UI.DOM.makeElement("span", null, ['margin-left']);
	const name = App.UI.DOM.makeElement("span", slave.slaveName, ['slave-name', "si-header"]);

	previous.id = "prev-slave";
	next.id = "next-slave";

	previous.append(
		App.UI.DOM.makeElement("span", App.UI.Hotkeys.hotkeys("prev-slave"), ['hotkey']),
		" ",
		App.UI.DOM.makeElement("span", App.UI.DOM.passageLink("Prev", "Slave Interact", () => {
			V.AS = placeInLine[0];
		}), ["adjacent-slave"]),
	);
	next.append(
		App.UI.DOM.makeElement("span", App.UI.DOM.passageLink("Next", "Slave Interact", () => {
			V.AS = placeInLine[1];
		}), ["adjacent-slave"]),
		" ",
		App.UI.DOM.makeElement("span", App.UI.Hotkeys.hotkeys("next-slave"), ['hotkey']),
	);

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

		frag.append(
			previous,
			name,
			' ',
			App.UI.DOM.makeElement("span", App.UI.favoriteToggle(slave, () => {
				App.UI.DOM.replace(div, content());
			}), ['si-header']),
			next,
		);

		return frag;
	}

	div.append(content());

	f.append(p);

	return f;
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @param {function():void} refresh
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
	const {
		he,
		him,
		his,
		himself,
		He,
		His
	} = getPronouns(slave);

	const el = new DocumentFragment();

	el.append(
		drugs(),
		curatives(),
		aphrodisiacs(),
		fertility(),
		incubator(),
		nursery(),
		breederEligibility(),
		bloating(),
		hormones(),
		diet(),
		dietBase(),
		snacks()
	);

	return el;

	function drugs() {
		let el = document.createElement('div');

		const drugLevelOptions = [];
		const lips = [];
		const breasts = [];
		const nipples = [];
		const butt = [];
		const dick = [];
		const balls = [];
		const fertility = [];
		const hormones = [];
		const psych = [];
		const misc = [];

		if (slave.drugs !== "no drugs") {
			drugLevelOptions.push({text: `None`, updateSlave: {drugs: `no drugs`}});
		}
		if (slave.indentureRestrictions < 2) {
			// Psych
			if (slave.intelligence > -100 && slave.indentureRestrictions < 1) {
				psych.push({text: `Psychosuppressants`, updateSlave: {drugs: `psychosuppressants`}});
			} else if (slave.intelligence > -100) {
				psych.push({text: `Psychosuppressants`, disabled: `Cannot suppress indentured slave`});
			} else if (slave.indentureRestrictions < 1) {
				psych.push({text: `Psychosuppressants`, disabled: `Too stupid to suppress`});
			} else {
				psych.push({text: `Psychosuppressants`, disabled: `Too stupid and indentured to suppress`});
			}
			if (V.arcologies[0].FSSlaveProfessionalismResearch === 1) {
				if (canImproveIntelligence(slave)) {
					psych.push({text: `Psychostimulants`, updateSlave: {drugs: `psychostimulants`}});
				} else {
					psych.push({text: `Psychostimulants`, disabled: `Cannot improve intelligence`});
				}
			}

			// Breasts
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (App.Medicine.fleshSize(slave, 'boobs') > 100) {
					breasts.push({text: `Reducers`, updateSlave: {drugs: `breast redistributors`}});
				} else {
					breasts.push({text: `Reducers`, disabled: `Boobs are too small`});
				}
			}
			if (slave.boobs < 50000) {
				breasts.push({text: `Enhancement`, updateSlave: {drugs: `breast injections`}});
				breasts.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive breast injections`}});
			} else {
				breasts.push({text: `Enhancement`, disabled: `Boobs are too large`});
			}
			if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
				if (slave.boobs < 50000) {
					breasts.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper breast injections`}});
				} else {
					breasts.push({text: `Hyper enhancement`, disabled: `Boobs are too large`});
				}
			}

			// Nipples
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (slave.nipples === "huge" || slave.nipples === "puffy" || slave.nipples === "cute") {
					nipples.push({text: `Reducers`, updateSlave: {drugs: `nipple atrophiers`}});
				} else {
					nipples.push({text: `Reducers`, disabled: `Nipples are ${slave.nipples}`});
				}
			}
			if (V.dispensary) {
				if (["inverted", "partially inverted", "cute", "tiny", "puffy", "flat"].includes(slave.nipples)) {
					nipples.push({text: `Enhancement`, updateSlave: {drugs: `nipple enhancers`}});
				} else if (slave.nipples === "huge") {
					nipples.push({text: `Enhancement`, disabled: `Nipples are already huge`});
				} else {
					nipples.push({text: `Enhancement`, disabled: `Has no effect on ${slave.nipples} nipples`});
				}
			}

			// Butt
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (slave.butt - slave.buttImplant > 0) {
					butt.push({text: `Reducers`, updateSlave: {drugs: `butt redistributors`}});
				} else {
					butt.push({text: `Reducers`, disabled: `Butt is too small`});
				}
			}
			if (slave.butt < 9) {
				butt.push({text: `Enhancement`, updateSlave: {drugs: `butt injections`}});
				butt.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive butt injections`}});
			} else {
				butt.push({text: `Enhancement`, disabled: `Butt is too large`});
			}
			if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
				if (slave.butt < 20) {
					butt.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper butt injections`}});
				} else {
					butt.push({text: `Hyper enhancement`, disabled: `Butt is too large`});
				}
			}

			// Lips
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (slave.lips - slave.lipsImplant > 0) {
					lips.push({text: `Reducers`, updateSlave: {drugs: `lip atrophiers`}});
				} else {
					lips.push({text: `Reducers`, disabled: `Lips are too small`});
				}
			}
			if (slave.lips <= 95 || (slave.lips <= 85 && V.seeExtreme !== 1)) {
				lips.push({text: `Enhancement`, updateSlave: {drugs: `lip injections`}});
			} else {
				lips.push({text: `Enhancement`, disabled: `Lips are too large`});
			}

			// Fertility
			fertility.push({text: `Fertility`, updateSlave: {drugs: `fertility drugs`}});
			if (V.seeHyperPreg === 1 && slave.indentureRestrictions < 1 && V.superFertilityDrugs === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || !FutureSocieties.isActive('FSRestart'))) {
				fertility.push({text: `Fertility+`, updateSlave: {drugs: `super fertility drugs`}});
			}

			// Dick/clit
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (slave.dick > 1) {
					dick.push({text: `Reducers`, updateSlave: {drugs: `penis atrophiers`}});
				} else if (slave.dick === 1) {
					dick.push({text: `Reducers`, disabled: `Dick is already at minimum size`});
				}
				if (slave.clit > 0) {
					dick.push({text: `Reducers`, updateSlave: {drugs: `clitoris atrophiers`}});
				}
			}
			if (slave.dick > 0) {
				if (slave.dick < 10) {
					dick.push({text: `Enhancement`, updateSlave: {drugs: `penis enhancement`}});
					dick.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive penis enhancement`}});
				} else {
					dick.push({text: `Enhancement`, disabled: `Dick is too large`});
				}
			} else {
				if (slave.clit < 5) {
					dick.push({text: `Enhancement`, updateSlave: {drugs: `penis enhancement`}});
					dick.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive penis enhancement`}});
				} else {
					dick.push({text: `Enhancement`, disabled: `Clit is too large`});
				}
			}
			if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
				if (slave.dick > 0) {
					if (slave.dick < 31) {
						dick.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper penis enhancement`}});
					} else {
						dick.push({text: `Hyper enhancement`, disabled: `Dick is too large`});
					}
				} else {
					if (slave.clit < 5) {
						dick.push({text: `Hyper enhancement`, updateSlave: {drugs: `penis enhancement`}});
					} else {
						dick.push({text: `Hyper enhancement`, disabled: `Clit is too large`});
					}
				}
			}
			if (slave.dick.isBetween(0, 11) && !canAchieveErection(slave) && slave.chastityPenis !== 1) {
				dick.push({text: `Erectile dysfunction circumvention`, updateSlave: {drugs: `priapism agents`}});
			}

			// Balls
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (slave.balls > 1) {
					balls.push({text: `Reducers`, updateSlave: {drugs: `testicle atrophiers`}});
				} else if (slave.balls === 1) {
					balls.push({text: `Reducers`, disabled: `Balls are already at minimum size`});
				}
			}
			if (slave.balls > 0) {
				balls.push({text: `Enhancement`, updateSlave: {drugs: `testicle enhancement`}});
				balls.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive testicle enhancement`}});
				if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
					balls.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper testicle enhancement`}});
				}
			}

			// Hormones
			if (V.precociousPuberty === 1 && V.pubertyHormones === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || !FutureSocieties.isActive('FSRestart'))) {
				if ((slave.ovaries === 1 || slave.mpreg === 1) && slave.pubertyXX === 0) {
					hormones.push({text: `Female injections`, updateSlave: {drugs: `female hormone injections`}});
				}
				if (slave.balls > 0 && slave.pubertyXY === 0) {
					hormones.push({text: `Male injections`, updateSlave: {drugs: `male hormone injections`}});
				}
			}
			hormones.push({text: `Blockers`, updateSlave: {drugs: `hormone blockers`}});
			hormones.push({text: `Enhancement`, updateSlave: {drugs: `hormone enhancers`}});

			// Misc
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (slave.labia > 0) {
					misc.push({text: `Labia reducers`, updateSlave: {drugs: `labia atrophiers`}});
				}
			}
			if (V.growthStim === 1) {
				if (canImproveHeight(slave)) {
					misc.push({text: `Growth stimulants`, updateSlave: {drugs: `growth stimulants`}});
				} else {
					misc.push({text: `Growth stimulants`, disabled: `Cannot increase height further`});
				}
			}
			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
				if (slave.weight > -95) {
					misc.push({text: `Weight loss pills`, updateSlave: {drugs: `appetite suppressors`}});
				} else {
					misc.push({text: `Weight loss pills`, disabled: `Slave is already at low weight`});
				}
			}
			misc.push({text: `Steroids`, updateSlave: {drugs: `steroids`}});
			if (slave.boobs > 250 && slave.boobShape !== "saggy" && V.purchasedSagBGone === 1) {
				misc.push({text: `Sag-B-Gone breast lifting cream`, updateSlave: {drugs: `sag-B-gone`}});
			}
			if (V.arcologies[0].FSYouthPreferentialistResearch === 1) {
				if (slave.visualAge > 18) {
					misc.push({text: `Anti-aging cream`, updateSlave: {drugs: `anti-aging cream`}});
				} else {
					misc.push({text: `Anti-aging cream`, disabled: `Slave already looks young enough`});
				}
			}
		}

		let title = document.createElement('div');
		title.textContent = `Drugs: `;
		let chosenDrug = document.createElement('span');
		chosenDrug.textContent = `${capFirstChar(slave.drugs)}. `;
		chosenDrug.style.fontWeight = "bold";
		title.append(chosenDrug);
		title.appendChild(App.UI.SlaveInteract.generateRows(drugLevelOptions, slave, "", false, refresh));
		el.append(title);

		// Put mod added drugs into categories:
		const modDrugs = new Map();
		App.Mods.Drugs.list.filter(drug => !drug.isPCDrug && drug.available(slave)).forEach(drug => {
			let typeArray;
			// Allow drugs to be added to vanilla categories
			switch (drug.type) {
				case "Lips": typeArray = lips; break;
				case "Breasts": typeArray = breasts; break;
				case "Nipples": typeArray = nipples; break;
				case "Butt": typeArray = butt; break;
				case "Dick":
				case "Clit": typeArray = dick; break;
				case "Fertility": typeArray = fertility; break;
				case "Hormones": typeArray = hormones; break;
				case "Psych": typeArray = psych; break;
				case "Misc": typeArray = misc; break;
				default:
					if (!modDrugs.has(drug.type)) {
						modDrugs.set(drug.type, []);
					}
					typeArray = modDrugs.get(drug.type);
			}
			// Test if drug should be enabled and push to array
			if (drug.enable(slave) === true) {
				typeArray.push({text: drug.text, updateSlave: {drugs: drug.name}});
			} else {
				typeArray.push({text: drug.text, disabled: drug.enable(slave)});
			}
		});

		appendLabeledChoiceRow("Lips", lips, el);
		appendLabeledChoiceRow("Breasts", breasts, el);
		appendLabeledChoiceRow("Nipples", nipples, el);
		appendLabeledChoiceRow("Butt", butt, el);
		appendLabeledChoiceRow(slave.dick > 0 ? "Dick" : "Clit", dick, el);
		appendLabeledChoiceRow("Balls", balls, el);
		appendLabeledChoiceRow("Fertility", fertility, el);
		appendLabeledChoiceRow("Hormones", hormones, el);
		appendLabeledChoiceRow("Psych", psych, el);
		appendLabeledChoiceRow("Misc", misc, el);

		// Add modded rows
		modDrugs.forEach((drugArray, drugType) => appendLabeledChoiceRow(drugType, drugArray, el));

		return el;
	}

	function curatives() {
		const curativeOptions = [];

		curativeOptions.push({text: `None`, updateSlave: {curatives: 0}});
		curativeOptions.push({text: `Preventatives`, updateSlave: {curatives: 1}});
		curativeOptions.push({text: `Curatives`, updateSlave: {curatives: 2}});

		let el = document.createElement('div');
		let title = document.createElement('div');
		title.append(`Health: `);
		let chosenOption = document.createElement('span');
		chosenOption.style.fontWeight = "bold";
		if (slave.curatives > 1) {
			chosenOption.textContent = `curatives`;
		} else if (slave.curatives > 0) {
			chosenOption.textContent = `preventatives`;
		} else {
			chosenOption.textContent = `none`;
		}
		title.appendChild(chosenOption);
		title.append(`.`);
		let link = document.createElement('div');
		link.className = "choices";
		link.appendChild(App.UI.SlaveInteract.generateRows(curativeOptions, slave, "", false, refresh));
		el.append(title);
		el.append(link);
		return el;
	}

	function aphrodisiacs() {
		const aphrodisiacOptions = [];

		aphrodisiacOptions.push({text: `None`, updateSlave: {aphrodisiacs: 0}});
		aphrodisiacOptions.push({text: `Aphrodisiacs`, updateSlave: {aphrodisiacs: 1}});
		aphrodisiacOptions.push({text: `Extreme aphrodisiacs`, updateSlave: {aphrodisiacs: 2}});
		aphrodisiacOptions.push({text: `Anaphrodisiacs`, updateSlave: {aphrodisiacs: -1}, note: `Suppresses libido`});

		let el = document.createElement('div');
		let title = document.createElement('div');
		title.append(`Aphrodisiacs: `);
		let chosenOption = document.createElement('span');
		chosenOption.style.fontWeight = "bold";
		if (slave.aphrodisiacs > 1) {
			chosenOption.textContent = `extreme`;
		} else if (slave.aphrodisiacs > 0) {
			chosenOption.textContent = `applied`;
		} else if (slave.aphrodisiacs === -1) {
			chosenOption.textContent = `anaphrodisiacs`;
		} else {
			chosenOption.textContent = `none`;
		}
		title.appendChild(chosenOption);
		title.append(`.`);
		let link = document.createElement('div');
		link.className = "choices";
		link.appendChild(App.UI.SlaveInteract.generateRows(aphrodisiacOptions, slave, "", false, refresh));
		el.append(title);
		el.append(link);
		return el;
	}

	function fertility() {
		let fertilityBlock = document.createElement('span');
		let linkArray = [];
		if (slave.ovaries === 1 || slave.mpreg === 1 || slave.preg > 0) {
			let note = "";
			if (slave.preg < -2) {
				note += `${He} has been sterilized`;
			} else if (slave.preg < -1) {
				note += `${He} is sterile`;
			} else if (slave.pubertyXX === 0 && slave.preg < 1) {
				note += `${He} is not yet fertile`;
			} else if (slave.ovaryAge >= 47 && slave.preg < 1) {
				note += `${He} is too old to become pregnant`;
				if (slave.preg === -1) {
					slave.preg = 0;
					SetBellySize(slave);
				}
			} else if (slave.broodmotherOnHold === 1) {
				note += `${His} pregnancy implant is turned off`;
				if (slave.broodmotherCountDown > 0) {
					note += `; ${he} is expected to be completely emptied of ${his} remaining brood in ${slave.broodmotherCountDown} week`;
					if (slave.broodmotherCountDown > 1) {
						note += `s`;
					}
					note += `.`;
					linkArray.push(App.UI.DOM.link(
						`Turn on implant`,
						() => {
							slave.broodmotherOnHold = 0;
							slave.broodmotherCountDown = 0;
							refresh();
						},
					));
				}
			} else if (slave.preg >= -1) {
				fertilityBlock.append("Contraception and fertility: ");
				/** @type {string} */
				let fertility;
				// fertility.id = "fertility";
				if (slave.preg === -1) {
					fertility = "using contraceptives";
				} else if (slave.pregWeek < 0) {
					fertility = "postpartum";
				} else if (slave.preg === 0) {
					fertility = "fertile";
				} else if (slave.preg < 4 && (slave.broodmother === 0 || slave.broodmotherOnHold === 1)) {
					fertility = "may be pregnant";
				} else if (slave.preg < 2) {
					fertility = "1 week pregnant";
				} else {
					fertility = `${Math.trunc(slave.preg * 1000) / 1000} weeks pregnant`; // * and / needed to avoid seeing something like 20.1000000008 in some cases.
					if (slave.broodmother > 0) {
						fertility += " broodmother";
					}
				}
				fertility += ". ";
				App.UI.DOM.appendNewElement("span", fertilityBlock, fertility, "bold");

				if (slave.preg === 0) {
					linkArray.push(App.UI.DOM.link(
						`Use contraceptives`,
						() => {
							slave.preg = -1;
							refresh();
						},
					));
				} else if (slave.preg === -1) {
					linkArray.push(App.UI.DOM.link(
						`Let ${him} get pregnant`,
						() => {
							slave.preg = 0;
							refresh();
						},
					));
				} else if (isInduced(slave)) {
					note += `Hormones are being slipped into ${his} food; ${he} will give birth suddenly and rapidly this week`;
				} else if (
					slave.preg > slave.pregData.normalBirth - 2 &&
					slave.preg > slave.pregData.minLiveBirth &&
					slave.broodmother === 0 &&
					!isInLabor(slave)
				) {
					linkArray.push(App.UI.DOM.link(
						`Induce labor`,
						() => {
							induce(slave);
							refresh();
						},
					));
					linkArray.push(App.UI.DOM.passageLink(`Give ${him} a cesarean section`, "csec"));
				} else if (slave.broodmother > 0) {
					if (slave.broodmotherOnHold !== 1) {
						linkArray.push(App.UI.DOM.link(
							`Turn off implant`,
							() => {
								slave.broodmotherOnHold = 1;
								slave.broodmotherCountDown = 38 - WombMinPreg(slave);
								refresh();
							},
						));
					}
					if (slave.preg >= 36) {
						linkArray.push(App.UI.DOM.passageLink(`Induce mass childbirth`, "BirthStorm"));
					}
				} else if (slave.preg > slave.pregData.minLiveBirth) {
					linkArray.push(App.UI.DOM.link(
						`Give ${him} a cesarean section`,
						() => {
							slave.broodmotherOnHold = 0;
							slave.broodmotherCountDown = 0;
						},
						[],
						"csec"
					));
				} else if (slave.preg > 0 && slave.breedingMark === 1 && V.propOutcome === 1 && FutureSocieties.isActive('FSRestart') && V.eugenicsFullControl !== 1 && (slave.pregSource === -1 || slave.pregSource === -6)) {
					note += "You are forbidden from aborting an Elite child";
				} else if (slave.preg > 0) {
					linkArray.push(App.UI.DOM.link(
						`Abort ${his} pregnancy`,
						() => {
							slave.broodmotherOnHold = 0;
							slave.broodmotherCountDown = 0;
						},
						[],
						"Abort"
					));
				}
			}
			App.UI.DOM.appendNewElement("span", fertilityBlock, note, "note");
			App.UI.DOM.appendNewElement("div", fertilityBlock, App.UI.DOM.generateLinksStrip(linkArray), "choices");
		}
		if (
			slave.pregKnown === 1 &&
			V.pregSpeedControl === 1 &&
			(
				slave.breedingMark !== 1 ||
				V.propOutcome === 0 ||
				V.eugenicsFullControl === 1 ||
				!FutureSocieties.isActive('FSRestart')
			) &&
			slave.indentureRestrictions < 1 &&
			slave.broodmother === 0 &&
			V.seePreg !== 0
		) {
			let title = document.createElement('div');
			let underline = document.createElement('span');
			underline.style.textDecoration = "underline";
			underline.textContent = "Pregnancy control";
			title.appendChild(underline);
			title.append(": ");

			if (slave.pregControl === "labor suppressors") {
				title.append("Labor is suppressed. ");
			} else if (slave.pregControl === "slow gestation") {
				title.append("Slowed gestation speed. ");
			} else if (slave.pregControl === "speed up") {
				title.append("Faster gestation speed, staffed clinic recommended. ");
			} else {
				title.append("Normal gestation and birth. ");
			}
			fertilityBlock.appendChild(title);

			linkArray = [];
			if (slave.pregControl !== "none") {
				linkArray.push(App.UI.DOM.link(
					`Normal ${slave.preg < slave.pregData.normalBirth ? "Gestation" : "Birth"}`,
					() => {
						slave.pregControl = "none";
						refresh();
					},
				));
			}
			if (slave.preg < slave.pregData.normalBirth) {
				if (slave.pregControl !== "slow gestation") {
					linkArray.push(App.UI.DOM.link(
						`Slow Gestation`,
						() => {
							slave.pregControl = "slow gestation";
							refresh();
						},
					));
				}
				if (slave.pregControl !== "speed up") {
					linkArray.push(App.UI.DOM.link(
						`Fast Gestation`,
						() => {
							slave.pregControl = "speed up";
							refresh();
						},
					));
				}
			}
			if (slave.preg >= slave.pregData.minLiveBirth) {
				if (slave.pregControl !== "labor suppressors") {
					linkArray.push(App.UI.DOM.link(
						`Suppress Labor`,
						() => {
							slave.pregControl = "labor suppressors";
							refresh();
						},
					));
				}
			}
			App.UI.DOM.appendNewElement("div", fertilityBlock, App.UI.DOM.generateLinksStrip(linkArray), "choices");
		}
		return fertilityBlock;
	}

	function incubator() {
		const reservedChildren = FetusGlobalReserveCount("incubator");
		let reservedIncubator = WombReserveCount(slave, "incubator");
		let reservedNursery = WombReserveCount(slave, "nursery");
		let WL = slave.womb.length;
		let el = document.createElement('div');
		const linkArray = [];

		if (V.incubator.capacity > 0) {
			if (slave.preg > 0 && slave.broodmother === 0 && slave.pregKnown === 1 && slave.eggType === "human") {
				if ((slave.assignment !== Job.DAIRY || V.dairyPregSetting === 0) && (slave.assignment !== Job.FARMYARD || V.farmyardBreeding === 0)) {
					let title = document.createElement('div');
					if (WL - reservedNursery === 0) {
						title.textContent = `${His} children are already reserved for ${V.nurseryName}`;
						title.style.fontStyle = "italic";
					} else {
						const freeTanks = (V.incubator.capacity - V.incubator.tanks.length);
						if (reservedIncubator > 0) {
							if (WL === 1) {
								title.textContent = `${His} child `;
							} else if (reservedIncubator < WL) {
								title.textContent = `${reservedIncubator} of ${his} children `;
							} else if (WL === 2) {
								title.textContent = `Both of ${his} children `;
							} else {
								title.textContent = `All ${reservedIncubator} of ${his} children `;
							}
							title.textContent += ` will be placed in ${V.incubator.name}. `;
							if ((reservedIncubator + reservedNursery < WL) && (reservedChildren < freeTanks)) {
								linkArray.push(
									App.UI.DOM.link(`Keep another child`, () => wombUpdateIncubator(1, true))
								);
								if (reservedIncubator > 0) {
									linkArray.push(
										App.UI.DOM.link(`Keep one less child`, () => wombUpdateIncubator(1, false))
									);
								}
								if (reservedIncubator > 1) {
									linkArray.push(
										App.UI.DOM.link(`Keep none of ${his} children`, () => wombUpdateIncubator(9999, false))
									);
								}
								if ((reservedChildren + WL - reservedIncubator) <= freeTanks) {
									linkArray.push(
										App.UI.DOM.link(`Keep the rest of ${his} children`, () => wombUpdateIncubator(9999, true))
									);
								}
							} else if ((reservedIncubator === WL) || (reservedChildren === freeTanks) || (reservedIncubator - reservedNursery >= 0)) {
								linkArray.push(
									App.UI.DOM.link(`Keep one less child`, () => wombUpdateIncubator(1, false))
								);
								if (reservedIncubator > 1) {
									linkArray.push(
										App.UI.DOM.link(`Keep none of ${his} children`, () => wombUpdateIncubator(9999, false))
									);
								}
							}
						} else if (reservedChildren < freeTanks) {
							title.textContent = `${He} is pregnant and you have `;
							if (freeTanks === 1) {
								title.textContent += `an `;
							}
							let tank = document.createElement('span');
							tank.className = "lime";
							tank.textContent = `available aging tank`;
							if (freeTanks > 1) {
								tank.textContent += `s`;
							}
							tank.textContent += `.`;
							let cCount = (WL > 1 ? "a" : "the");
							linkArray.push(
								App.UI.DOM.link(`Keep ${cCount} child`, () => wombUpdateIncubator(1, true))
							);
							title.appendChild(tank);
							if ((WL > 1) && (reservedChildren + WL) <= freeTanks) {
								linkArray.push(
									App.UI.DOM.link(`Keep all of ${his} children`, () => wombUpdateIncubator(9999, true))
								);
							}
						} else if (reservedChildren === freeTanks) {
							title.textContent = `You have no available tanks for ${his} children. `;
						}
					}
					el.append(title);
					App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(linkArray), "choices");
				}
			}
		}

		function wombUpdateIncubator(count, addToWomb) {
			if (addToWomb) {
				WombAddToGenericReserve(slave, "incubator", count);
			} else {
				WombCleanGenericReserve(slave, "incubator", count);
			}
			refresh();
		}

		return el;
	}

	function nursery() {
		let el = document.createElement('div');
		if (V.nursery > 0) {
			const reservedChildrenNursery = FetusGlobalReserveCount("nursery");
			let reservedIncubator = WombReserveCount(slave, "incubator");
			let reservedNursery = WombReserveCount(slave, "nursery");
			let WL = slave.womb.length;
			if (slave.preg > 0 && slave.broodmother === 0 && slave.pregKnown === 1 && slave.eggType === "human") {
				if ((slave.assignment !== Job.DAIRY || V.dairyPregSetting === 0) && (slave.assignment !== Job.FARMYARD || V.farmyardBreeding === 0)) {
					let title = document.createElement('div');
					const linkArray = [];
					if (WL - reservedIncubator === 0) {
						title.textContent = `${His} children are already reserved for ${V.incubator.name}`;
						title.style.fontStyle = "italic";
					} else {
						const freeCribs = (V.nurseryCribs - V.cribs.length);
						if (reservedNursery > 0) {
							if (WL === 1) {
								title.textContent = `${His} child will be placed in ${V.nurseryName}. `;
							} else if (reservedNursery < WL) {
								title.textContent = `${reservedNursery} of ${his} children will be placed in ${V.nurseryName}.`;
							} else if (WL === 2) {
								title.textContent = `Both of ${his} children will be placed in ${V.nurseryName}. `;
							} else {
								title.textContent = `All ${reservedNursery} of ${his} children will be placed in ${V.nurseryName}. `;
							}
							if ((reservedIncubator + reservedNursery < WL) && (reservedChildrenNursery < freeCribs)) {
								linkArray.push(
									App.UI.DOM.link(`Keep another child`, () => wombUpdateNursery(1, true))
								);

								if (reservedNursery > 0) {
									linkArray.push(
										App.UI.DOM.link(`Keep one less child`, () => wombUpdateNursery(1, false))
									);
								}
								if (reservedNursery > 1) {
									linkArray.push(
										App.UI.DOM.link(`Keep none of ${his} children`, () => wombUpdateNursery(9999, false))
									);
								}
								if ((reservedChildrenNursery + WL - reservedNursery) <= freeCribs) {
									linkArray.push(
										App.UI.DOM.link(`Keep the rest of ${his} children`, () => wombUpdateNursery(9999, true))
									);
								}
							} else if ((reservedNursery === WL) || (reservedChildrenNursery === freeCribs) || (reservedNursery - reservedIncubator >= 0)) {
								linkArray.push(
									App.UI.DOM.link(`Keep one less child`, () => wombUpdateNursery(1, false))
								);

								if (reservedNursery > 1) {
									linkArray.push(
										App.UI.DOM.link(`Keep none of ${his} children`, () => wombUpdateNursery(9999, false))
									);
								}
							}
						} else if (reservedChildrenNursery < freeCribs) {
							title.textContent = `${He} is pregnant and you have `;
							if (freeCribs === 1) {
								title.textContent += `an `;
							}
							let crib = document.createElement('span');
							crib.className = "lime";
							crib.textContent = `available room`;
							if (freeCribs > 1) {
								crib.textContent += `s`;
							}
							crib.textContent += `.`;
							let cCount = (WL > 1 ? "a" : "the");
							linkArray.push(
								App.UI.DOM.link(`Keep ${cCount} child`, () => wombUpdateNursery(1, true))
							);
							title.appendChild(crib);
							if ((WL > 1) && (reservedChildrenNursery + WL) <= freeCribs) {
								linkArray.push(
									App.UI.DOM.link(`Keep all of ${his} children`, () => wombUpdateNursery(9999, true))
								);
							}
						} else if (reservedChildrenNursery === freeCribs) {
							title.textContent = `You have no available rooms for ${his} children. `;
						}
					}
					el.append(title);
					App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(linkArray), "choices");
				}
			}
		}

		function wombUpdateNursery(count, addToWomb) {
			if (addToWomb) {
				WombAddToGenericReserve(slave, "nursery", count);
			} else {
				WombCleanGenericReserve(slave, "nursery", count);
			}
			refresh();
		}

		return el;
	}

	function breederEligibility() {
		const el = document.createElement("div");
		if (V.propOutcome === 1 && FutureSocieties.isActive('FSRestart')) {
			if (slave.breedingMark === 0 && slave.fuckdoll === 0 && slave.eggType === "human" && isFertile(slave) && slave.preg <= 0) {
				el.classList.add("choices");
				el.append(
					App.UI.DOM.passageLink(
						`Breeder Eligibility Exam`,
						`BreedingTest`
					)
				);
			}
		}
		return el;
	}

	function bloating() {
		let bloating = document.createElement('div');
		if (slave.inflation > 0) {
			let intro = document.createElement('span');
			intro.textContent = "Required Bloating";
			intro.style.textDecoration = "underline";
			bloating.append(intro);

			bloating.append(": ");

			let requirement = document.createElement('span');
			requirement.style.fontWeight = "bold";
			requirement.id = "inflate";
			if (slave.inflation === 3) {
				requirement.textContent = `${He} is required to keep 2 gallons of ${slave.inflationType} in ${him} at all times`;
			} else if (slave.inflation === 2) {
				requirement.textContent = `${He} is required to keep 4 liters of ${slave.inflationType} in ${him} at all times`;
			} else if (slave.inflation === 1) {
				requirement.textContent = `${He} is required to keep 2 liters of ${slave.inflationType} in ${him} at all times`;
			}
			bloating.append(requirement);
			bloating.append(". ");

			let link = App.UI.DOM.link(
				`Let ${him} deflate`,
				() => {
					deflate(slave);
					refresh();
				},
			);
			bloating.append(link);
		}
		// make sure it updates itself after run
		return bloating;
	}

	function hormones() {
		let el = document.createElement('div');
		const options = [];
		const level = [];

		if (slave.hormones !== 0) {
			level.push({text: `None`, updateSlave: {hormones: 0}});
		}

		if (slave.indentureRestrictions < 2) {
			options.push({text: `Intensive Female`, updateSlave: {hormones: 2}});
		} else {
			options.push({text: `Intensive Female`, disabled: `Cannot use intensive hormones on indentured slaves`});
		}
		options.push({text: `Female`, updateSlave: {hormones: 1}});
		options.push({text: `Male`, updateSlave: {hormones: -1}});
		if (slave.indentureRestrictions < 2) {
			options.push({text: `Intensive Male`, updateSlave: {hormones: -2}});
		} else {
			options.push({text: `Intensive Male`, disabled: `Cannot use intensive hormones on indentured slaves`});
		}

		let title = document.createElement('div');
		title.textContent = `Hormones: `;
		let choice = document.createElement('span');
		choice.style.fontWeight = "bold";
		switch (slave.hormones) {
			case 2: {
				choice.textContent = `intensive female. `;
				break;
			}
			case 1: {
				choice.textContent = `female. `;
				break;
			}
			case 0: {
				choice.textContent = `none. `;
				break;
			}
			case -1: {
				choice.textContent = `male. `;
				break;
			}
			case -2: {
				choice.textContent = `intensive male. `;
				break;
			}
			default: {
				choice.textContent = `Not set. `;
			}
		}

		title.append(choice);
		title.appendChild(App.UI.SlaveInteract.generateRows(level, slave, "", false, refresh));
		el.append(title);

		let links = document.createElement('div');
		links.appendChild(App.UI.SlaveInteract.generateRows(options, slave, "", false, refresh));
		links.className = "choices";
		el.append(links);

		return el;
	}

	function diet() {
		let el = document.createElement('div');

		let title = document.createElement('div');
		title.textContent = `Diet: `;
		let choice = document.createElement('span');
		choice.style.fontWeight = "bold";
		choice.textContent = `${capFirstChar(slave.diet)}. `;

		title.append(choice);
		el.append(title);

		const health = [];
		health.push({text: `Healthy`, updateSlave: {diet: "healthy"}});
		if (V.dietCleanse === 1) {
			if (slave.health.condition < 90 || slave.chem >= 10) {
				health.push({text: `Cleanse`, updateSlave: {diet: "cleansing"}});
			} else {
				health.push({text: `Cleanse`, disabled: `${He} is already healthy`});
			}
		}

		const weight = [];
		if (slave.weight >= -95) {
			weight.push({text: `Lose weight`, updateSlave: {diet: "restricted"}});
		} else {
			weight.push({text: `Lose weight`, disabled: `${He} is already underweight`});
		}
		if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN && V.feeder === 1) {
			if (slave.weight > 10 || slave.weight < -10) {
				weight.push({text: `Correct weight`, updateSlave: {diet: "corrective"}});
			} else {
				weight.push({text: `Correct weight`, disabled: `${He} is already a healthy weight`});
			}
		}
		if (slave.weight <= 200) {
			weight.push({text: `Fatten`, updateSlave: {diet: "fattening"}});
		} else {
			weight.push({text: `Fatten`, disabled: `${He} is already overweight`});
		}

		const muscle = [];
		if (slave.muscles < 100 && !isAmputee(slave)) {
			muscle.push({text: `Build muscle`, updateSlave: {diet: "muscle building"}});
		} else if (!isAmputee(slave)) {
			muscle.push({text: `Build muscle`, disabled: `${He} is maintaining ${his} enormous musculature`});
		} else {
			muscle.push({text: `Build muscle`, disabled: `${He} has no limbs and thus can't effectively build muscle`});
		}

		if ((slave.muscles > 0 || slave.fuckdoll === 0) && canWalk(slave)) {
			muscle.push({text: `Slim down`, updateSlave: {diet: "slimming"}});
		} else if (!canWalk(slave)) {
			muscle.push({text: `Slim down`, disabled: `${He} can't walk and thus can't trim down`});
		} else if (slave.fuckdoll > 0) {
			muscle.push({text: `Slim down`, disabled: `${He} has no muscles left to lose`});
		}

		const production = [];
		if (slave.balls > 0 && V.cumProDiet === 1) {
			production.push({text: `Cum production`, updateSlave: {diet: "cum production"}});
		}
		if (((isFertile(slave) && slave.preg === 0) || (slave.geneticQuirks.superfetation === 2 && canGetPregnant(slave) && V.geneticMappingUpgrade !== 0)) && V.dietFertility === 1) {
			production.push({text: `Fertility`, updateSlave: {diet: "fertility"}});
		}

		const hormone = [];
		if (V.feeder === 1) {
			hormone.push({text: `Estrogen enriched`, updateSlave: {diet: "XX"}});
			hormone.push({text: `Testosterone enriched`, updateSlave: {diet: "XY"}});
			if (V.dietXXY === 1 && slave.balls > 0 && (slave.ovaries === 1 || slave.mpreg === 1)) {
				hormone.push({text: `Herm hormone blend`, updateSlave: {diet: "XXY"}});
			}
		}

		appendLabeledChoiceRow("Health", health, el);
		appendLabeledChoiceRow("Weight", weight, el);
		appendLabeledChoiceRow("Muscle", muscle, el);
		appendLabeledChoiceRow("Production", production, el);
		appendLabeledChoiceRow("Hormone", hormone, el);

		return el;
	}

	function dietBase() {
		let el = document.createElement('div');
		const milk = [];
		const cum = [];

		// Milk
		if (slave.dietCum < 2) {
			milk.push({text: `Milk added`, updateSlave: {dietMilk: 1}});
			if (slave.dietCum < 2) {
				milk.push({text: `Milk based`, updateSlave: {dietMilk: 2, dietCum: 0}});
			}
			if (slave.dietMilk) {
				milk.push({text: `Remove milk`, updateSlave: {dietMilk: 0}});
			}
		} else {
			milk.push({text: `Milk`, disabled: `Diet is based entirely on cum`});
		}

		// Cum
		if (slave.dietMilk < 2) {
			cum.push({text: `Cum added`, updateSlave: {dietCum: 1}});
			cum.push({text: `Cum based`, updateSlave: {dietCum: 2, dietMilk: 0}});
			if (slave.dietCum) {
				cum.push({text: `Remove cum`, updateSlave: {dietCum: 0}});
			}
		} else {
			cum.push({text: `Cum`, disabled: `Diet is based entirely on milk`});
		}

		let title = document.createElement('div');
		title.textContent = `Diet base: `;
		let choice = document.createElement('span');
		choice.style.fontWeight = "bold";
		if (slave.dietCum === 2) {
			choice.textContent = `cum based. `;
		} else if (slave.dietCum === 1 && slave.dietMilk === 0) {
			choice.textContent = `cum added. `;
		} else if (slave.dietCum === 1 && slave.dietMilk === 1) {
			choice.textContent = `cum and milk added. `;
		} else if (slave.dietMilk === 1 && slave.dietCum === 0) {
			choice.textContent = `milk added. `;
		} else if (slave.dietMilk === 2) {
			choice.textContent = `milk based. `;
		} else if (slave.dietCum === 0 && slave.dietMilk === 0) {
			choice.textContent = `normal. `;
		} else {
			choice.textContent = `THERE HAS BEEN AN ERROR.`;
		}

		title.append(choice);
		el.append(title);

		appendLabeledChoiceRow("Milk", milk, el);
		appendLabeledChoiceRow("Cum", cum, el);

		return el;
	}

	function snacks() {
		let el = document.createElement('div');
		let options = [];

		if (V.arcologies[0].FSHedonisticDecadenceResearch === 1) {
			let title = document.createElement('div');
			title.textContent = `Solid Slave Food Access: `;
			let choice = document.createElement('span');
			choice.style.fontWeight = "bold";
			if (slave.onDiet === 0) {
				choice.textContent = `Free to stuff ${himself}.`;
			} else {
				choice.textContent = `On a strict diet.`;
			}
			title.append(choice);
			el.append(title);

			options.push({text: `No access`, updateSlave: {onDiet: 1}});
			options.push({text: `Full access`, updateSlave: {onDiet: 0}});

			let links = document.createElement('div');
			links.appendChild(App.UI.SlaveInteract.generateRows(options, slave, "", false, refresh));
			links.className = "choices";
			el.append(links);
		}

		return el;
	}

	/** Append a simple row of choices with a label to a container, if there are choices to be made.
	 * @param {string} label
	 * @param {RowItem[]} array
	 * @param {HTMLElement} el
	 */
	function appendLabeledChoiceRow(label, array, el) {
		if (array.length > 0) {
			const links = document.createElement('div');
			links.append(`${label}: `);
			links.appendChild(App.UI.SlaveInteract.generateRows(array, slave, "", false, refresh));
			links.className = "choices";
			return el.appendChild(links);
		}
	}
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @param {function():void} refresh
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.records = function(slave, refresh) {
	const el = new DocumentFragment();
	let r;
	let linkArray;
	const {
		He, His,
		his, him
	} = getPronouns(slave);

	if (V.studio === 1) {
		App.UI.DOM.appendNewElement("h3", el, "Media");
		slave.porn.spending = Math.clamp(Math.ceil(slave.porn.spending / 1000) * 1000, 0, 5000);

		if (slave.porn.prestige === 3) {
			App.UI.DOM.appendNewElement("div", el, `${He} is so prestigious in the realm of ${slave.porn.fameType} porn that ${his} fame is self-sustaining.`, ["note"]);
		} else {
			if (slave.porn.feed === 0) {
				r = [];
				r.push(`The media hub is not releasing highlights of ${his} sex life.`);
				r.push(
					App.UI.DOM.link(
						"Release",
						() => {
							slave.porn.feed = 1;
							refresh();
						}
					)
				);
				App.Events.addNode(el, r, "div");
			} else {
				r = [];
				r.push(`The media hub is releasing highlights of ${his} sex life`);
				if (slave.porn.spending < 500) {
					r.push(`to those who can find it.`);
				} else if (slave.porn.spending < 2500) {
					r.push(`on several websites.`);
				} else if (slave.porn.spending > 5000) {
					r.push(`through your old distributor.`);
				} else {
					r.push(`on many websites.`);
				}
				if (slave.porn.spending === 0) {
					linkArray = [];
					linkArray.push(
						App.UI.DOM.link(
							"Halt",
							() => {
								slave.porn.feed = 0;
								slave.porn.focus = "none";
								refresh();
							}
						)
					);
					linkArray.push(
						App.UI.DOM.link(
							"Publicize",
							() => {
								slave.porn.spending += 1000;
								refresh();
							},
							[],
							"",
							`Will cost ${cashFormat(1000)} weekly.`
						)
					);
					r.push(App.UI.DOM.generateLinksStrip(linkArray));
					App.Events.addNode(el, r, "div");
				} else {
					r.push(
						App.UI.DOM.makeTextBox(
							slave.porn.spending,
							v => {
								slave.porn.spending = v;
							},
							true
						)
					);
					r.push(`weekly is spent to publicize them.`);

					linkArray = [];
					linkArray.push(
						App.UI.DOM.link(
							"Halt",
							() => {
								slave.porn.spending = 0;
								slave.porn.feed = 0;
								slave.porn.focus = "none";
								refresh();
							}
						)
					);
					if (slave.porn.spending <= 4000) {
						linkArray.push(
							App.UI.DOM.link(
								"Increase",
								() => {
									slave.porn.spending += 1000;
									refresh();
								},
								[],
								"",
								`Spending more than ${cashFormat(5000)} weekly will have no effect.`
							)
						);
					}
					linkArray.push(
						App.UI.DOM.link(
							"Decrease",
							() => {
								slave.porn.spending -= 1000;
								refresh();
							}
						)
					);
					r.push(App.UI.DOM.generateLinksStrip(linkArray));
					App.Events.addNode(el, r, "div");
				}
				if (V.studioFeed === 1) {
					r = [];
					if (slave.porn.viewerCount < 100) {
						r.push(`${He} lacks the fame in porn needed to discern what ${his} feed is getting tagged as.`);
					} else {
						if (slave.porn.prestige > 0) {
							r.push(`${He} is known for ${slave.porn.fameType === "generic" ? `standard, vanilla` : slave.porn.fameType} porn${(slave.porn.prestige > 1) ? ` and viewers have grown to expect it from ${him}` : ``}.`);
						}
						if (slave.porn.focus === "none") {
							r.push(`You are allowing ${his} viewers to guide the direction of ${his} content.`);
						} else {
							r.push(`You are focusing attention on the ${slave.porn.focus} aspect of ${his} content.`);
						}
						r.push(App.Porn.genreChoiceLinks(slave, () => refresh()));
					}
					App.Events.addNode(el, r, "div");
				}
			}
			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.combineNodes(App.Porn.makeFameProgressChart(slave)));
			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.combineNodes("Current viewership breakdown:", App.Porn.makeViewershipChart(slave)));
		}
	}
	App.UI.DOM.appendNewElement("h3", el, "Financial");
	App.UI.DOM.appendNewElement("p", el, slaveExpenses(slave));

	App.UI.DOM.appendNewElement("h3", el, `Statistics`);
	App.UI.DOM.appendNewElement("p", el, statistics());

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

		const slaves = [...slave.partners].filter(i => i > 0);
		const ownedSlaves = slaves.filter(s => getSlave(s));
		const unownedSlaves = slaves.length - ownedSlaves.length;
		const other = [];

		const ownedSlavesSpan = document.createElement("span");

		for (let i = 0; i < ownedSlaves.length; i++) {
			const innerSpan = document.createElement("span");
			const target = getSlave(ownedSlaves[i]);

			innerSpan.style.display = 'inline-block';	// hack to prevent span breaking line and giving unusable tooltip
			innerSpan.style.marginRight = '4px';

			if (ownedSlaves.length > 1) {
				if (i === ownedSlaves.length - 1) {
					innerSpan.append(
						` and `,
						contextualIntro(slave, target, true),
					);

					if (unownedSlaves > 0) {
						innerSpan.append(`, as well as ${numberWithPlural(unownedSlaves, 'slave')} you don't currently own`);
					}

					innerSpan.append(`.`);
				} else {
					innerSpan.append(contextualIntro(slave, target, true));

					if (ownedSlaves.length > 2) {
						innerSpan.append(`, `);
					}
				}
			} else {
				innerSpan.append(
					contextualIntro(slave, target, true, true),
					`.`,
				);
			}

			ownedSlavesSpan.append(innerSpan);
		}

		const partners = new Map([
			[-1, "you"],
			[-2, `citizens of ${V.arcologies[0].name}`],
			[-3, `your former master`],
			[-4, `another arcology owner`],
			[-6, `members of the Societal Elite`],
			[-8, `your animals`],
			[-9, `members of the Futanari Sisters`],
		]);

		for (const [ID, name] of partners) {
			if (slave.partners.has(ID)) {
				other.push(name);
			}
		}

		const link = App.UI.DOM.link(`${numberWithPluralOne(slaves.length, 'other slave')}`, () => {
			const innerDiv = document.createElement("div");

			innerDiv.append(
				`${slave.slaveName} has ${other.length > 0 ? `been fucked by ${toSentence(other)}, as well as `: `slept with `}`,
				ownedSlavesSpan
			);

			App.UI.DOM.replace(div, innerDiv);
		});

		if (slaves.length > 0) {
			text.push(
				`${He}'s had sex with`,
				link,
				`so far.`,
			);
		}

		if (other.length > 0) {
			text.push(`${He} has ${slaves.length > 0 ? `also` : `only`} had sex with ${toSentence(other)}.`);
		}

		if (V.showSexualHistory === 1 && V.ui !== "start") {
			text.push(App.Desc.sexualHistory(slave));
		}

		App.Events.addNode(div, text);

		return div;
	}

	r = [];
	linkArray = [];
	if (V.slaveCostFactor > 1) {
		r.push(App.UI.DOM.makeElement("span", `The slave market is bullish; the price of slaves is high.`, ["yellow"]));
	} else if (V.slaveCostFactor < 1) {
		r.push(App.UI.DOM.makeElement("span", `The slave market is bearish; the price of slaves is low.`, ["yellow"]));
	}

	if (V.slaves.length < 2) {
		r.push("You cannot sell your last slave");
	} else if (slave.origin === "You bought $him from a body dump, completely broken." && (V.week - slave.weekAcquired <= 8)) {
		r.push(`A discarded slave must be kept for at least two months to ensure health before being sold.`);
	} else {
		if (slave.accent < 4) {
			linkArray.push(
				App.UI.DOM.link(
					`Sell ${him}`,
					() => {
						cashX(-500, "personalBusiness", slave);
					},
					[],
					"Sell Slave",
					`Listing ${him} for sale will cost ${cashFormat(500)}`
				)
			);
		} else {
			r.push(`${His} lack of language and basic life skills is a red sign to most slave appraisers. ${He} must not act like a child to be sold without raising suspicion.`);
		}
		if ((V.seeAge !== 0) && (slave.indenture < 1)) {
			linkArray.push(
				App.UI.DOM.passageLink(
					`Retire ${him}`,
					"retire",
				)
			);
		}
		linkArray.push(
			App.UI.DOM.passageLink(
				`Discard ${him}`,
				"Discard Confirm",
			)
		);
		if (V.seeExtreme) {
			if (V.threatened[0].includes(slave.ID)) {
				linkArray.push(
					App.UI.DOM.disabledLink(
						`Threaten ${his} life`,
						[
							`You've already threatened ${him} this week.`,
						]
					)
				);
			} else {
				linkArray.push(
					App.UI.DOM.passageLink(
						`Threaten ${his} life`,
						"KillSlave",
						() => {
							V.threatened[0].push(slave.ID);
						}
					)
				);
			}
		}
	}
	linkArray.push(
		App.UI.DOM.passageLink(
			`Export this slave`,
			"Export Slave",
		)
	);
	if (V.slaveBotGeneration) {
		linkArray.push(
			App.UI.DOM.passageLink(
				`Create slave bot`,
				"Create Slave Bot",
			)
		);
	}
	if (V.cheatMode) {
		linkArray.push(
			App.UI.DOM.passageLink(
				`Import a slave`,
				"Import Slave",
			)
		);
	}
	r.push(App.UI.DOM.generateLinksStrip(linkArray));
	App.Events.addNode(el, r, "p");
	return el;
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @param {function():void} refresh
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.rules = function(slave, refresh) {
	const frag = new DocumentFragment();
	let p;
	let div;
	let array;
	/**
	 * @type {Array<listChoice>}
	 */
	let choices;
	const {
		He, His,
		he, him, his, himself
	} = getPronouns(slave);

	if (V.seePreg !== 0) {
		if (V.universalRulesImpregnation === "PC") {
			updateBreederLink("you", "PCExclude");
		} else if (V.universalRulesImpregnation === "HG") {
			updateBreederLink("the Head Girl", "HGExclude");
		} else if (V.universalRulesImpregnation === "Stud") {
			updateBreederLink("your Stud", "StudExclude");
		}
	}

	function updateBreederLink(breeder, exclude) {
		p = document.createElement('p');
		const exempt = slave[exclude] ? "Include" : "Exempt";

		p.append(`Will ${slave[exclude] ? "not " : ""}be bred by ${breeder} when fertile. `);
		p.append(
			App.UI.DOM.link(`${exempt} ${him}`, () => {
				slave[exclude] = slave[exclude] ^ 1; // switch 0 and 1
				refresh();
			})
		);
		frag.append(p);
	}

	p = document.createElement('p');

	array = [];
	if (slave.useRulesAssistant === 0) {
		App.UI.DOM.appendNewElement("span", p, `Not subject `, ["bold", "gray"]);
		App.UI.DOM.appendNewElement("span", p, `to the Rules Assistant. `, "gray");
		array.push(
			App.UI.DOM.link(
				`Include ${him}`,
				() => {
					slave.useRulesAssistant = 1;
					refresh();
				}
			)
		);
	} else {
		App.UI.DOM.appendNewElement("h3", p, `Rules Assistant`);

		if (slave.hasOwnProperty("currentRules") && slave.currentRules.length > 0) {
			const ul = document.createElement("UL");
			ul.style.margin = "0";
			V.defaultRules.filter(
				x => ruleApplied(slave, x)
			).map(
				x => {
					const li = document.createElement('li');
					li.append(x.name);
					ul.append(li);
				}
			);

			// set up rules display
			if ($("ul").has("li").length) {
				App.UI.DOM.appendNewElement("div", p, `Rules applied: `);
				p.append(ul);
			} else {
				App.UI.DOM.appendNewElement("div", p, `There are no rules that would apply to ${him}.`, "gray");
			}
		}
		array.push(
			App.UI.DOM.link(
				`Apply rules`,
				() => {
					DefaultRules(slave);
					refresh();
				}
			)
		);
		array.push(
			App.UI.DOM.link(
				`Exempt ${him}`,
				() => {
					slave.useRulesAssistant = 0;
					refresh();
				}
			)
		);
		array.push(
			App.UI.DOM.link(
				`Apply all rules to ${him} again`,
				() => {
					removeFromRulesToApplyOnce(slave);
					DefaultRules(slave);
					refresh();
				}
			)
		);
		array.push(App.UI.DOM.passageLink("Rules Assistant Options", "Rules Assistant"));
	}
	p.append(App.UI.DOM.generateLinksStrip(array));
	frag.append(p);

	p = document.createElement('p');
	App.UI.DOM.appendNewElement("h3", p, `Other Rules`);

	if (slave.fuckdoll > 0) {
		App.UI.DOM.appendNewElement("span", p, "Rules have little meaning for living sex toys", "note");
	} else {
		// Living
		penthouseCensus();
		p.append("Living standard: ");

		if (App.Data.misc.facilityCareers.includes(slave.assignment)) {
			App.UI.DOM.appendNewElement("span", p, ` ${His} living conditions are managed by ${his} assignment.`, ["note"]);
		} else if ((slave.assignment === Job.HEADGIRL) && (V.HGSuite === 1)) {
			App.UI.DOM.appendNewElement("span", p, ` ${He} has ${his} own little luxurious room in the penthouse with everything ${he} needs to be a proper Head Girl.`, ["note"]);
		} else if ((slave.assignment === Job.BODYGUARD) && (V.dojo > 1)) {
			App.UI.DOM.appendNewElement("span", p, ` ${He} has a comfortable room in the armory to call ${his} own.`, ["note"]);
		} else {
			choices = [
				{value: "spare"},
				{value: "normal"},
			];
			if (canMoveToRoom(slave) || slave.rules.living === "luxurious") {
				choices.push({value: "luxurious"});
			} else {
				choices.push({
					value: "luxurious",
					disabled: ["No luxurious rooms available"]
				});
			}

			p.append(listChoices(choices, "living"));
		}

		// Rest
		div = document.createElement("div");
		div.append("Sleep rules: ");
		if ([Job.NURSE, Job.HEADGIRL, Job.TEACHER, Job.STEWARD, Job.MATRON, Job.FARMER, Job.MADAM, Job.WARDEN, Job.DJ, Job.MILKMAID].includes(slave.assignment)) {
			App.UI.DOM.appendNewElement("span", div, ` ${His} sleeping schedule is managed by ${his} assignment.`, "note");
		} else if ([Job.GLORYHOLE, Job.FARMYARD, Job.CLUB, Job.PUBLIC, Job.WHORE, Job.BROTHEL, Job.SUBORDINATE, Job.HOUSE, Job.QUARTER, Job.FUCKTOY, Job.MASTERSUITE, Job.MILKED].includes(slave.assignment) || (V.dairyRestraintsSetting < 2 && slave.assignment === Job.DAIRY)) {
			choices = [
				{value: "none", tooltip: "Never"},
				{value: "cruel", tooltip: "When Exhausted"},
				{value: "restrictive", tooltip: "When Fatigued"},
				{value: "permissive", tooltip: "When Tired"},
				{value: "mandatory", tooltip: "Every Week"},
			];
			div.append(listChoices(choices, "rest"));
		} else {
			App.UI.DOM.appendNewElement("span", div, ` ${His} assignment does not allow setting a sleeping schedule.`, ["note"]);
		}
		p.append(div);

		// Mobility Aids
		if (!canWalk(slave) && canMove(slave)) {
			div = document.createElement("div");
			div.append("Use of mobility aids: ");
			choices = [
				{value: "restrictive"},
				{value: "permissive"},
			];
			div.append(listChoices(choices, "mobility"));
			p.append(div);
		}

		// Punishment
		div = document.createElement("div");
		div.append("Typical punishment: ");
		choices = [
			{value: "confinement", tooltip: App.UI.DOM.combineNodes(`Increases `, App.UI.DOM.makeElement("span", `fear`, ["trust", "dec"]), ` when the slave is punished.`)},
			{value: "whipping", tooltip: App.UI.DOM.combineNodes(`Significantly increases `, App.UI.DOM.makeElement("span", `fear`, ["trust", "dec"]), ` at the cost of `, App.UI.DOM.makeElement("span", `health`, ["health", "dec"]), ` when the slave is punished.`)},
			{value: "chastity", tooltip: App.UI.DOM.combineNodes(`Increases both `, App.UI.DOM.makeElement("span", `fear`, ["trust", "dec"]), ` and `, App.UI.DOM.makeElement("span", `devotion`, ["devotion", "inc"]), ` at the cost of `, App.UI.DOM.makeElement("span", `libido`, ["libido", "dec"]), ` when the slave is punished.`)},
			{value: "situational", tooltip: App.UI.DOM.combineNodes(`Increases `, App.UI.DOM.makeElement("span", `fear`, ["trust", "dec"]), ` when the slave is punished.`)},
		];
		div.append(listChoices(choices, "punishment"));
		p.append(div);

		// Reward
		div = document.createElement("div");
		div.append("Typical reward: ");
		choices = [
			{value: "relaxation", tooltip: App.UI.DOM.combineNodes(`Improves `, App.UI.DOM.makeElement("span", `health`, ["health", "inc"]), ` when a reward is earned.`)},
			{value: "drugs", tooltip: App.UI.DOM.combineNodes(`Significantly improves `, App.UI.DOM.makeElement("span", `devotion`, ["devotion", "inc"]), ` at the cost of `, App.UI.DOM.makeElement("span", `health`, ["health", "dec"]), ` when a reward is earned.`)},
			{value: "orgasm", tooltip: App.UI.DOM.combineNodes(`Improves `, App.UI.DOM.makeElement("span", `devotion`, ["devotion", "inc"]), ` and raises `, App.UI.DOM.makeElement("span", `libido`, ["libido", "inc"]), ` when a reward is earned.`)},
			{value: "situational", tooltip: App.UI.DOM.combineNodes(`Improves `, App.UI.DOM.makeElement("span", `devotion`, ["devotion", "inc"]), ` when a reward is earned.`)},
		];
		div.append(listChoices(choices, "reward"));
		p.append(div);

		// Lactation
		if (slave.lactation !== 2) {
			div = document.createElement("div");
			div.append("Lactation maintenance: ");
			choices = [
				{
					title: "Left alone",
					value: "none",
				},
			];

			if (slave.lactation === 0) {
				choices.push(
					{
						title: "Induce lactation",
						value: "induce",
					}
				);
			} else {
				choices.push(
					{
						title: "Maintain lactation",
						value: "maintain",
					}
				);
			}
			div.append(listChoices(choices, "lactation"));
			p.append(div);
		}

		p.append(orgasm(slave));

		if (slave.voice !== 0) {
			div = document.createElement("div");
			div.append("Speech rules: ");
			choices = [
				{value: "restrictive"},
				{value: "permissive"},
			];
			if (slave.accent > 3) {
				choices.push({value: "language lessons"});
			} else if (slave.accent > 0) {
				choices.push({value: "accent elimination"});
			}
			div.append(listChoices(choices, "speech"));
			p.append(div);
		}

		div = document.createElement("div");
		div.append("Relationship rules: ");
		choices = [
			{value: "restrictive"},
			{value: "just friends"},
			{value: "permissive"},
		];
		div.append(listChoices(choices, "relationship"));
		p.append(div);

		p.append(smartSettings(slave));
	}
	frag.append(p);
	return frag;

	/**
	 * @typedef {object} listChoice
	 * @property {string} value
	 * @property {string} [title]
	 * @property {string|HTMLElement|DocumentFragment} [tooltip]
	 * @property {Array<string>} [disabled]
	 */

	/**
	 * @param {Array<listChoice>} choices
	 * @param {string} property
	 * @returns {HTMLUListElement}
	 */
	function listChoices(choices, property) {
		const links = [];
		for (const c of choices) {
			const title = c.title || capFirstChar(c.value);
			if (c.disabled) {
				links.push(
					App.UI.DOM.disabledLink(
						title, c.disabled
					)
				);
			} else if (slave.rules[property] === c.value) {
				links.push(
					App.UI.DOM.disabledLink(
						title,
						c.tooltip ? ["Current Setting", c.tooltip] : ["Current Setting"]
					)
				);
			} else {
				links.push(
					App.UI.DOM.link(
						title,
						() => {
							slave.rules[property] = c.value;
							refresh();
						},
						[],
						"",
						c.tooltip ? c.tooltip : ""
					)
				);
			}
		}
		return App.UI.DOM.generateLinksStrip(links);
	}

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

		const title = document.createElement('div');
		title.textContent = `Non-assignment orgasm rules: `;
		el.append(title);

		const choices = [
			{
				title: "Masturbation",
				value: "masturbation",
				enabled: `Will be allowed to pleasure ${himself}`,
				disabled: `Will not be allowed to pleasure ${himself}`,
			},
			{
				title: "Partner",
				value: "partner",
				enabled: `Will be allowed sexual contact with ${his} romantic partner`,
				disabled: `Will not be allowed sexual contact with ${his} romantic partner`,
			},
			{
				title: "Facility leaders",
				value: "facilityLeader",
				enabled: `Development facility leaders are allowed to satisfy ${his} sexual needs while ${he} is assigned to their facility`,
				disabled: `Development facility leaders are not allowed to satisfy ${his} sexual needs while ${he} is assigned to their facility`,
			},
			{
				title: "Family",
				value: "family",
				enabled: `Will be allowed sexual contact with close family members`,
				disabled: `Will not be allowed sexual contact with close family members`,
			},
			{
				title: "Other slaves",
				value: "slaves",
				enabled: `Will be allowed sexual contact with other slaves`,
				disabled: `Will not be allowed sexual contact with other slaves`,
			},
			{
				title: "Master",
				value: "master",
				enabled: `You will fuck ${him} when ${he} needs it`,
				disabled: `You will not fuck ${him} when ${he} needs it`,
				master: true
			},
		];

		for (const orgasmObj of choices) {
			const row = document.createElement("div");
			row.classList.add("choices");
			row.append(`${orgasmObj.title}: `);
			App.UI.DOM.appendNewElement("span", row, makeLinks(orgasmObj));
			el.append(row);
		}

		return el;

		function makeLinks(orgasmObj) {
			const linkArray = [];
			makeLinks();

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

			function makeLinks() {
				const allow = orgasmObj.master ? `Grant` : `Allow`;
				const forbid = orgasmObj.master ? `Deny` : `Forbid`;
				if (!slave.rules.release[orgasmObj.value]) {
					linkArray.push(App.UI.DOM.link(allow, () => {
						slave.rules.release[orgasmObj.value] = 1;
						refresh();
					}));
					linkArray.push(App.UI.DOM.disabledLink(forbid, [orgasmObj.disabled]));
				} else {
					linkArray.push(App.UI.DOM.disabledLink(allow, [orgasmObj.enabled]));
					linkArray.push(App.UI.DOM.link(forbid, () => {
						slave.rules.release[orgasmObj.value] = 0;
						refresh();
					}));
				}
			}
		}
	}

	function smartSettings(slave) {
		let el = document.createElement('div');
		const smartBulletVibe = dildoVibeLevel(slave) > 1 || slave.dickAccessory === "smart bullet vibrator";
		const smartDildoVibe = slave.vaginalAttachment === "smart vibrator";

		if (slave.piercing.genitals.smart || smartBulletVibe || smartDildoVibe) {
			const level = new Map();
			const bodyPart = new Map();
			const BDSM = new Map();
			const gender = new Map();

			// Level
			level.set(`No sex`, `none`);
			level.set(`All sex`, `all`);
			level.set(`Disabled`, `off`);

			// Body part
			bodyPart.set(`Vanilla`, `vanilla`);
			bodyPart.set(`Oral`, `oral`);
			bodyPart.set(`Anal`, `anal`);
			bodyPart.set(`Boobs`, `boobs`);
			if (V.seePreg !== 0) {
				bodyPart.set(`Preg`, `pregnancy`);
			}
			// BDSM
			BDSM.set(`Sub`, `submissive`);
			BDSM.set(`Dom`, `dom`);
			BDSM.set(`Masochism`, `masochist`);
			BDSM.set(`Sadism`, `sadist`);
			BDSM.set(`Humiliation`, `humiliation`);

			// Gender
			gender.set(`Men`, `men`);
			gender.set(`Women`, `women`);
			gender.set(`Anti-men`, `anti-men`);
			gender.set(`Anti-women`, `anti-women`);
			let label = null;
			if (slave.piercing.genitals.smart) {
				label = `Smart ${slave.dick < 1 ? "clit" : "frenulum"} piercing `;
				label += (smartBulletVibe || smartDildoVibe) ? `and smart vibrator setting: ` : `setting: `;
			} else if (smartBulletVibe) {
				label = `Smart bullet vibrator setting: `;
			} else if (smartDildoVibe) {
				label = `Smart vibrating dildo attachment setting: `;
			}
			let title = App.UI.DOM.appendNewElement('div', el, label);
			let selected = App.UI.DOM.appendNewElement('span', title, `${slave.clitSetting}. `);
			selected.style.fontWeight = "bold";

			choices("Level", level);
			choices("Body part", bodyPart);
			choices("BDSM", BDSM);
			choices("Gender", gender);
		}

		return el;

		function choices(title, map) {
			const row = document.createElement("div");
			row.classList.add("choices");
			row.append(`${title}: `);

			const linkArray = [];
			for (const [text, value] of map) {
				if (slave.clitSetting === value) {
					linkArray.push(
						App.UI.DOM.disabledLink(
							text,
							["Currently selected"]
						)
					);
				} else {
					linkArray.push(
						App.UI.DOM.link(
							text,
							() => {
								slave.clitSetting = value;
								refresh();
							}
						)
					);
				}
			}
			row.append(App.UI.DOM.generateLinksStrip(linkArray));
			el.append(row);
		}
	}
};