code:
/**
 * @param {FC.HumanState} 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 {FC.HumanState} 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 {FC.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 {FC.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 {FC.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 {FC.HumanState} 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 {FC.HumanState} 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 {FC.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 {FC.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 {FC.HumanState} 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 {FC.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 {FC.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 {FC.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 {FC.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 === 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 (V.PC.health.condition < -20 || V.PC.health.illness > 1 || onBedRest(V.PC, true)) {
		V.personalAttention = {task: PersonalAttention.RELAX};
	} else if (isHorny(V.PC)) {
		V.personalAttention = {task: PersonalAttention.SEX};
	} else if (isPCCareerInCategory("escort")) {
		V.personalAttention = {task: PersonalAttention.WHORING};
	} else if (isPCCareerInCategory("servant")) {
		V.personalAttention = {task: PersonalAttention.MAID};
	} else {
		V.personalAttention = {task: PersonalAttention.BUSINESS};
	}
};

/**
 * @template {string} In
 * @param {In} input
 * @returns {FC.PersonalAttentionTrainingObjective|In}
 */
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 "train her":
			return "combat training";
		case "spar with her":
			return "spar";
		case "explore her sexuality":
			return "explore sexuality";
		case "manage your libido":
			return "ravish";
		case "sate your lust":
			return "ravished";
		case "torture her":
			return "torture";
	}
	// settings for inducing flaws/paraphilias are unchanged
	return input;
};

/**
	* @param {FC.PersonalAttentionTrainingObjective} objective
	* @param {FC.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 "combat training":
			return `teach ${him} combat`;
		case "spar":
			return `spar with ${him}`;
		case "explore sexuality":
			return `explore ${his} sexuality`;
		case "ravish":
			return `manage your libido`;
		case "ravished":
			return `sate your lust`;
		case "torture":
			return `torture ${him}`;
	}
	// settings for inducing flaws/paraphilias can be printed as-is
	return objective;
};

/**
 * @param {FC.HumanID} ID
 * @param {FC.PersonalAttentionTrainingObjective} objective
 * @returns {boolean}
 */
globalThis.getPersonalAttention = function(ID, objective = null) {
	const tasks = ["soften behavioral flaw", "fix behavioral flaw", "soften sexual flaw", "fix sexual flaw", "break will", "harshly break will", "build devotion", "health", "learn skills", "combat training", "spar", "explore sexuality"]; // Not counting rutting and torture as training.
	if (typeof V.personalAttention.slaves === "undefined") {
		return false;
	} else if (!ID) {
		if (!objective) {
			return false;
		} else if (objective === "training") {
			return (tasks.includes(V.personalAttention.slaves[0].objective) || tasks.includes(V.personalAttention.slaves[1]?.objective));
		} else {
			return (V.personalAttention.slaves[0].objective === objective || V.personalAttention.slaves[1]?.objective === objective);
		}
	} else {
		if (!objective) {
			return (V.personalAttention.slaves[0].ID === ID || V.personalAttention.slaves[1]?.ID === ID);
		} else if (objective === "training") {
			return ((V.personalAttention.slaves[0].ID === ID && tasks.includes(V.personalAttention.slaves[0].objective)) || (V.personalAttention.slaves[1]?.ID === ID && tasks.includes(V.personalAttention.slaves[1]?.objective)));
		} else {
			return ((V.personalAttention.slaves[0].ID === ID && V.personalAttention.slaves[0].objective === objective) || (V.personalAttention.slaves[1]?.ID === ID && V.personalAttention.slaves[1]?.objective === objective));
		}
	}
};

/**
 * @param {FC.HumanID} ID
 * @returns {"none" | FC.PersonalAttentionTrainingObjective}
 */
globalThis.getPersonalAttentionType = function(ID) {
	if (typeof V.personalAttention.slaves === "undefined") {
		return "none";
	} else {
		if (V.personalAttention.slaves[0].ID === ID) {
			return V.personalAttention.slaves[0].objective;
		} else if (V.personalAttention.slaves[1]?.ID === ID) {
			return V.personalAttention.slaves[1].objective;
		}
	}
	return "none";
};

/** @returns {boolean} */
globalThis.getSharedPersonalAttention = function() {
	if (typeof V.personalAttention.slaves === "undefined") {
		return false;
	} else {
		return V.personalAttention.slaves[0].objective === V.personalAttention.slaves[1]?.objective;
	}
};

repo_name: Nithhogg/fc

license: mit


code:
App.PersonalLog = {
	/**
	 * Opens the Personal Log dialog
	 */
	dialog: function dialog() {
		if (Dialog.isOpen()) {
			Dialog.close();
		}
		Dialog.create("Personal Log", "personal-log-dialog");
		Dialog.append(`<p>You can write your entries using <a href="https://www.markdownguide.org/cheat-sheet/" target="_blank" rel="noopener noreferrer" class="link-external">Markdown</a>.</p>`);
		let logEditor = App.UI.DOM.makeElement("textarea", "", ["log-entry-editor"]);
		logEditor.placeholder = "Add a new log entry";
		Dialog.append(logEditor);

		Dialog.append(App.UI.DOM.makeElement("div", App.UI.DOM.link("Save", () => {
			if (logEditor.dataset.index && logEditor.value) {
				V.personalLog[logEditor.dataset.index].log = logEditor.value;
				V.personalLog[logEditor.dataset.index].edited = new Date().toISOString();
			} else if (logEditor.value) {
				V.personalLog.push({
					"timestamp": new Date().toISOString(),
					"edited": null,
					"log": logEditor.value
				});
			}

			// The passage reload will force Twine to save state, if the passage is safe to jump to/from.
			// In unsafe passages (weekend, events, recruitments, slave passages, etc.), there will be a risk
			// to lose modifications if the browser is closed or the page reloads, even if a manual save is made.
			if (getPassagesWithTag("jump-from-safe").map(passage => passage.title).includes(passage()) &&
				getPassagesWithTag("jump-to-safe").map(passage => passage.title).includes(passage())) {
				App.UI.reload();
			}
			dialog();
		}), ["log-entry-save"]));

		let dl = App.UI.DOM.makeElement("dl", null, null);
		for (const [i, log] of V.personalLog.entries()) {
			let dt = App.UI.DOM.makeElement("dt", null, null);
			dt.append(App.UI.DOM.makeElement("span", new Date(Date.parse(log.timestamp)).toLocaleString(), ["log-entry-created"]));
			if (log.edited) {
				dt.append(App.UI.DOM.makeElement("small", `Edited ${new Date(Date.parse(log.edited)).toLocaleString()}`, ["log-entry-edited"]));
			}
			dt.append(App.UI.DOM.generateLinksStrip([
				App.UI.DOM.link("Edit", () => {
					logEditor.value = log.log;
					logEditor.dataset.timestamp = log.timestamp;
					logEditor.dataset.index = i.toString();
				}),
				App.UI.DOM.link("Delete", () => {
					V.personalLog.splice(i, 1);
					dialog();
				})
			]));
			dl.append(dt);
			let dd = App.UI.DOM.makeElement("dd", null, null);
			// @ts-ignore
			dd.innerHTML = marked.parse(log.log);
			dl.append(dd);
		}
		Dialog.append(dl);

		Dialog.open();
	}
};


code:
// cSpell:ignore maiesiophiliacs

/**
 * @typedef {object} PornGenreType
 * @property {number} focusedViewershipFactor
 * @property {number} unfocusedViewershipFactor
 * @property {number} viewershipSoakingFactor
 * @property {(slave: FC.SlaveState) => number} bonusViewership
 * @property {string} name
 */

/**
 * @typedef {object} PornGenre
 * @property {string} fameVar
 * @property {string} fameName
 * @property {string} focusName
 * @property {PornGenreType} type
 * @property {string} prestigeDesc1
 * @property {string} prestigeDesc2
 * @property {string} prestigeDesc3
 * @property {(slave: FC.SlaveState) => string} hitText
 * @property {(slave: FC.SlaveState) => string} trinketShotDesc
 * @property {(slave: FC.SlaveState) => boolean} valid
 * @property {() => string} uiName
 */

/** @type {{[key: string]: PornGenreType}} */
App.Porn.GenreType = {
	paraphilia: {
		focusedViewershipFactor: 1.5,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 0.0,
		// eslint-disable-next-line jsdoc/require-jsdoc
		bonusViewership: function(slave) { return slave.fetishStrength * 2.0; },
		name: "paraphilia"
	},
	fetish: {
		focusedViewershipFactor: 2.0,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 1.0,
		// eslint-disable-next-line jsdoc/require-jsdoc
		bonusViewership: function(slave) { return slave.fetishStrength; },
		name: "fetish"
	},
	general: {
		focusedViewershipFactor: 4.0,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 1.0,
		// eslint-disable-next-line jsdoc/require-jsdoc
		bonusViewership: function(slave) { return 0.0; },
		name: "general"
	},
	quirk: {
		focusedViewershipFactor: 6.0,
		unfocusedViewershipFactor: 0.5,
		viewershipSoakingFactor: 1.0,
		// eslint-disable-next-line jsdoc/require-jsdoc
		bonusViewership: function(slave) { return 0.0; },
		name: "quirk"
	},
	generic: {
		focusedViewershipFactor: 5.0,
		unfocusedViewershipFactor: 1.0,
		viewershipSoakingFactor: 0.0,
		// eslint-disable-next-line jsdoc/require-jsdoc
		bonusViewership: function(slave) { return 0.0; },
		name: "generic"
	}
};

/** @type {{[key: string]: PornGenre}} */
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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} pleasing a line of partners without cumming once`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "neglectful"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} bathing in a tub of cum`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "cum addict"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} taking a series of huge cocks up ${getPronouns(slave).his} ass`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "anal addict" && canDoAnal(slave); },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} flashing strangers`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "attention whore"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} having ${getPronouns(slave).his} tits measured`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "breast growth"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} wanton enjoyment of pleasure through force amuses viewers that enjoy rape and abuse.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} taking what ${getPronouns(slave).he} wants by force`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "abusive"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} tormenting ${getPronouns(slave).his} prey`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "malicious"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} orgasming from pain`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "self hating"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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`;
		}
	},
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualFlaw === "breeder"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `With ${getPronouns(slave).his} submissive streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} submission`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === Fetish.SUBMISSIVE; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} drinking a glass of cum`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "cumslut"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} shaking ${getPronouns(slave).his} booty`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "buttslut"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `With ${getPronouns(slave).his} fetish for humiliation, ${getPronouns(slave).he} has a clear advantage when it comes to demeaning smut.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} humiliated in public`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "humiliation"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} bare chest`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "boobs"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `With ${getPronouns(slave).his} dominant streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} seated upon ${getPronouns(slave).his} obedient toy`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "dom"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `With ${getPronouns(slave).his} sadistic streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} whipping ${getPronouns(slave).his} lover`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "sadist"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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 streak",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `With ${getPronouns(slave).his} masochistic streak, ${getPronouns(slave).he} has a clear advantage when it comes to fetish smut.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} being whipped`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "masochist"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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`;
		}
	},
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fetish === "pregnancy"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} latex encased body attracts a variety of viewers with tastes ranging from bondage to dolls.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing it offering itself`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.fuckdoll > 0; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} rape`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return (slave.devotion < -20) && (slave.counter.anal + slave.counter.vaginal > 0); },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} gravid swell may be a turn off to some, but the maiesiophiliacs love it.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} cradling ${getPronouns(slave).his} middle`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.bellyPreg > 500; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} weight gives ${getPronouns(slave).him} a heavy allure to the chubby chasers and BBW lovers out there.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} giving a bellyjob`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.weight > 95; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} young age gives ${getPronouns(slave).him} a dangerous edge and a number of careful viewers.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} 'innocence'`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.visualAge <= 12; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} trying on ${getPronouns(slave).his} old clothes`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return (slave.weight > 30 && slave.diet === "fattening") || (slave.inflation > 0 && slave.inflationType === "food"); },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} powerful erection excites those who see it, especially when it is put to good use.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} money shot`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return canPenetrate(slave) && slave.dick > 3; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} powerful muscles and bodybuilder physique attracts a dedicated audience.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} holding ${getPronouns(slave).his} partner in the air during sex`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.muscles > 80; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`;
		}
	},
	// eslint-disable-next-line jsdoc/require-jsdoc
	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`;
		}
	},
	// eslint-disable-next-line jsdoc/require-jsdoc
	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
		);
	},
	// eslint-disable-next-line jsdoc/require-jsdoc
	uiName: function() { return capFirstChar(this.fameName); }
};

App.Porn.Genre.fucker = {
	fameVar: "fucker",
	fameName: "penetrative",
	focusName: "penetrative",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed witnessing $his mastery of drilling vaginas and rectums",
	prestigeDesc2: "$His many fans relish learning new techniques from watching $him masterfully penetrate holes",
	prestigeDesc3: "Millions are intimately familiar with $his masterful technique at pleasing others by pounding their pussy or butthole",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).He} hypnotizes ${getPronouns(slave).his} audience with the flawless technique ${getPronouns(slave).he} uses when penetrating any bodily orifice.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} driving ${getPronouns(slave).his} partner crazy with the pleasure granted by ${getPronouns(slave).his} masterful cock.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return canPenetrate(slave) && slave.dick > 2 && slave.skill.penetrative >= 100; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	uiName: function() { return capFirstChar(this.fameName); }
};

App.Porn.Genre.clitFucker = {
	fameVar: "clitFucker",
	fameName: "clitoral penetrative",
	focusName: "dickclit",
	type: App.Porn.GenreType.general,
	prestigeDesc1: "Thousands have enjoyed watching $him drill vaginas and rectums with nothing but $his clit",
	prestigeDesc2: "$His many fans relish the sight of $him fucking holes with $his clit",
	prestigeDesc3: "Millions are intimately familiar with the sight of $his clit pounding holes and putting dicks to shame",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).He} amazes viewers with just how well a clitoris can be used in place of a dick.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} prominently erect clit.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.chastityVagina === 0 && slave.clit >= 3 && slave.skill.penetrative >= 60; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).He} impresses with just how much dick can slip down ${getPronouns(slave).his} throat.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} getting facefucked`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "gagfuck queen"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).He} impresses with ${getPronouns(slave).his} ability to put up just the right amount of fight during sex.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} struggling`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "strugglefuck queen" && (canDoVaginal(slave) || canDoAnal(slave)); },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).He} impresses with ${getPronouns(slave).his} ability to push ${getPronouns(slave).his} anus to its limit.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} taking an enormous dick up ${getPronouns(slave).his} ass`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "painal queen" && canDoAnal(slave); },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).He} tantalizes viewers with ${getPronouns(slave).his} suggestive moves.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} stripping`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "tease"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).He} draws viewers in with ${getPronouns(slave).his} genuine pleasure.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} sharing an orgasm with ${getPronouns(slave).his} partner`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "romantic"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return `${getPronouns(slave).His} perverted tastes shock and allure viewers in ways they didn't even realize.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} doing something perverted`; }, /* FIXME: really, that's the best we could come up with??? */
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "perverted"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} bringing ${getPronouns(slave).his} partner to orgasm`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "caring"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} doing something unmentionable`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "unflinching"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	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.`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} belly bulging from within`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return slave.sexualQuirk === "size queen"; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	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",
	// eslint-disable-next-line jsdoc/require-jsdoc
	hitText: function(slave) { return ``; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} mid-coitus`; },
	// eslint-disable-next-line jsdoc/require-jsdoc
	valid: function(slave) { return true; /* anyone can do generic porn */ },
	// eslint-disable-next-line jsdoc/require-jsdoc
	uiName: function() { return "Smut is smut"; }
};

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

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

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

/**
 * Returns all the genres with a given type.
 * @param {PornGenre["type"]} type
 * @returns {PornGenre[]}
 */
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 {FC.SlaveState} slave
 * @param {function(FC.SlaveState) : void} callback
 * @returns {HTMLElement}
 */
App.Porn.genreChoiceLinks = function(slave, callback) {
	let makeLink = (genre) => {
		if (slave.porn.focus === genre.focusName) {
			return App.UI.DOM.disabledLink(genre.uiName(), [`Current focus`]);
		} else {
			return App.UI.DOM.link(genre.uiName(), (s, g) => {
				s.porn.focus = g.focusName;
				if (callback) {
					callback(s);
				}
			}, [slave, genre]);
		}
	};
	let links = this.getAllGenres().filter((g) => g.valid(slave)).map(makeLink);
	if (slave.porn.focus === "none") {
		links.push(App.UI.DOM.disabledLink("No focus", [`Already has no focus`]));
	} else {
		links.push(App.UI.DOM.link("No focus", (s) => {
			s.porn.focus = "none";
			if (callback) {
				callback(s);
			}
		}, [slave]));
	}
	return App.UI.DOM.generateLinksStrip(links);
};