code:
/**
 * @param {App.Entity.SlaveState} slave
 * @returns {Record<string, string>}
 */
App.Medicine.Modification.brandRecord = function(slave) {
	const scars = Object.assign({}, slave.brand);
	Object.assign(scars, slave.leg.left?.brand);
	Object.assign(scars, slave.leg.right?.brand);
	Object.assign(scars, slave.arm.left?.brand);
	Object.assign(scars, slave.arm.right?.brand);
	return scars;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @returns {{brand:Record<string, string>, scar: Record<string, Partial<App.Entity.ScarState>>}}
 */
App.Medicine.Modification.modifiableBodyPart = function(slave, location) {
	const bodyPartRoot = App.UI.bodyPartRoot(location);
	const bodyPartData = App.Data.Slave.body.get(bodyPartRoot);

	/** @type {{brand:Record<string, string>, scar: Record<string, Partial<App.Entity.ScarState>>}} */
	let object = slave;

	// if undefined: custom location
	if (bodyPartData !== undefined) {
		// select the correct scar object based on location
		if (bodyPartData.category === "legs") {
			if (location.startsWith("left")) {
				if (getLeftLegID(slave) === 1) {
					object = slave.leg.left;
				} else {
					return undefined;
				}
			} else {
				if (getRightLegID(slave) === 1) {
					object = slave.leg.right;
				} else {
					return undefined;
				}
			}
		} else if (bodyPartData.category === "arms") {
			if (location.startsWith("left")) {
				if (getLeftArmID(slave) === 1) {
					object = slave.arm.left;
				} else {
					return undefined;
				}
			} else {
				if (getRightArmID(slave) === 1) {
					object = slave.arm.right;
				} else {
					return undefined;
				}
			}
		}
	}

	return object;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @returns {Record<string, string>} If undefined, the location does not exist or can't be branded.
 */
App.Medicine.Modification.brandObject = function(slave, location) {
	return App.Medicine.Modification.modifiableBodyPart(slave, location)?.brand;
};

/**
 * Creates a new brand on a slaves body at a given location. Does nothing, if the location (for example an arm) does not
 * exist or can't be branded.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @param {string} design
 */
App.Medicine.Modification.addBrand = function(slave, location, design) {
	const scarObject = App.Medicine.Modification.brandObject(slave, location);
	if (scarObject === undefined) {
		// body part does not exist
		return;
	}
	scarObject[location] = design;
};

/**
 * Basic application of scar
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 */
App.Medicine.Modification.removeBrand = function(slave, location) {
	const brandObject = App.Medicine.Modification.brandObject(slave, location);
	if (brandObject === undefined) {
		// body part does not exist
		return;
	}

	delete brandObject[location];
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {Record<string, Partial<App.Entity.ScarState>>}
 */
App.Medicine.Modification.scarRecord = function(slave) {
	const scars = Object.assign({}, slave.scar);
	Object.assign(scars, slave.leg.left?.scar);
	Object.assign(scars, slave.leg.right?.scar);
	Object.assign(scars, slave.arm.left?.scar);
	Object.assign(scars, slave.arm.right?.scar);
	return scars;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @returns {Record<string, Partial<App.Entity.ScarState>>} If undefined, the location does not exist or can't be scarred.
 */
App.Medicine.Modification.scarObject = function(slave, location) {
	return App.Medicine.Modification.modifiableBodyPart(slave, location)?.scar;
};

/**
 * Creates a new scar on a slaves body at a given location. Does nothing, if the location (for example an arm) does not
 * exist or can't be scarred.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @param {keyof App.Entity.ScarState|string} design
 * @param {number} [weight=1]
 */
App.Medicine.Modification.addScar = function(slave, location, design, weight = 1) {
	/*
	V.scarApplied = 1;
	surgeryDamage(slave, 10); // dangerous to uncomment this as sometimes many scars are applied at once.
	cashX(forceNeg(surgery.costs), "slaveSurgery", slave);
	surgeryDamage(slave, (V.PC.skill.medicine >= 100) ? Math.round(surgery.healthCosts / 2) : surgery.healthCosts);*/

	const scarObject = App.Medicine.Modification.scarObject(slave, location);
	if (scarObject === undefined) {
		// body part does not exist
		return;
	}

	if (!scarObject.hasOwnProperty(location)) {
		scarObject[location] = new App.Entity.ScarState();
	}
	if (scarObject[location].hasOwnProperty(design)) {
		scarObject[location][design] += weight;
	} else {
		scarObject[location][design] = weight;
	}
};

/**
 * Remove all scars from a given location
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 */
App.Medicine.Modification.removeAllScars = function(slave, location) {
	const scarObject = App.Medicine.Modification.scarObject(slave, location);
	if (scarObject === undefined) {
		// body part does not exist
		return;
	}

	delete scarObject[location];
};

/**
 * Basic application of scar
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @param {keyof App.Entity.ScarState|string} design
 */
App.Medicine.Modification.removeScar = function(slave, location, design) {
	const scarObject = App.Medicine.Modification.scarObject(slave, location);
	if (scarObject === undefined) {
		// body part does not exist
		return;
	}

	if (scarObject.hasOwnProperty(location)) { // if scar object exists for this body part
		if (scarObject[location].hasOwnProperty(design)) { // if object has this kind of scar (might be custom)
			if (["burn", "c-section", "chain", "cutting", "exotic", "generic", "menacing", "surgical", "whip"].includes(design)) {
				scarObject[location][design] = 0;
			} else {
				delete scarObject[location][design]; // scar was custom
			}
		}
		// remove the scar object entirely if no entry is scarred:
		let weights = Object.values(scarObject[location]);
		let total = 0;
		let i;
		for (i = 0; i < weights.length; i++) {
			total += weights[i];
		}
		if (total === 0) {
			delete scarObject[location];
		}
	}
};

/**
 * Slave is whipped over the entire body, and strains in manacles so much that the wrists and ankles scar if present.
 * @param {App.Entity.SlaveState} slave
 * @param {number} [weight]
 */
App.Medicine.Modification.addScourged = function(slave, weight) {
	let scarArray = ["left breast", "right breast", "back", "lower back", "left buttock", "right buttock"];
	// Whip
	if (getLeftArmID(slave) === 1) {
		scarArray.push("left upper arm");
	}
	if (getRightArmID(slave) === 1) {
		scarArray.push("right upper arm");
	}
	if (getLeftLegID(slave) === 1) {
		scarArray.push("left thigh");
	}
	if (getRightLegID(slave) === 1) {
		scarArray.push("right thigh");
	}

	for (let i = 0; i < scarArray.length; i++) {
		App.Medicine.Modification.addScar(slave, scarArray[i], "whip", weight);
	}
	// Manacles
	scarArray = [];
	if (getLeftArmID(slave) === 1) {
		scarArray.push("left wrist");
	}
	if (getRightArmID(slave) === 1) {
		scarArray.push("right wrist");
	}
	if (getLeftLegID(slave) === 1) {
		scarArray.push("left ankle");
	}
	if (getRightLegID(slave) === 1) {
		scarArray.push("right ankle");
	}
	for (let i = 0; i < scarArray.length; i++) {
		App.Medicine.Modification.addScar(slave, scarArray[i], "chain", weight);
	}
};

/**
 * Scars a slave over a large section of their body.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location full, upper, lower, left or right
 * @param {keyof App.Entity.ScarState} type whip, burn, surgical, generic
 * @param {number} weight
 */
App.Medicine.Modification.addBulkScars = function(slave, location, type, weight) {
	let scarArray = [];

	/* Divide slave into quarters, and add each quarter as needed. */

	/* Top left */
	if (["full", "left", "upper"].includes(location)) {
		scarArray.push("left breast");
		if (getLeftArmID(slave) === 1) {
			scarArray.push("left upper arm", "left lower arm", "left hand");
		}
	}

	/* Top right */
	if (["full", "right", "upper"].includes(location)) {
		scarArray.push("right breast");
		if (getRightArmID(slave) === 1) {
			scarArray.push("right upper arm", "right lower arm", "right hand");
		}
	}

	/* Lower left */
	if (["full", "left", "lower"].includes(location)) {
		scarArray.push("left buttock");
		if (getLeftLegID(slave) === 1) {
			scarArray.push("left thigh", "left calf", "left foot");
		}
	}

	/* Lower Right */
	if (["full", "lower", "right"].includes(location)) {
		scarArray.push("right buttock");
		if (getRightLegID(slave) === 1) {
			scarArray.push("right thigh", "right calf", "right foot");
		}
	}

	/* Extra */
	if (["full", "upper"].includes(location)) {
		scarArray.push("back", "lower back");
	}

	for (let i = 0; i < scarArray.length; i++) {
		App.Medicine.Modification.addScar(slave, scarArray[i], type, weight);
	}
};

/**
 * Adds a piercing to a slave.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @param {number} weight
 * @returns {string} slave reaction
 */
App.Medicine.Modification.setPiercing = function(slave, location, weight) {
	// reaction
	const {
		He, His,
		he, his, him
	} = getPronouns(slave);
	const delta = weight - slave.piercing[location].weight;
	let r = [];
	if (location === "tongue") {
		if (slave.fetishKnown && slave.fetish === "cumslut" && slave.fetishStrength > 10) {
			r.push(`Since ${he}'s an oral whore, ${he}'s`);
			if (delta > 0) {
				if (weight > 1) {
					r.push(`<span class="devotion inc">very happy</span> you took the time to personally pierce ${his} tongue so heavily.`);
				} else {
					r.push(`<span class="devotion inc">happy</span> to get a tongue piercing from you personally.`);
				}
				r.push(`After all, ${he}'s pretty sure it's a sign there will be plenty of ${V.PC.dick > 0 ? `dick for ${him} to suck` : `pussy for ${him} to eat`} in the future.`);
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r.push(`<span class="devotion dec">saddened</span> you decided to take out ${his} tongue piercings. ${He}'s afraid this means something about ${his} future as a suck slut.`);
				slave.devotion += 2 * delta;
			} else {
				r.push(`disappointed to lose ${his} tongue piercings, but accepts your will for ${his} body.`);
			}
		}
		if (slave.sexualFlaw === "hates oral" && weight > 0) {
			r.push(`${He} has trouble thinking of ${his} mouth as a warm, wet, playful hole, so ${his} new piercings will be therapeutic. Having something to suck on at all times should force ${him} past ${his} hang-ups.`);
		}
	} else if (location === "nipples" || location === "areolae") {
		if (slave.fetishKnown && slave.fetish === "boobs" && slave.fetishStrength > 10) {
			r.push(`Since ${he}'s fixated on ${his} boobs, ${he}'s`);
			if (delta > 0) {
				if (weight > 1) {
					r.push(`<span class="devotion inc">very happy</span> you took the time to personally pierce them so heavily.`);
				} else {
					r.push(`<span class="devotion inc">happy</span> to get them pierced by you personally.`);
				}
				r.push(`As far as ${he}'s concerned, more attention on ${his} tits is always good.`);
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r.push(`<span class="devotion dec">saddened</span> you decided to take out ${his} piercings. ${He} doesn't like anything that makes ${his} tits less distinctive.`);
				slave.devotion += 2 * delta;
			} else {
				r.push(`disappointed to lose ${his} the piercings in ${his} ${location}, but accepts your will for ${his} body.`);
			}
		}
	} else if (location === "corset") {
		if (slave.fetishKnown && slave.fetish === "masochist" && slave.fetishStrength > 10) {
			r.push(`Since ${he}'s a pain whore`);
			if (delta > 0) {
				r.push(`${he} <span class="devotion inc">got off</span> on you giving ${him} a corset piercing personally. ${He} loved the feeling of the metal entering ${his} flesh.`);
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r.push(`<span class="devotion dec">saddened</span> you decided to take out ${his} corset piercings. ${He}'ll miss the constant slight discomfort they gave ${him}.`);
				slave.devotion += 2 * delta;
			} else {
				r.push(`disappointed to lose ${his} corset piercings, but accepts your will for ${his} body.`);
			}
		}
	} else if (location === "vagina") {
		if (slave.fetishKnown && slave.fetish === "pregnancy" && slave.fetishStrength > 10) {
			r.push(`Since ${he}'s a whore for impregnation, ${he}'s`);
			if (delta > 0) {
				if (weight > 1) {
					r.push(`<span class="devotion inc">very happy</span> you took the time to personally pierce ${his} pussylips so heavily.`);
				} else {
					r.push(`<span class="devotion inc">happy</span> to get pussy piercings from you personally.`);
				}
				r.push(`Like most pregnancy fetishists, ${he}'s a little desperate for dick, and anything that points towards more bareback sex is appealing to ${him}.`);
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r.push(`saddened you decided to take out ${his} pussy piercings. Like most pregnancy fetishists, ${he}'s a little desperate for dick, and ${he}'s afraid of anything that points towards less attention focused on ${his} cunt.`);
				slave.devotion += 2 * delta;
			} else {
				r.push(`disappointed to lose ${his} pussy piercings, but accepts your will for ${his} body.`);
			}
		}
		if (slave.sexualFlaw === "hates penetration" && weight > 0) {
			r.push(`${He} has trouble thinking of ${his} vagina as a fuckhole rather than something special to protect and cherish, so ${his} new piercings will be therapeutic. The constant, inescapable stimulation of ${his} labia should force ${him} past ${his} hang-ups.`);
		}
	} else if (location === "dick") {
		if (slave.fetishKnown && slave.fetish === "pregnancy" && slave.fetishStrength > 10) {
			r.push(`Since ${he}'s fixated on using ${his} cock to impregnate other slaves, ${he}'s`);
			if (delta > 0) {
				if (weight > 1) {
					r.push(`<span class="devotion inc">very happy</span> you took the time to personally stick so much metal into ${his} dick${(slave.scrotum > 0) ? ` and ballsack` : ``}.`);
				} else {
					r.push(`<span class="devotion inc">happy</span> to get penis piercings from you personally.`);
				}
				r.push(`${He}'s extremely proud of ${his} newly decorated member.`);
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r.push(`saddened you decided to take out ${his} dick piercings. ${He} passionately loves using ${his} penis, and ${he}'s concerned you're planning to let ${him} do less of that.`);
				slave.devotion += 2 * delta;
			} else {
				r.push(`disappointed to lose ${his} dick piercings, but accepts your will for ${his} body.`);
			}
		}
	} else if (location === "anus") {
		if (slave.fetishKnown && slave.fetish === "buttslut" && slave.fetishStrength > 10) {
			r.push(`Since ${he}'s an anal slut, ${he}'s`);
			if (delta > 0) {
				if (weight > 1) {
					r.push(`<span class="devotion inc">very happy</span> you took the time to personally pierce the entire area around ${his} whorish asspussy.`);
				} else {
					r.push(`<span class="devotion inc">happy</span> to get a piercing next to ${his} favorite hole from you personally.`);
				}
				r.push(`${He} can't wait to find out how the metal will feel against ${his} butthole when ${he} moves around.`);
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r.push(`saddened you decided to take out ${his} backdoor piercings. ${He} will miss the constant anal titillation, and ${he}'s worried this means you're becoming less interested in ${his} butthole.`);
				slave.devotion += 2 * delta;
			} else {
				r.push(`disappointed to lose ${his} backdoor piercings, but accepts your will for ${his} body.`);
			}
		}
		if (slave.sexualFlaw === "hates anal" && weight > 0) {
			r.push(`${He} has trouble thinking of ${his} asshole as a sexy, fuckable hole, so ${his} new piercings will be therapeutic. The constant tickling back there should force ${him} past ${his} hang-ups.`);
		}
	} else if (location === "genitals") {
		if (weight === 0) {
			slave.piercing.genitals.smart = false;
		}
	}

	if (slave.genes === "XY" && slave.attrXY <= 35 && ["ear", "eyebrow", "lips", "navel", "nose"].includes(location)) {
		r.push(`${His} girly new`);
		switch (location) {
			case "ear":
				r.push(`pierced ears should help ${him} a little with ${his} issues about ${his} self-image as a fuckable slave girl.`);
				break;
			case "lips":
				r.push(`facial piercing, just below ${his} pretty mouth, should help ${him} get used to the idea of it being a warm, wet hole for cocks.`);
				break;
			case "nose":
				r.push(`nose piercing should make ${his} reflection in the mirror seem just a bit more fuckably feminine to ${him}.`);
				break;
			case "navel":
				r.push(`navel piercing should help ${him} see ${his} naked body in a submissively sexual way, especially as ${he} turns to hide it and present ${his} asspussy.`);
				break;
			case "eyebrow":
				r.push(`facial piercing should help destroy ${his} vestiges of sexual identity outside of ${his} life as a hole for cocks.`);
				break;
			default:
				r.push(`set of slutty facial piercings should help ${him} get used to the idea of being a girly slave meant to please dicks.`); /* impossible, but I'm leaving the text anyway */
		}
		if (slave.devotion < -20) {
			r.push(`Unfortunately, that positive effect will have to wait until ${he}'s a little less resistant to the idea of being a sex slave.`);
		}
	}

	slave.piercing[location].weight = weight;
	return r.join(" ");
};

/**
 * Adds a tattoo to a slave.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @param {string|number} design (0 removes)
 * @param {boolean} cheat
 * @returns {string} slave reaction
 */
App.Medicine.Modification.setTattoo = function(slave, location, design, cheat) {
	if (!cheat) {
		cashX(forceNeg(V.modCost), "slaveMod", slave);
	}

	// reaction
	const {He, he, His, his, him} = getPronouns(slave);
	const {say, title: Master} = getEnunciation(slave);
	let r = [];
	if (location === "vagina" && design === "lewd crest") {
		if (canSee(slave)) {
			if (slave.fetish === "pregnancy") {
				r.push(`${He} smiles, pleased at the attention ${his} womb is getting.`);
			} else if (slave.fetish === "submissive") {
				r.push(`${He} flushes, understanding that ${he} has been marked for debauchery.`);
			} else if (slave.fetish === "humiliation") {
				r.push(`${He} smiles, pleased that ${he}'s been marked erotic.`);
			} else if (slave.devotion > 50) {
				r.push(`${He} smiles, pleased at the mark of your attention to ${his} body.`);
				r.push(Spoken(slave, `"Thank you, ${Master},"`));
				r.push(`${he} ${say}s devotedly.`);
				r.push(Spoken(slave, `"I love you and I'll serve you with my body as best I can."`));
			} else if (slave.devotion > 20) {
				r.push(`${He} stares, torn between admiration at the intricate design and realization that ${his} abdomen has been permanently tattooed.`);
				r.push(Spoken(slave, `"Thank you, ${Master},"`));
				r.push(`${he} ${say}s, taking refuge in propriety.`);
				r.push(Spoken(slave, `"Um, d-does this mean I'm even more of a, um, s-slut now?"`));
				r.push(`${he} asks hesitantly, not sounding very enthusiastic.`);
			} else if (slave.trust < -20) {
				r.push(`${He} stares, looking sad as the meaning of a permanent display of ${his} status as lewd slut sinks in. After a short pause ${he} remembers ${his} duties and stammers,`);
				r.push(Spoken(slave, `"T-thank you, ${Master}."`));
				r.push(`After a while longer ${he} turns ${his} head away, clearly not wanting to dwell on how ${his} body is being branded to attract sexual attention.`);
			} else {
				r.push(`${He} stares, horrified at the lewd crest that now adorns ${his} lower belly.`);
			}
		}
	}
	if (location === "anus" && design !== 0) {
		if (canSee(slave) && canTalk(slave)) {
			const anus = (function(s) {
				if (s.anus > 3) {
					return "anal gape";
				} else if (s.anus === 3) {
					return "relaxed asspussy";
				} else if (s.anus === 2) {
					return "soft butthole";
				} else {
					return "tight butthole";
				}
			})(slave);

			if (design === "bleached") {
				r.push(`${He} knew ${he} was getting ${his} ass bleached, of course. The stinging sensation was hard to miss. Even so, ${he} cranes ${his} neck around as soon as possible to look at ${his} ${anus} in the mirror.`);
			} else {
				r.push(`${He} knew ${he} was getting ${his} asshole tattooed, of course. ${He} cranes ${his} neck around as soon as possible to look at ${his} ${anus} in the mirror.`);
				if (slave.butt > 5) {
					r.push(`${His} huge buttocks are still held apart by the spreaders used to give the tattooing actuators access to ${his} most intimate area, giving ${him} a good view.`);
				}
			}
			if (slave.fetishKnown && slave.fetish === "buttslut") {
				switch (design) {
					case "bleached":
						r.push(`${He} giggles at ${his} crinkled fuckhole's clean, fuckable appearance, and starts squeezing ${his} sphincter muscles experimentally.`);
						r.push(Spoken(slave, `"Thank you, ${Master},"`));
						r.push(`${he} gasps, flushing.`);
						break;
					case "flowers":
					case "tribal patterns":
						r.push(`${He} gasps at the beautiful design and then suddenly bursts into tears.`);
						r.push(Spoken(slave, `"T-thank you, ${Master},"`));
						r.push(`${he} blubbers.`);
						r.push(Spoken(slave, `"I'm s-such an anal whore."`));
						break;
					case "advertisements":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out`);
						if (slave.vagina > -1) {
							r.push(`'Fucktoy's Favorite Fuckhole.' ${He} smiles naughtily.`);
							r.push(Spoken(slave, `"${Master}, it sure is,"`));
						} else {
							r.push(`'Spit First.' ${He} smiles naughtily.`);
							r.push(Spoken(slave, `"${Master}, that works for me. Lube's good too. Or, um, dry's fine too,"`));
						}
						r.push(`${he} giggles.`);
						break;
					case "rude words":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out`);
						if (slave.vagina > -1) {
							r.push(`'Fucktoy's Favorite Fuckhole.' ${He} smiles naughtily.`);
							r.push(Spoken(slave, `"${Master}, it sure is,"`));
						} else {
							r.push(`'Sissy Slut's Sodomy Slit.' ${He} smiles naughtily.`);
							r.push(Spoken(slave, `"${Master}, I love it when you fuck my lewd butt slit,"`));
						}
						r.push(`${he} giggles.`);
						break;
					case "sacrilege":
						r.push(`${He} stares, ${his} mouth working as ${he} reads the ring of Aramaic blasphemy around ${his} hole. Getting it, ${he} smiles naughtily.`);
						r.push(Spoken(slave, `"${Master}, I'm a horrible little butthole succubus. Please, ${Master}, feed me. I need cum in my ass to survive."`));
						break;
					case "sacrament":
						r.push(`${He} stares, ${his} mouth working as ${he} reads the ring of Aramaic around ${his} hole. Getting it, ${he} smiles beatifically.`);
						r.push(Spoken(slave, `"${Master}, I always knew buttsex was a sacrament."`));
						break;
					case "degradation":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out '${getWrittenTitle(slave)} Made Me An Anal Slut.' ${He} smiles naughtily.`);
						r.push(Spoken(slave, `"Oh ${Master}, you did,"`));
						r.push(`${he} ${say}s gratefully.`);
						r.push(Spoken(slave, `"And I love you for it."`));
						break;
					case "counting":
						r.push(`${He} stares, realizing that the markings denote how many times ${he}'s been sodomized.`);
						r.push(Spoken(slave, `"${Master}, thank you, and, and, I hope I'll have to come back in here a lot,"`));
						r.push(`${he} ${say}s.`);
						r.push(Spoken(slave, `"Um, because I have to have that updated. Because I'm being fucked in the butt a lot,"`));
						r.push(`${he} adds, wanting to be completely clear.`);
						break;
					case "bovine patterns":
						r.push(`${He} stares at ${his} new cow's anus. Finally, ${he} focuses on the upsides of being permanently transformed like this.`);
						r.push(Spoken(slave, `"${Master}, I hope this means I'll have to be fed up the butt a lot,"`));
						r.push(`${he} ${say}s, referencing the milking systems' anal feeders.`);
						break;
					case "possessive":
						r.push(`${He} stares, realizing that the ring of numbers around ${his} anus give the date ${he} was enslaved by you.`);
						r.push(Spoken(slave, `"Best place for that date, ${Master},"`));
						r.push(`${he} ${say}s.`);
						r.push(Spoken(slave, `"Being a butt slut is my life now."`));
						break;
					case "paternalist":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out '${getWrittenTitle(slave)} Helped Me Love Anal.'`);
						r.push(Spoken(slave, `"Oh ${Master}, you did,"`));
						r.push(`${he} coos.`);
						r.push(Spoken(slave, `"You did! Thank you so much for helping me become the anal slut I was always meant to be!"`));
						break;
					default:
						r.push(`${He} smiles, pleased at the attention ${his} ${anus} is getting.`);
				}
			} else if (slave.devotion > 50) {
				switch (design) {
					case "bleached":
						r.push(`${He} gasps at how closely the crinkled skin around ${his} anal opening matches the rest of ${his} ${slave.skin} body.`);
						r.push(Spoken(slave, `"Oh, it's so beautiful! Thank you, ${Master}, thank you,"`));
						r.push(`${he} exclaims lovingly.`);
						break;
					case "flowers":
					case "tribal patterns":
						r.push(`${He} gasps at the beautiful design.`);
						r.push(Spoken(slave, `"It's so pretty, ${Master},"`));
						r.push(`${he} ${say}s.`);
						r.push(Spoken(slave, `"Thank you. I love you so much!"`));
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole.`);
						r.push(Spoken(slave, `"Thank you, ${Master},"`));
						r.push(`${he} ${say}s devotedly.`);
						r.push(Spoken(slave, `"I love you and I'll serve you with my butthole as best I can."`));
						break;
					default:
						r.push(`${He} smiles, pleased at the mark of your attention to ${his} body.`);
						r.push(Spoken(slave, `"Thank you, ${Master},"`));
						r.push(`${he} ${say}s devotedly.`);
						r.push(Spoken(slave, `"I love you and I'll serve you with my butthole as best I can."`));
				}
			} else if (slave.sexualFlaw === "hates anal") {
				r.push(`${He} stares,`);
				switch (design) {
					case "bleached":
						r.push(`horrified, and then shudders convulsively, but can't look away.`);
						break;
					case "flowers":
					case "tribal patterns":
						r.push(`torn between fascination at the intricate design and horror at having ${his} anus permanently tattooed.`);
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r.push(`${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole. ${He} starts to cry as their meaning sinks in.`);
						break;
					default:
						r.push(`lower lip quivering.`);
				}
				r.push(`${He} hates being assraped, and knows that this means there's lots of it in store for ${him}.`);
				r.push(Spoken(slave, `"${Master}, p-please,"`));
				r.push(`${he} moans halfheartedly.`);
				r.push(Spoken(slave, `"I d-don't like being b-butt f-fucked."`));
			} else if (slave.devotion > 20) {
				r.push(`${He} stares,`);
				switch (design) {
					case "bleached":
						r.push(`fascinated. ${He} doesn't seem sure what to make of this.`);
						break;
					case "flowers":
					case "tribal patterns":
						r.push(`torn between admiration at the intricate design and realization that ${his} anus has been permanently tattooed.`);
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r.push(`${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole. ${He} shudders a little as they sink in and ${he} internalizes what they say about ${him} and ${his} asshole.`);
						break;
					default:
						r.push(`realizing that ${his} anus has been permanently tattooed. It's yet another proof that ${he}'s permanently a sex slave, and ${his} anus is for fucking.`);
				}
				r.push(Spoken(slave, `"Thank you, ${Master},"`));
				r.push(`${he} ${say}s, taking refuge in propriety.`);
				r.push(Spoken(slave, `"Um, d-does this mean I'm going to be more of a, um, b-buttslut?"`));
				r.push(`${he} asks hesitantly, not sounding very enthusiastic.`);
			} else if (slave.trust < -20) {
				r.push(`${He} stares,`);
				switch (design) {
					case "bleached":
						r.push(`trying to process the appearance of what must seem like an unnaturally clean-looking hole.`);
						break;
					case "flowers":
					case "tribal patterns":
						r.push(`struggling to process the fact that one of ${his} most intimate areas has been tattooed. Beautifully tattooed, but still."`);
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r.push(`${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole. ${He} shudders as ${he} does ${his} best to conceal ${his} feelings about such a permanent display of ${his} status as an anal slave.`);
						break;
					default:
						r.push(`looking sad as the meaning of a permanent display of ${his} status as an anal slave sinks in.`);
				}
				r.push(`After a short pause ${he} remembers ${his} duties and stammers,`);
				r.push(Spoken(slave, `"T-thank you, ${Master}."`));
				r.push(`After a while longer ${he} turns ${his} head away, clearly not wanting to dwell on how ${his} anus is obviously being beautified to attract sexual attention.`);
			} else {
				switch (design) {
					case "bleached":
						r.push(`${His} face darkens as ${he} sees ${his} crinkled fuckhole's clean, fuckable appearance.`);
						r.push(Spoken(slave, `"Don't you dare fucking assrape me!"`));
						r.push(`${he} bursts out.`);
						break;
					case "flowers":
					case "tribal patterns":
						r.push(`${He}'s momentarily surprised by the pretty design, but then ${his} face darkens.`);
						r.push(Spoken(slave, `"That's permanent!"`));
						r.push(`${he} bursts out.`);
						r.push(Spoken(slave, `"My asshole is going to look like that forever!"`));
						break;
					case "advertisements":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out`);
						if (slave.vagina > -1) {
							r.push(`'Second Pussy.' ${His} face darkens.`);
							r.push(Spoken(slave, `"No it's not,"`));
							r.push(`${he} bursts out.`);
							r.push(Spoken(slave, `"Anal is dirty."`));
						} else {
							r.push(`'Spit First.' ${His} face darkens.`);
							r.push(Spoken(slave, `"Gross,"`));
							r.push(`${he} bursts out.`);
							r.push(Spoken(slave, `"Gross and wrong!"`));
						}
						break;
					case "rude words":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out`);
						if (slave.vagina > -1) {
							r.push(`'Fucktoy's Favorite Fuckhole.' ${His} face darkens.`);
							r.push(Spoken(slave, `"No it's not,"`));
							r.push(`${he} bursts out.`);
							r.push(Spoken(slave, `"It's my ass!"`));
						} else {
							r.push(`'Sissy Slut's Sodomy Slit.' ${His} face darkens.`);
							r.push(Spoken(slave, `"No it's not,"`));
							r.push(`${he} bursts out.`);
							r.push(Spoken(slave, `"I'm not a sissy! Or a slut! And it's not a slit!"`));
							r.push(`Those are debatable, but they can all change, of course.`);
						}
						break;
					case "sacrament":
					case "sacrilege":
						r.push(`${He} stares, ${his} mouth working as ${he} reads the ring of Aramaic around ${his} hole. ${He} doesn't understand it, but ${he}'s not happy about having stuff permanently inscribed around ${his} ass.`);
						break;
					case "degradation":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out '${getWrittenTitle(slave)} Made Me An Anal Slut.' ${His} face darkens.`);
						r.push(Spoken(slave, `"No I'm not,"`));
						r.push(`${he} bursts out. That remains to be seen.`);
						break;
					case "counting":
						r.push(`${He} stares, realizing that the markings denote how many times ${he}'s been sodomized. ${His} face darkens.`);
						r.push(Spoken(slave, `"I see there's room for more,"`));
						r.push(`he bursts out.`);
						r.push(Spoken(slave, `"Fucking gross."`));
						break;
					case "bovine patterns":
						r.push(`${He} stares at ${his} new cow's anus.`);
						r.push(Spoken(slave, `"Fucking gross,"`));
						r.push(`${he} bursts out.`);
						r.push(Spoken(slave, `"I'm not an animal."`));
						r.push(`That remains to be seen.`);
						break;
					case "possessive":
						r.push(`${He} stares, realizing that the ring of numbers around ${his} anus give the date ${he} was enslaved by you. ${His} face darkens.`);
						r.push(Spoken(slave, `"Worst day of my life,"`));
						r.push(`${he} bursts out.`);
						break;
					case "paternalist":
						r.push(`${He} stares, ${his} mouth working as ${he} spells out '${getWrittenTitle(slave)} Helped Me Love Anal.'`);
						r.push(Spoken(slave, `"I don't,"`));
						r.push(`${he} bursts out.`);
						r.push(Spoken(slave, `"It's fucking gross."`));
						break;
					default:
						r.push(`${He} stares, horrified that you have defaced ${his} body in this way.`);
				}
			}
		}
	}
	// TODO: maybe some reactions for other kinds of tattoos would be nice too?

	slave[`${location}Tat`] = design;
	return r.join(" ");
};


code:
App.PersonalAttention.reset = function() {
	if (isPCCareerInCategory("escort")) {
		V.personalAttention = {task: PersonalAttention.WHORING};
	} else if (isPCCareerInCategory("servant")) {
		V.personalAttention = {task: PersonalAttention.MAID};
	} else {
		V.personalAttention = {task: PersonalAttention.BUSINESS};
	}
};

/**
	* @param {string} input
	* @returns {string}
	*/
App.PersonalAttention.update = function(input) {
	switch (input) {
		case "soften her behavioral flaw":
			return "soften behavioral flaw";
		case "fix her behavioral flaw":
			return "fix behavioral flaw";
		case "soften her sexual flaw":
			return "soften sexual flaw";
		case "fix her sexual flaw":
			return "fix sexual flaw";
		case "break her will":
			return "break will";
		case "harshly break her will":
			return "harshly break will";
		case "build her devotion":
			return "build devotion";
		case "look after her health":
			return "health";
		case "teach her":
			return "learn skills";
		case "explore her sexuality":
			return "explore sexuality";
	}
	// settings for inducing flaws/paraphilias are unchanged
	return input;
};

/**
	* @param {string} objective
	* @param {App.Entity.SlaveState} slave
	* @returns {string}
	*/
App.PersonalAttention.getText = function(objective, slave) {
	const {his, him} = getPronouns(slave);
	switch (objective) {
		case "soften behavioral flaw":
			return `soften ${his} behavioral flaw`;
		case "fix behavioral flaw":
			return `fix ${his} behavioral flaw`;
		case "soften sexual flaw":
			return `soften ${his} sexual flaw`;
		case "fix sexual flaw":
			if (App.Data.misc.paraphiliaList.includes(slave.sexualFlaw)) {
				return `remove ${his} paraphilia`;
			} else {
				return `fix ${his} sexual flaw`;
			}
		case "break will":
			return `break ${his} will`;
		case "harshly break will":
			return `harshly break ${his} will`;
		case "build devotion":
			return `build ${his} devotion`;
		case "health":
			return `look after ${his} health`;
		case "learn skills":
			return `teach ${him}`;
		case "explore sexuality":
			return `explore ${his} sexuality`;
	}
	// settings for inducing flaws/paraphilias can be printed as-is
	return objective;
};

license: mit


code:
App.Porn = {};
App.Porn.GenreType = {
	paraphilia: {
		focusedViewershipFactor: 1.5,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 0.0,
		bonusViewership: function(slave) { return slave.fetishStrength * 2.0; }
	},
	fetish: {
		focusedViewershipFactor: 2.0,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 1.0,
		bonusViewership: function(slave) { return slave.fetishStrength; }
	},
	general: {
		focusedViewershipFactor: 4.0,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 1.0,
		bonusViewership: function(slave) { return 0.0; }
	},
	quirk: {
		focusedViewershipFactor: 6.0,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 1.0,
		bonusViewership: function(slave) { return 0.0; }
	},
	generic: {
		focusedViewershipFactor: 5.0,
		unfocusedViewershipFactor: 1.0,
		viewershipSoakingFactor: 0.0,
		bonusViewership: function(slave) { return 0.0; }
	}
};

App.Porn.Genre = {};

/* Paraphilia genres */

App.Porn.Genre.neglectful = {
	fameVar: "neglectful",
	fameName: "orgasm denial",
	focusName: "neglectful",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed the sight of $him ignoring $his own pleasure",
	prestigeDesc2: "$His many fans relish the sight of $him denying $himself pleasure",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him denying $himself pleasure",
	hitText: function(slave) { return `${getPronouns(slave).His} lack of interest in getting off ${getPronouns(slave).himself}, even when at the verge of climax, makes ${getPronouns(slave).him} a hit with viewers that enjoy orgasm denial.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} pleasing a line of partners without cumming once`; },
	valid: function(slave) { return slave.sexualFlaw === "neglectful"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.cumAddict = {
	fameVar: "cumAddict",
	fameName: "cum addiction",
	focusName: "cum addict",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed watching $him do anything and everything for cum",
	prestigeDesc2: "$His many fans relish the sight of $him doing anything for cum",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him doing anything for cum",
	hitText: function(slave) { return `${getPronouns(slave).His} complete obsession with cum makes ${getPronouns(slave).him} a hit with viewers that enjoy bukkake and cum drinking.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} bathing in a tub of cum`; },
	valid: function(slave) { return slave.sexualFlaw === "cum addict"; },
	uiName: function() { return capFirstChar(this.fameName); }
};

App.Porn.Genre.analAddict = {
	fameVar: "analAddict",
	fameName: "anal addiction",
	focusName: "anal addict",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed watching $him do anything for a dick in $his ass",
	prestigeDesc2: "$His many fans relish the sight of $him doing anything for a dick up $his ass",
	prestigeDesc3: "Millions are intimately familiar with the sight of $his well-versed anus.",
	hitText: function(slave) { return `${getPronouns(slave).His} complete obsession with taking things up ${getPronouns(slave).his} ass makes ${getPronouns(slave).him} a hit with viewers that enjoy hardcore anal.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} taking a series of huge cocks up ${getPronouns(slave).his} ass.`; },
	valid: function(slave) { return slave.sexualFlaw === "anal addict" && canDoAnal(slave); },
	uiName: function() { return capFirstChar(this.fameName); }
};

App.Porn.Genre.attentionWhore = {
	fameVar: "attentionWhore",
	fameName: "exhibition",
	focusName: "attention whore",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed watching $him do anything for attention",
	prestigeDesc2: "$His many fans relish the sight of $him doing anything for attention",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him doing anything for attention",
	hitText: function(slave) { return `${getPronouns(slave).His} complete obsession with being the center of attention makes ${getPronouns(slave).him} a hit with viewers that savor ${getPronouns(slave).his} frequent exhibitionism.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} flashing strangers`; },
	valid: function(slave) { return slave.sexualFlaw === "attention whore"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.breastGrowth = {
	fameVar: "breastGrowth",
	fameName: "breast expansion",
	focusName: "breast growth",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed charting the growth of $his breasts",
	prestigeDesc2: "$His many fans relish the sight of $his expanding bust",
	prestigeDesc3: "Millions are intimately familiar with the history of $his growing bust",
	hitText: function(slave) { return `${getPronouns(slave).His} complete obsession with the ever increasing size of ${getPronouns(slave).his} tits makes ${getPronouns(slave).him} a hit with viewers that enjoy enormous knockers and breast expansion.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} having ${getPronouns(slave).his} tits measured`; },
	valid: function(slave) { return slave.sexualFlaw === "breast growth"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.abusive = {
	fameVar: "abusive",
	fameName: "abuse",
	focusName: "abusive",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed watching $him abuse others",
	prestigeDesc2: "$His many fans relish the sight of $him abusing others",
	prestigeDesc3: "Millions are intimately familiar with $his abusive tendencies",
	hitText: function(slave) { return `${getPronouns(slave).His} wanton enjoyment of pleasure through force amuses viewers that enjoy rape and abuse.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} taking what ${getPronouns(slave).he} wants by force`; },
	valid: function(slave) { return slave.sexualFlaw === "abusive"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.malicious = {
	fameVar: "malicious",
	fameName: "sexual torture",
	focusName: "malicious",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed $him getting off from the suffering $he caused",
	prestigeDesc2: "$His many fans relish the sight of $him getting off from the suffering $he caused",
	prestigeDesc3: "Millions are intimately familiar with $his hunger for making others suffer",
	hitText: function(slave) { return `${getPronouns(slave).His} sexual appetite for others' suffering makes ${getPronouns(slave).him} a hit with viewers that enjoy sadism and violence.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} tormenting ${getPronouns(slave).his} prey`; },
	valid: function(slave) { return slave.sexualFlaw === "malicious"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.selfHating = {
	fameVar: "selfHating",
	fameName: "self hating",
	focusName: "self hating",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed watching $him happily suffer",
	prestigeDesc2: "$His many fans relish $his suffering",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him suffering",
	hitText: function(slave) { return `${getPronouns(slave).His} complete disregard for ${getPronouns(slave).his} own wellbeing makes ${getPronouns(slave).him} a hit with viewers that enjoy watching ${getPronouns(slave).him} suffer.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} orgasming from pain`; },
	valid: function(slave) { return slave.sexualFlaw === "self hating"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.breeder = {
	fameVar: "breeder",
	fameName: "breeder",
	focusName: "breeder",
	type: App.Porn.GenreType.paraphilia,
	prestigeDesc1: "Thousands have enjoyed watching $him obsess over pumping out babies",
	prestigeDesc2: "$His many fans relish $his obsession with having children",
	prestigeDesc3: "Millions are intimately familiar with $his obsession with being pregnant",
	hitText: function(slave) { return `${getPronouns(slave).His} complete obsession with getting and staying pregnant makes ${getPronouns(slave).him} a hit with viewers with all manner of pregnancy fetish, but particularly resonates with those as focused on it as ${getPronouns(slave).he} is.`; },
	trinketShotDesc: function(slave) {
		if (slave.counter.births > 0) {
			return `showing ${getPronouns(slave).him} having an orgasmic birth`;
		} else {
			return `showing ${getPronouns(slave).him} being bred`;
		}
	},
	valid: function(slave) { return slave.sexualFlaw === "breeder"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

/* Fetish genres */

App.Porn.Genre.sub = {
	fameVar: "sub",
	fameName: "submissive",
	focusName: "submissive",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed $his submission",
	prestigeDesc2: "$His many fans relish $his submissiveness",
	prestigeDesc3: "Millions are intimately familiar with $his submissiveness",
	hitText: function(slave) { return `With ${getPronouns(slave).his} submissive streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} submission`; },
	valid: function(slave) { return slave.fetish === "submissive"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.cumSlut = {
	fameVar: "cumSlut",
	fameName: "cum",
	focusName: "cumslut",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed $his taste for cum",
	prestigeDesc2: "$His many fans relish $his desire for cum",
	prestigeDesc3: "Millions are intimately familiar with $his taste for cum",
	hitText: function(slave) { return `With ${getPronouns(slave).his} taste for cum, ${getPronouns(slave).he} has a clear advantage when it comes to ejaculate-based smut.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} drinking a glass of cum`; },
	valid: function(slave) { return slave.fetish === "cumslut"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.anal = {
	fameVar: "anal",
	fameName: "buttslut",
	focusName: "buttslut",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed the sight of $his rear",
	prestigeDesc2: "$His many fans relish the sight of $his rear",
	prestigeDesc3: "Millions are intimately familiar with the sight of $his rear",
	hitText: function(slave) { return `With ${getPronouns(slave).his} fetish for asses, ${getPronouns(slave).he} finds friends in the company of viewers that love rear ends.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} shaking ${getPronouns(slave).his} booty`; },
	valid: function(slave) { return slave.fetish === "buttslut"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.humiliation = {
	fameVar: "humiliation",
	fameName: "humiliating",
	focusName: "humiliation",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed $him humiliating $himself",
	prestigeDesc2: "$His many fans relish $his frequent humiliation",
	prestigeDesc3: "Millions are intimately familiar with $his frequent humiliation",
	hitText: function(slave) { return `With ${getPronouns(slave).his} fetish for humiliation, ${getPronouns(slave).he} has a clear advantage when it comes to demeaning smut.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} humiliated in public`; },
	valid: function(slave) { return slave.fetish === "humiliation"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.boobs = {
	fameVar: "boobs",
	fameName: "breast",
	focusName: "boobs",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed the sight of $his breasts",
	prestigeDesc2: "$His many fans relish the sight of $his breasts",
	prestigeDesc3: "Millions are intimately familiar with $his breasts",
	hitText: function(slave) { return `With ${getPronouns(slave).his} fetish for tits, ${getPronouns(slave).he} has a clear advantage when it comes to breast focused smut.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} bare chest`; },
	valid: function(slave) { return slave.fetish === "boobs"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.dom = {
	fameVar: "dom",
	fameName: "dominant",
	focusName: "dom",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed $his dominance",
	prestigeDesc2: "$His many fans relish $his dominance",
	prestigeDesc3: "Millions are intimately familiar with $his dominant streak",
	hitText: function(slave) { return `With ${getPronouns(slave).his} dominant streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} seated upon ${getPronouns(slave).his} obedient toy`; },
	valid: function(slave) { return slave.fetish === "dom"; },
	uiName: function() { return capFirstChar(this.fameName); }
};

App.Porn.Genre.sadist = {
	fameVar: "sadist",
	fameName: "sadistic",
	focusName: "sadist",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed $his sadism",
	prestigeDesc2: "$His many fans relish $his sadism",
	prestigeDesc3: "Millions are intimately familiar with $his sadistic streak",
	hitText: function(slave) { return `With ${getPronouns(slave).his} sadistic streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} whipping ${getPronouns(slave).his} lover`; },
	valid: function(slave) { return slave.fetish === "sadist"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.masochist = {
	fameVar: "masochist",
	fameName: "masochistic",
	focusName: "masochist",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed $his masochism",
	prestigeDesc2: "$His many fans relish the sight of $his masochism",
	prestigeDesc3: "Millions are intimately familiar with $his masochistic stream",
	hitText: function(slave) { return `With ${getPronouns(slave).his} masochistic streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} being whipped`; },
	valid: function(slave) { return slave.fetish === "masochist"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.pregnancy = {
	fameVar: "pregnancy",
	fameName: "pregnancy fetish",
	focusName: "pregnancy",
	type: App.Porn.GenreType.fetish,
	prestigeDesc1: "Thousands have enjoyed $his fondness for pregnancy",
	prestigeDesc2: "$His many fans relish the sight of $his fondness for pregnancy",
	prestigeDesc3: "Millions are intimately familiar with $his pregnancy kink",
	hitText: function(slave) { return `With ${getPronouns(slave).his} fetish for all things pregnancy, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	trinketShotDesc: function(slave) {
		if (slave.ovaries === 1 || slave.mpreg === 1) {
			return `showing ${getPronouns(slave).him} getting knocked up`;
		} else if (slave.dick > 0) {
			return `showing ${getPronouns(slave).him} knocking a girl up`;
		} else {
			return `showing ${getPronouns(slave).him} pretending to be pregnant`;
		}
	},
	valid: function(slave) { return slave.fetish === "pregnancy"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

/* General genres */

App.Porn.Genre.fuckdoll = {
	fameVar: "fuckdoll",
	fameName: "fuckdoll",
	focusName: "fuckdoll",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "$His fans relish the sight of $him being used",
	prestigeDesc2: "$His many fans relish the sight of $him being used",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him being used",
	hitText: function(slave) { return `${getPronouns(slave).His} latex encased body attracts a variety of viewers with tastes ranging from bondage to dolls.`; },
	trinketShotDesc: function(slave) { return `showing it offering itself`; },
	valid: function(slave) { return slave.fuckdoll > 0; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.rape = {
	fameVar: "rape",
	fameName: "rape",
	focusName: "rape",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed the sight of $him being raped",
	prestigeDesc2: "$His many fans relish the sight of $him being raped",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him being raped",
	hitText: function(slave) { return `${getPronouns(slave).He} is too unbroken for consensual sex, but ${getPronouns(slave).his} viewers wouldn't want it any other way.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} rape`; },
	valid: function(slave) { return (slave.devotion < -20) && (slave.counter.anal + slave.counter.vaginal > 0); },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.preggo = {
	fameVar: "preggo",
	fameName: "preggo",
	focusName: "preggo",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed watching $him swell with child",
	prestigeDesc2: "$His many fans relish the sight of $him swollen with child",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him swollen with child",
	hitText: function(slave) { return `${getPronouns(slave).His} gravid swell may be a turn off to some, but the maiesiophiliacs love it.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} cradling ${getPronouns(slave).his} middle`; },
	valid: function(slave) { return slave.bellyPreg > 500; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.BBW = {
	fameVar: "BBW",
	fameName: "BBW",
	focusName: "BBW",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed the sight of $his thick and soft body",
	prestigeDesc2: "$His many fans relish the sight of $his thick, soft body",
	prestigeDesc3: "Millions are intimately familiar with $his thick, soft body",
	hitText: function(slave) { return `${getPronouns(slave).His} weight gives ${getPronouns(slave).him} a heavy allure to the chubby chasers and BBW lovers out there.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} giving a bellyjob`; },
	valid: function(slave) { return slave.weight > 95; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.loli = {
	fameVar: "loli",
	fameName: "underage",
	focusName: "loli",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed the sight of $his childish body",
	prestigeDesc2: "$His many fans relish $his immature body",
	prestigeDesc3: "Millions are intimately familiar with $his immature body",
	hitText: function(slave) { return `${getPronouns(slave).His} young age gives ${getPronouns(slave).him} a dangerous edge and a number of careful viewers.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} 'innocence'`; },
	valid: function(slave) { return slave.visualAge <= 12; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.gainer = {
	fameVar: "gainer",
	fameName: "weight gain",
	focusName: "gainer",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed the sight of $him eating and gaining weight",
	prestigeDesc2: "$His many fans relish how curvy $he's gotten",
	prestigeDesc3: "Millions are intimately familiar with how much weight $he has gained",
	hitText: function(slave) { return `${getPronouns(slave).His} expanding waistline attracts those who enjoy seeing a ${getPronouns(slave).girl} pack on the pounds while stuffing ${getPronouns(slave).his} face.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} trying on ${getPronouns(slave).his} old clothes`; },
	valid: function(slave) { return (slave.weight > 30 && slave.diet === "fattening") || (slave.inflation > 0 && slave.inflationType === "food"); },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.stud = {
	fameVar: "stud",
	fameName: "big dick",
	focusName: "stud",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed the sight of $his throbbing erection",
	prestigeDesc2: "$His many fans relish the sight of $his heavy dick",
	prestigeDesc3: "Millions are intimately familiar with the sight of $his erect dick",
	hitText: function(slave) { return `${getPronouns(slave).His} powerful erection excites those who see it, especially when it is put to good use.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} money shot`; },
	valid: function(slave) { return canPenetrate(slave) && slave.dick > 3; },
	uiName: function() { return capFirstChar(this.fameName); }
};

App.Porn.Genre.muscle = {
	fameVar: "muscle",
	fameName: "muscle",
	focusName: "muscle",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed the sight of $his hard muscles",
	prestigeDesc2: "$His many fans relish the sight of $his hard muscles",
	prestigeDesc3: "Millions are intimately familiar with the sight of $his hard muscles",
	hitText: function(slave) { return `${getPronouns(slave).His} powerful muscles and bodybuilder physique attracts a dedicated audience.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} holding ${getPronouns(slave).his} partner in the air during sex`; },
	valid: function(slave) { return slave.muscles > 80; },
	uiName: function() { return capFirstChar(this.fameName); }
};

App.Porn.Genre.incest = {
	fameVar: "incest",
	fameName: "taboo",
	focusName: "incest",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed seeing the love $he shares with $his family members",
	prestigeDesc2: "$His many fans relish the sight of $him loving $his family members",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him loving $his family members",
	hitText: function(slave) {
		if (App.Utils.hasPartnerSex(slave) && areRelated(slave, getSlave(slave.relationshipTarget))) {
			const partner = getSlave(slave.relationshipTarget);
			return `${getPronouns(slave).His} sexual escapades with ${getPronouns(slave).his} ${relativeTerm(slave, partner)} ${partner.slaveName} excite viewers attracted to incestuous relationships.`;
		} else if (App.Utils.hasFamilySex(slave)) {
			return `${getPronouns(slave).His} sexual escapades with ${getPronouns(slave).his} close family members excite viewers attracted to incest.`;
		} else {
			return `${getPronouns(slave).His} sexual escapades with you, ${getPronouns(slave).his} own ${relativeTerm(slave, V.PC)}, excite viewers attracted to incestuous relationships.`;
		}
	},
	trinketShotDesc: function(slave) {
		if (App.Utils.hasPartnerSex(slave) && areRelated(slave, getSlave(slave.relationshipTarget))) {
			const partner = getSlave(slave.relationshipTarget);
			return `showing ${getPronouns(slave).him} having fun with ${getPronouns(slave).his} ${relativeTerm(slave, partner)} ${partner.slaveName}`;
		} else if (App.Utils.hasFamilySex(slave)) {
			return `showing ${getPronouns(slave).him} having incestuous fun with ${getPronouns(slave).his} family`;
		} else {
			return `showing ${getPronouns(slave).him} having incestuous fun with you`;
		}
	},
	valid: function(slave) {
		return (V.seeIncest !== 0) && (
			(App.Utils.hasFamilySex(slave)) || // has sex with family
			(App.Utils.hasPartnerSex(slave) && areRelated(slave, getSlave(slave.relationshipTarget))) || // or with her partner, who is related to her
			(App.Utils.sexAllowed(slave, V.PC) && areRelated(slave, V.PC)) // or with you, and is related to you
		);
	},
	uiName: function() { return capFirstChar(this.fameName); }
};

/* quirk genres */

App.Porn.Genre.deepThroat = {
	fameVar: "deepThroat",
	fameName: "deepthroat",
	focusName: "gagfuck queen",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed the sounds $he makes when being throatfucked",
	prestigeDesc2: "$His many fans relish the sounds $he makes when being throatfucked",
	prestigeDesc3: "Millions are intimately familiar with the sounds $he makes when being throatfucked",
	hitText: function(slave) { return `${getPronouns(slave).He} impresses with just how much dick can slip down ${getPronouns(slave).his} throat.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} getting facefucked`; },
	valid: function(slave) { return slave.sexualQuirk === "gagfuck queen"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.struggleFuck = {
	fameVar: "struggleFuck",
	fameName: "unwilling",
	focusName: "strugglefuck queen",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed how $he struggles during sex",
	prestigeDesc2: "$His many fans relish how perfectly $he struggles during sex",
	prestigeDesc3: "Millions are intimately familiar with how perfectly $he struggles during sex",
	hitText: function(slave) { return `${getPronouns(slave).He} impresses with ${getPronouns(slave).his} ability to put up just the right amount of fight during sex.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} struggling`; },
	valid: function(slave) { return slave.sexualQuirk === "strugglefuck queen" && (canDoVaginal(slave) || canDoAnal(slave)); },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.painal = {
	fameVar: "painal",
	fameName: "hardcore anal",
	focusName: "painal queen",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed watching $his asshole pushed to its limit",
	prestigeDesc2: "$His many fans relish watching $his asshole pushed to its limit",
	prestigeDesc3: "Millions are intimately familiar with seeing $his asshole pushed to its limit",
	hitText: function(slave) { return `${getPronouns(slave).He} impresses with ${getPronouns(slave).his} ability to push ${getPronouns(slave).his} anus to its limit.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} taking an enormous dick up ${getPronouns(slave).his} ass`; },
	valid: function(slave) { return slave.sexualQuirk === "painal queen" && canDoAnal(slave); },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.tease = {
	fameVar: "tease",
	fameName: "softcore",
	focusName: "tease",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed $his lewd striptease",
	prestigeDesc2: "$His many fans relish $his lewd striptease",
	prestigeDesc3: "Millions are intimately familiar with $his lewd striptease",
	hitText: function(slave) { return `${getPronouns(slave).He} tantalizes viewers with ${getPronouns(slave).his} suggestive moves.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} stripping`; },
	valid: function(slave) { return slave.sexualQuirk === "tease"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.romantic = {
	fameVar: "romantic",
	fameName: "romantic",
	focusName: "romantic",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed the deep bond $he shares with $his partners",
	prestigeDesc2: "$His many fans relish the deep bond $he shares with $his partners",
	prestigeDesc3: "Millions are intimately familiar with the deep bond $he shares with $his partners",
	hitText: function(slave) { return `${getPronouns(slave).He} draws viewers in with ${getPronouns(slave).his} genuine pleasure.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} sharing an orgasm with ${getPronouns(slave).his} partner`; },
	valid: function(slave) { return slave.sexualQuirk === "romantic"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.pervert = {
	fameVar: "pervert",
	fameName: "really perverted",
	focusName: "perverted",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed $his most perverted tendencies",
	prestigeDesc2: "$His many fans relish the depths of $his perversions",
	prestigeDesc3: "Millions are intimately familiar with the depths of $his perversions",
	hitText: function(slave) { return `${getPronouns(slave).His} perverted tastes shock and allure viewers in ways they didn't even realize.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} doing something perverted`; }, /* FIXME: really, that's the best we could come up with??? */
	valid: function(slave) { return slave.sexualQuirk === "perverted"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.caring = {
	fameVar: "caring",
	fameName: "voyeur",
	focusName: "caring",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed watching $him devote $himself to $his partners' pleasure",
	prestigeDesc2: "$His many fans relish $his devotion to $his partners' pleasure",
	prestigeDesc3: "Millions are intimately familiar with $his devotion to $his partners' pleasure",
	hitText: function(slave) { return `${getPronouns(slave).His} sincere devotion to ${getPronouns(slave).his} partner's pleasure draws in viewers with an interest in watching couples fuck.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} bringing ${getPronouns(slave).his} partner to orgasm`; },
	valid: function(slave) { return slave.sexualQuirk === "caring"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.unflinching = {
	fameVar: "unflinching",
	fameName: "unspeakable",
	focusName: "unflinching",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed $his willingness to do things not repeated in polite company",
	prestigeDesc2: "$His many fans relish $his willingness to do anything and everything",
	prestigeDesc3: "Millions are intimately familiar with $his willingness to do things not repeated in polite company",
	hitText: function(slave) { return `${getPronouns(slave).His} willingness to do anything catches the attention of those who enjoy acts that should never be mentioned to others.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} doing something unmentionable`; },
	valid: function(slave) { return slave.sexualQuirk === "unflinching"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

App.Porn.Genre.sizeQueen = {
	fameVar: "sizeQueen",
	fameName: "huge insertion",
	focusName: "size queen",
	type: App.Porn.GenreType.quirk,
	prestigeDesc1: "Thousands have enjoyed the sight of $his holes filled to their limits",
	prestigeDesc2: "$His many fans relish the sight of $his holes filled to their limits",
	prestigeDesc3: "Millions are intimately familiar with the sight of $his holes filled to their limits",
	hitText: function(slave) { return `${getPronouns(slave).His} intent on taking the largest things possible into ${getPronouns(slave).his} holes draws in viewers with an interest for huge insertions.`; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} belly bulging from within`; },
	valid: function(slave) { return slave.sexualQuirk === "size queen"; },
	uiName: function() { return capFirstChar(this.focusName); }
};

/* Generic porn - leave this entry last */

App.Porn.Genre.general = {
	fameVar: "general",
	fameName: "generic",
	focusName: "porn",
	type: App.Porn.GenreType.generic,
	prestigeDesc1: "Thousands have enjoyed the sight of $him being used",
	prestigeDesc2: "$His many fans relish the sight of $him being used",
	prestigeDesc3: "Millions are intimately familiar with the sight of $him mid-coitus",
	hitText: function(slave) { return ``; },
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} mid-coitus`; },
	valid: function(slave) { return true; /* anyone can do generic porn */ },
	uiName: function() { return "Smut is smut"; }
};

/** Returns a given genre by its fame name. */
App.Porn.getGenreByFameName = function(fameName) {
	return _.values(App.Porn.Genre).find((g) => g.fameName === fameName);
};

/** Returns a given genre by its focus name. */
App.Porn.getGenreByFocusName = function(focusName) {
	return _.values(App.Porn.Genre).find((g) => g.focusName === focusName);
};

/** Returns all the genres in the system. */
App.Porn.getAllGenres = function() {
	return _.values(App.Porn.Genre);
};

/** Returns all the genres with a given type. */
App.Porn.getGenresByType = function(type) {
	return _.values(App.Porn.Genre).filter((g) => g.type === type);
};

/** Returns the links necessary to set any valid porn genre for this slave.
 * @param {string} page SugarCube passage name to redirect to when the link is clicked
 * @param {App.Entity.SlaveState} slave
 * @returns {HTMLElement}
 */
App.Porn.genreChoiceLinks = function(page, slave) {
	let makeLink = (genre) => App.UI.DOM.link(genre.uiName(), (s, g) => s.porn.focus = g.focusName, [slave, genre], page);
	let links = this.getAllGenres().filter((g) => g.valid(slave)).map(makeLink);
	links.push(App.UI.DOM.link("No focus", (s) => s.porn.focus = "none", [slave], page));
	return App.UI.DOM.generateLinksStrip(links);
};


code:
/* Major props to the anons who worked together to forge the Super Pregnancy Project. Let your legacy go unforgotten.*/
globalThis.getPregBellySize = function(s) {
	let targetLen;
	let gestationWeek = s.preg;
	let fetuses = s.pregType;
	let phi = 1.618;

	if (gestationWeek <= 32) {
		targetLen = ((0.00006396 * Math.pow(gestationWeek, 4)) - (0.005501 * Math.pow(gestationWeek, 3)) + (0.161 * Math.pow(gestationWeek, 2)) - (0.76 * gestationWeek) + 0.208);
	} else if (gestationWeek <= 106) {
		targetLen = ((-0.0000004675 * Math.pow(gestationWeek, 4)) + (0.0001905 * Math.pow(gestationWeek, 3)) - (0.029 * Math.pow(gestationWeek, 2)) + (2.132 * gestationWeek) - 16.575);
	} else {
		targetLen = ((-0.00003266 * Math.pow(gestationWeek, 2)) + (0.076 * gestationWeek) + 43.843);
	}

	return ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)) * fetuses);
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
globalThis.bellyAdjective = function(slave) {
	if (slave.belly >= 1500) {
		if (slave.belly >= 1000000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'unfathomably distended, brimming with life';
			} else {
				return `unfathomable`;
			}
		} else if (slave.belly >= 750000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'monolithic bulging';
			} else {
				return `monolithic`;
			}
		} else if (slave.belly >= 600000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'titanic bulging';
			} else {
				return `titanic`;
			}
		} else if (slave.belly >= 450000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'gigantic bulgy';
			} else {
				return `gigantic`;
			}
		} else if (slave.belly >= 300000) {
			return 'massive';
		} else if (slave.belly >= 100000) {
			return 'giant';
		} else if (slave.belly >= 15000) {
			return 'huge';
		} else if (slave.belly >= 10000) {
			return 'big';
		} else {
			return `swollen`;
		}
	}
	return "";
};

/* calculates and returns expected ovum count during conception*/
globalThis.setPregType = function(actor) {
	/* IMHO rework is possible. Can be more interesting to play, if this code will take in account more body conditions - age, fat, food, hormone levels, etc. */

	let ovum = jsRandom(actor.pregData.normalOvaMin, actor.pregData.normalOvaMax); // for default human profile it's always 1.
	let fertilityStack = 0; // adds an increasing bonus roll for stacked fertility drugs

	/* Suggestion for better animal pregnancy support - usage of another variable then ovum for fertility drugs bonus, and then adding actor.pregData.drugsEffect multiplier to it before adding to ovum. Example:

		let bonus = 0;

		... (code below where ovum changed to bonus)

		bonus *= actor.pregData.drugsEffect;
		ovum += bonus;

	*/

	if (actor.broodmother < 1) { // Broodmothers should be not processed here. Necessary now.
		if (typeof actor.readyOva === "number" && actor.readyOva !== 0) {
			ovum = actor.readyOva; // just single override; for delayed impregnation cases
		} else {
			if (actor.eggType === "horse" || actor.eggType === "cow") {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1]);
					fertilityStack += 0.8;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					fertilityStack += 0.4;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					fertilityStack += 0.2;
				}
				if (actor.ovaImplant === "fertility") {
					fertilityStack += 0.3;
				}
				if (actor.hormones === 2) {
					fertilityStack += 0.2;
				}
				if (actor.hormoneBalance >= 200) {
					fertilityStack += 0.3;
				}
				if (actor.diet === "fertility") {
					fertilityStack += 0.3;
				}
				if (V.masterSuitePregnancyFertilitySupplements === 1 && (actor.assignment === Job.MASTERSUITE || actor.assignment === Job.CONCUBINE)) {
					fertilityStack += 0.5;
				}
				if (V.reproductionFormula === 1 && (V.week - actor.weekAcquired > 0)) {
					fertilityStack += 0.2;
				}
				if (actor.drugs === "super fertility drugs") {
					fertilityStack += 1.6;
				} else if (actor.drugs === "fertility drugs") {
					fertilityStack += 0.6;
				}
				fertilityStack = Math.floor(fertilityStack);
				if (V.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(0, fertilityStack * 2);
					} else {
						ovum += jsRandom(0, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 4) {
							ovum = 4;
						}
					} else if (ovum > 3) {
						ovum = 3;
					}
				}
			} else if (actor.eggType === "dog") {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsEither([1, 2, 2, 3]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					ovum += jsEither([0, 1, 1, 2]);
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					ovum += jsEither([0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (actor.ovaImplant === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (actor.hormones === 2) {
					ovum += jsEither([0, 0, 0, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.hormoneBalance >= 200) {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.diet === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (V.masterSuitePregnancyFertilitySupplements === 1 && (actor.assignment === Job.MASTERSUITE || actor.assignment === Job.CONCUBINE)) {
					ovum += jsEither([0, 0, 0, 1, 1, 2, 2, 2, 3, 3]);
					fertilityStack++;
					fertilityStack++;
				}
				if (V.reproductionFormula === 1 && (V.week - actor.weekAcquired > 0)) {
					fertilityStack++;
				}
				if (actor.drugs === "super fertility drugs") {
					ovum += jsEither([1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.drugs === "fertility drugs") {
					ovum += jsEither([0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3]);
					fertilityStack++;
				}
				if (V.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(0, fertilityStack * 2);
					} else {
						ovum += jsRandom(0, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 8) {
							ovum = jsEither([6, 8]);
						}
					} else if (ovum > 8) {
						ovum = jsRandom(6, 8);
					}
				}
			} else if (actor.eggType === "pig") {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsRandom(4, 8);
					fertilityStack += 16;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					ovum += jsRandom(2, 6);
					fertilityStack += 10;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					ovum += jsRandom(2, 4);
					fertilityStack += 6;
				}
				if (actor.ovaImplant === "fertility") {
					ovum += jsRandom(4, 12);
					fertilityStack += 6;
				}
				if (actor.hormones === 2) {
					ovum += jsRandom(0, 4);
					fertilityStack += 3;
				}
				if (actor.hormoneBalance >= 200) {
					ovum += jsRandom(0, 4);
					fertilityStack += 3;
				}
				if (actor.diet === "fertility") {
					ovum += jsRandom(4, 10);
					fertilityStack += 6;
				}
				if (V.masterSuitePregnancyFertilitySupplements === 1 && (actor.assignment === Job.MASTERSUITE || actor.assignment === Job.CONCUBINE)) {
					ovum += jsRandom(8, 16);
					fertilityStack += 10;
				}
				if (V.reproductionFormula === 1 && (V.week - actor.weekAcquired > 0)) {
					fertilityStack += 2;
				}
				if (actor.drugs === "super fertility drugs") {
					ovum += jsRandom(10, 40);
					fertilityStack += 32;
				} else if (actor.drugs === "fertility drugs") {
					ovum += jsRandom(10, 20);
					fertilityStack += 16;
				}
				if (V.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(fertilityStack / 2, fertilityStack * 2);
					} else {
						ovum += jsRandom(fertilityStack / 4, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 76) {
							ovum = jsEither([70, 72, 74, 76]);
						}
					} else if (ovum > 75) {
						ovum = jsRandom(60, 75);
					}
				}
			} else {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsEither([1, 2, 2, 3]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					ovum += jsEither([0, 1, 1, 2]);
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					ovum += jsEither([0, 0, 0, 0, 1]);
					fertilityStack++;
				} else {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // base chance for twins
				}
				if (actor.ovaImplant === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (actor.hormones === 2) {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.hormoneBalance >= 200) {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.diet === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (actor.ID === -1) {
					if (actor.forcedFertDrugs > 0) {
						ovum += jsEither([1, 1, 1, 1, 1, 2, 2, 2, 3]);
						fertilityStack++;
						fertilityStack++;
					}
					if (V.reproductionFormula === 1 && !canEatFood(actor)) {
						fertilityStack++;
					}
				} else {
					if (V.masterSuitePregnancyFertilitySupplements === 1 && (actor.assignment === Job.MASTERSUITE || actor.assignment === Job.CONCUBINE)) {
						ovum += jsEither([0, 0, 0, 1, 1, 2, 2, 2, 3, 3]);
						fertilityStack++;
						fertilityStack++;
						if (V.reproductionFormula === 1 && (V.week - actor.weekAcquired > 0)) {
							fertilityStack++;
						}
					}
				}
				if (actor.drugs === "super fertility drugs") {
					ovum += jsEither([1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.drugs === "fertility supplements") {
					ovum += jsEither([0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2]);
					fertilityStack++;
				} else if (actor.drugs === "fertility drugs") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3]);
					fertilityStack++;
				}
				if (V.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(0, fertilityStack * 2);
					} else {
						ovum += jsRandom(0, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 12) {
							ovum = jsEither([10, 12]);
						}
					} else if (ovum > 12) {
						ovum = jsRandom(6, 12);
					}
				}
				if (actor.ID === -1) { // temp until player birth is redone
					ovum = Math.clamp(ovum, 0, 8);
				}
			}
		}
	}

	if (actor.geneticQuirks.superfetation === 2 && actor.womb.length > 0) {
		let ftVol = FetusGetPrediction(actor, actor.pregData.normalBirth);
		let cmVol = ftVol * actor.womb.length;
		let maxVol = actor.pregAdaptation * 2000;
		if (V.seeHyperPreg === 0) {
			maxVol /= 10; // without hyperpreg enabled it's limited to be roughly ten times smaller.
		}
		let freeVol = maxVol - cmVol;
		let coeff = ((maxVol / actor.womb.length) / (freeVol / ftVol)) / 2; // more divide to 2 is to balance for ensured 1 ova even if over limit.

		if (coeff < 0) { coeff = 0; }

		if (V.seeHyperPreg !== 0) {
			// This is a second chance for implantation.
			// Should be affected only by chemical and genetic for easier implantation.
			// Not directly related to ova count, only to body/womb condition.
			// Raise successful implantation chance with already overfull womb. AFTER previous check.
			coeff += jsRandom(0, fertilityStack / 2);
		}

		if (ovum > coeff) {
			ovum = coeff;
		}

		/* for future, if monthly cycle will be a thing to implement, this will be useful.
		if (ovum < 0)
			ovum = jsRandom(coeff/fertilityStack, 1);
		*/
		if (ovum < 1) { ovum = 1; }

		// console.log("Name: ", actor.slaveName, " ftVol:", ftVol, " cmVol:", cmVol, " maxVol:", maxVol, " freeVol:", freeVol, " coeff:", coeff, " fertilityStack: ", fertilityStack, " ovum: ", ovum);
	}

	return Math.ceil(ovum);
};

/** Attempt to get a slave pregnant
 * Penetrative ability, ability to become pregnant, and canBreed() must be checked outside of this. Designed to assume .eggType === "human".
 * @param {App.Entity.SlaveState | App.Entity.PlayerState} target is the slave to get pregnant. Also accepts the PC.
 * @param {number} chance is the % chance to conceive.
 * @param {0|1|2} hole control's the hole involved (0 - vagina, 1 - ass, 2 - both). .mpreg did this.
 * @param {number} [fatherID] is the ID of her sire or 0 if undefined.
 * @returns {string} Returns "${He} has become pregnant." if relevant
 */
globalThis.knockMeUp = function(target, chance, hole, fatherID) {
	let r = ``;
	if (V.seePreg !== 0) {
		// eslint-disable-next-line no-nested-ternary
		if (jsRandom(0, 99) < (chance + (V.reproductionFormula * ((target.pregSource <= 0) ? ((target.ID === -1) ? 0 : 10) : 20)))) {
			if (target.mpreg === hole || hole === 2) {
				if (target.pregWeek <= 0) {
					target.preg = 1;
					target.pregSource = (!fatherID ? 0 : fatherID);
					if (target.ID !== -1) {
						target.pregWeek = 1;
					}
				}

				target.pregType = setPregType(target);
				WombImpregnate(target, target.pregType, fatherID, 1);

				if (V.menstruation === 1) {
					//
				} else {
					target.pregKnown = 1;
					if (target.ID === -1) {
						/* r += "You have gotten pregnant."; */
					} else {
						const {He} = getPronouns(target);
						r += `<span class="lime">${He} has become pregnant.</span>`;
					}
					if (target.geneticQuirks.superfetation === 2 && target.womb.length > 0) {
						if (V.seeHyperPreg === 1) {
							target.fertPeak = 1;
						} else {
							target.fertPeak = 4;
						}
					}
				}
			}
		}
	}
	return r;
};

globalThis.getIncubatorReserved = function( /* slaves */ ) {
	return FetusGlobalReserveCount("incubator");
};

globalThis.getNurseryReserved = function( /* slaves */ ) {
	return FetusGlobalReserveCount("nursery");
};

/** Find a slave's father, if they are also one of your slaves, wherever they may be
 * @param {number} fatherID
 * @returns {FC.HumanState}
 */
globalThis.findFather = function(fatherID) {
	let father = getSlave(fatherID);
	if (father === undefined) {
		if (V.incubator.capacity > 0) {
			father = V.incubator.tanks.find(s => s.ID === fatherID);
		}
	}
	if (father === undefined) {
		if (V.nursery > 0) {
			father = V.cribs.find(s => s.ID === fatherID);
		}
	}

	return father;
};

/* not to be used until that last part is defined. It may become slave.boobWomb.volume or some shit */
/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getBaseBoobs = function(slave) {
	return slave.boobs - slave.boobsImplant - slave.boobsMilk - slave.boobsWombVolume;
};

/**
 * Terminate a pregnancy without birth (i.e. miscarriage/abortion), while automatically applying the correct postpartum length
 * @param {App.Entity.SlaveState | App.Entity.PlayerState} slave
 */
globalThis.TerminatePregnancy = function(slave) {
	if (slave.bellyPreg > 1500) {
		// late term - highly fertile slaves spring back quicker
		if (slave.geneticQuirks.fertility + slave.geneticQuirks.hyperFertility >= 4) {
			slave.pregWeek = -2;
		} else if (slave.geneticQuirks.hyperFertility > 1) {
			slave.pregWeek = -3;
		} else {
			slave.pregWeek = -4;
		}
	} else if (slave.pregWeek >= 4) {
		// still early
		slave.pregWeek = -2;
	} else if (slave.pregWeek > 0) {
		// very early
		slave.pregWeek = -1;
	}
	WombFlush(slave);
	SetBellySize(slave);
};


code:
App.Utils.Pronouns = class {
	/**
	 * @param {{pronoun: number}} obj
	 */
	constructor(obj) {
		/** @type {FC.Data.Pronouns.Definition} */
		this._pronouns = obj.pronoun < App.Data.Pronouns.Kind.custom
			? App.Data.Pronouns.Std[obj.pronoun]
			: V.customPronouns[obj.pronoun];
	}

	get pronoun() { return this._pronouns.pronoun; }
	get possessivePronoun() { return this._pronouns.possessivePronoun; }
	get possessive() { return this._pronouns.possessive; }
	get object() { return this._pronouns.object; }
	get objectReflexive() { return this._pronouns.objectReflexive; }
	get noun() { return this._pronouns.noun; }

	get Pronoun() { return capFirstChar(this.pronoun); }
	get PossessivePronoun() { return capFirstChar(this.possessivePronoun); }
	get Possessive() { return capFirstChar(this.possessive); }
	get Object() { return capFirstChar(this.object); }
	get ObjectReflexive() { return capFirstChar(this.objectReflexive); }
	get Noun() { return capFirstChar(this.noun); }

	get he() { return this.pronoun; }
	get him() { return this.object; }
	get his() { return this.possessive; }
	get himself() { return this.objectReflexive; }
	get boy() { return this.noun; }

	get He() { return this.Pronoun; }
	get Him() { return this.Object; }
	get His() { return this.Possessive; }
	get Himself() { return this.ObjectReflexive; }
	get Boy() { return this.Noun; }

	get she() { return this.pronoun; }
	get her() { return this.object; }
	get hers() { return this.possessivePronoun; }
	get herself() { return this.objectReflexive; }
	get girl() { return this.noun; }

	get She() { return this.Pronoun; }
	get Her() { return this.Object; }
	get Hers() { return this.PossessivePronoun; }
	get Herself() { return this.ObjectReflexive; }
	get Girl() { return this.Noun; }

	get woman() { return this.noun === "girl" ? "woman" : "man"; }
	get women() { return this.noun === "girl" ? "women" : "men"; }
	get loli() { return this.noun === "girl" ? "loli" : "shota"; }

	get Woman() { return capFirstChar(this.woman); }
	get Women() { return capFirstChar(this.women); }
	get Loli() { return capFirstChar(this.loli); }

	get daughter() { return this.noun === "girl" ? "daughter" : "son"; }
	get Daughter() { return capFirstChar(this.daughter); }

	get sister() { return this.noun === "girl" ? "sister" : "brother"; }
	get Sister() { return capFirstChar(this.sister); }

	// TODO: remove this once overhauled
	// eslint-disable-next-line sonarjs/no-all-duplicated-branches
	get wife() { return this.noun === "girl" ? "wife" : "wife"; }
	get Wife() { return capFirstChar(this.wife); }
	// TODO: remove this once overhauled
	// eslint-disable-next-line sonarjs/no-all-duplicated-branches
	get wives() { return this.noun === "girl" ? "wives" : "wives"; }
	get Wives() { return capFirstChar(this.wives); }

	get mother() { return this.noun === "girl" ? "mother" : "father"; }
	get Mother() { return capFirstChar(this.mother); }

	get father() { return this.noun === "girl" ? "mother" : "father"; }
	get Father() { return capFirstChar(this.father); }

	/**
	 * @param {string} suffix
	 * @returns {Object.<string, string>}
	 */
	appendSuffix(suffix) {
		/** @type {Object.<string, string>} */
		const r = {};
		// "constructor" will be the first property. slice(1) to skip it
		for (const prop of Object.getOwnPropertyNames(Object.getPrototypeOf(this)).slice(1)) {
			r[prop + suffix] = this[prop];
		}
		return r;
	}
};

/**
 * @param {{pronoun: number}} obj
 * @returns {App.Utils.Pronouns}
 */
globalThis.getPronouns = function(obj) {
	return new App.Utils.Pronouns(obj);
};

/**
 * @param {{pronoun: number}} obj
 * @param {App.Entity.SlaveState} spokenBy
 * @returns {App.Utils.Pronouns}
 */
globalThis.getSpokenPronouns = function(obj, spokenBy) {
	const lispHandler = {
		get: function(obj, prop) {
			const result = obj[prop];
			if (typeof result === "string" && lisps) {
				return lispReplace(result);
			} else {
				return result;
			}
		}
	};

	const lisps = SlaveStatsChecker.checkForLisp(spokenBy);
	return new Proxy(new App.Utils.Pronouns(obj), lispHandler);
};

/**
 * @param {number} dickRatio
 * @returns {App.Utils.Pronouns}
 */
globalThis.getNonlocalPronouns = function(dickRatio) {
	/* a fake slave object, we need the .pronoun attribute only */
	const slave = {pronoun: App.Data.Pronouns.Kind.female};
	/* Used for generic slaves, citizens, security, etc. */
	if (V.diversePronouns === 1 && dickRatio > 0 && (dickRatio >= 100 || random(1, 100) <= dickRatio)) {
		slave.pronoun = App.Data.Pronouns.Kind.male;
	}

	return getPronouns(slave);
};

/** Get a property for a given slave, with the correct pronouns.
 * @param {App.Entity.SlaveState} slave
 * @param {string} prop
 * @returns {string}
 */
globalThis.pronounsForSlaveProp = function(slave, prop) {
	const pronouns = getPronouns(slave);
	return prop.replace(/\$([A-Z]?[a-z]+)/g, (match, cap1) => pronouns[cap1] || match);
};


code:
globalThis.sortDomObjects = function(objects, attrName, reverse = 0) {
	reverse = (reverse) ? -1 : 1;

	function sortingByAttr(a, b) {
		let aVal = a.getAttribute(attrName);
		let bVal = b.getAttribute(attrName);
		let aInt = parseInt(aVal);
		if (!isNaN(aInt)) {
			return ((parseInt(bVal) - aInt) * reverse);
		} else if (bVal > aVal) {
			return -1 * reverse;
		}
		return ((aVal === bVal) ? 0 : 1) * reverse;
	}

	return objects.toArray().sort(sortingByAttr);
};

globalThis.sortButtonsByDevotion = function() {
	let $sortedButtons = $('#qlWrapper button').remove();
	$sortedButtons = sortDomObjects($sortedButtons, 'data-devotion');
	$($sortedButtons).appendTo($('#qlWrapper'));
	quickListBuildLinks();
};

globalThis.sortButtonsByTrust = function() {
	let $sortedButtons = $('#qlWrapper button').remove();
	$sortedButtons = sortDomObjects($sortedButtons, 'data-trust');
	$($sortedButtons).appendTo($('#qlWrapper'));
	quickListBuildLinks();
};

globalThis.quickListBuildLinks = function() {
	$("[data-scroll-to]").click(App.UI.quickBtnScrollToHandler);
};

App.UI.quickBtnScrollToHandler = function() {
	let $this = $(this);
	let $toElement = $this.attr('data-scroll-to');
	// note the * 1 enforces $offset to be an integer, without
	// it we scroll to True, which goes nowhere fast.
	let $offset = parseInt($this.attr('data-scroll-offset') || "0");
	let $speed = parseInt($this.attr('data-scroll-speed') || "500");
	// Use javascript scrollTop animation for in page navigation.
	$('html, body').animate({
		scrollTop: $($toElement).offset().top + $offset
	}, $speed);
};

globalThis.sortNurseryPossiblesByName = function() {
	let $sortedNurseryPossibles = $('#ql-nursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-name');
	$($sortedNurseryPossibles).appendTo($('#ql-nursery'));
};

globalThis.sortNurseryPossiblesByPregnancyWeek = function() {
	let $sortedNurseryPossibles = $('#ql-nursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-preg-week');
	$($sortedNurseryPossibles).appendTo($('#ql-nursery'));
};

globalThis.sortNurseryPossiblesByPregnancyCount = function() {
	let $sortedNurseryPossibles = $('#ql-nursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-preg-count');
	$($sortedNurseryPossibles).appendTo($('#ql-nursery'));
};

globalThis.sortNurseryPossiblesByReservedSpots = function() {
	let $sortedNurseryPossibles = $('#ql-nursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-reserved-spots');
	$($sortedNurseryPossibles).appendTo($('#ql-nursery'));
};

globalThis.sortNurseryPossiblesByPreviousSort = function() {
	let sort = V.sortNurseryList;
	if (sort !== 'unsorted') {
		if (sort === 'Name') {
			sortNurseryPossiblesByName();
		} else if (sort === 'Reserved Nursery Spots') {
			sortNurseryPossiblesByReservedSpots();
		} else if (sort === 'Pregnancy Week') {
			sortNurseryPossiblesByPregnancyWeek();
		} else if (sort === 'Number of Children') {
			sortNurseryPossiblesByPregnancyCount();
		}
	}
};


code:
/**
 * @param {{ rivalry: number }} actor
 * @returns {string}
 */
globalThis.rivalryTerm = function(actor) {
	if (actor.rivalry === 1) {
		return "growing rival";
	} else if (actor.rivalry === 2) {
		return "rival";
	} else {
		return "bitter rival";
	}
};

/**
 * @param {{ relationship: number, pronoun: number }} actor
 * @returns {string}
 */
globalThis.relationshipTerm = function(actor) {
	if (actor.relationship === 1) {
		return "friend";
	} else if (actor.relationship === 2) {
		return "best friend";
	} else if (actor.relationship === 3) {
		return "friend with benefits";
	} else if (actor.relationship === 4) {
		return "lover";
	} else {
		return `slave ${getPronouns(actor).wife}`;
	}
};

/**
 * @param {{ relationship: number, pronoun: number }} actor
 * @returns {string}
 */
globalThis.relationshipTermShort = function(actor) {
	if (actor.relationship === 1) {
		return "friend";
	} else if (actor.relationship === 2) {
		return "BFF";
	} else if (actor.relationship === 3) {
		return "FWB";
	} else if (actor.relationship === 4) {
		return "lover";
	} else {
		return `${getPronouns(actor).wife}`;
	}
};

/**
 * @param {{ relationship: number, pronoun: number }} id
 * @returns {string}
 */
globalThis.PCrelationshipTerm = function(id) {
	if (id.relationship === -2) {
		return "lover";
	} else if (id.relationship === -3) {
		return `${getPronouns(id).wife}`;
	}
};

/**
 * Introduces an actor by using any meaningful relationship(s) with an already on-screen actor, and their name.
 * Returns strings like: "your husband John", "his growing rival and mother Alice", or "her best friend and twin sister Carla".
 * If there is no known relationship between them, returns the name alone.
 * Use this function instead of just printing the slave's name when you'd like to let the player to know if two actors are related,
 * even though it's not going to have any mechanical impact on the scene.
 * @param {FC.HumanState} context
 * @param {FC.HumanState} actor
 * @param {boolean} [asLink] When true, instead of using the slave's first name, use their full name with a link to the slave description dialog.
 * @param {boolean} [insertComma] When true, if a relationship is found, it will be separated from the actor's name by a comma ("her father, Dave" instead of "her father Dave")
 * @param {boolean} [capitalize] When true, capitalizes the first letter of the intro. Use if called at the beginning of a sentence.
 * @returns {string|DocumentFragment}
 */
globalThis.contextualIntro = function(context, actor, asLink = false, insertComma = false, capitalize = false) {
	let r = ``;
	const preamble = (context === V.PC) ? "your" : getPronouns(context).possessive;

	let terms = [];
	const contextSlave = asSlave(context);
	const actorSlave = asSlave(actor);
	if (contextSlave && contextSlave.relationship > 0 && contextSlave.relationshipTarget === actor.ID) {
		terms.push(relationshipTerm(contextSlave));
	} else if (context === V.PC && actorSlave && actorSlave.relationship < -1) {
		terms.push(PCrelationshipTerm(actorSlave));
	} else if (actor === V.PC && contextSlave && contextSlave.relationship < -1) {
		terms.push(PCrelationshipTerm(contextSlave));
	} else if (contextSlave && contextSlave.rivalry > 0 && contextSlave.rivalryTarget === actor.ID) {
		terms.push(rivalryTerm(contextSlave));
	}

	terms = terms.concat(relativeTerms(context, actor));

	if (terms.length > 0) {
		r = (capitalize ? capFirstChar(preamble) : preamble) + " " + toSentence(terms);
	}

	if (r !== ``) {
		r += (insertComma || actor === V.PC) ? ", " : " ";
	}

	if (asLink && actorSlave) {
		const frag = document.createDocumentFragment();
		frag.append(r, App.UI.DOM.slaveDescriptionDialog(actorSlave));
		return frag;
	} else {
		r += actor === V.PC ? "you" : actor.slaveName;
		return r;
	}
};