code:
/**
 * UI for the Body Modification system/studio. Refreshes without refreshing the passage.
 * @param {App.Entity.SlaveState} slave
 * @param {boolean} cheat if true, will hide scenes, prevent damage to slaves, and keep the player from being billed for mods.
 */
App.UI.bodyModification = function(slave, cheat = false) {
	const container = document.createElement("span");
	container.id = "body-modification";
	const {
		He, His,
		he, his, him, himself
	} = getPronouns(slave);
	let piercingLevel;
	let modReaction = "";
	/** @type {string|0} */
	let tattooChoice = "";
	let scarApplied = false;
	let brandApplied = false;
	let degradation = 0;

	container.append(createPage());
	return container;

	function createPage() {
		const el = new DocumentFragment();
		if (!cheat) {
			App.Events.drawEventArt(el, slave);
			el.append(intro());
			el.append(reaction());
		}
		el.append(piercings());
		el.append(tattoos());
		el.append(branding());
		el.append(scar());
		return el;
	}

	function intro() {
		const el = new DocumentFragment();
		App.UI.DOM.appendNewElement("h1", el, "Body Modification Studio");
		App.UI.DOM.appendNewElement("div", el, `${SlaveFullName(slave)} is lying strapped down on the table in your body modification studio. ${He} is entirely at your mercy.`, "scene-intro");
		return el;
	}

	function reaction() {
		const el = new DocumentFragment();
		let r = [];
		if (brandApplied || degradation || scarApplied || modReaction) {
			if (slave.fuckdoll === 0) {
				if (canSee(slave)) {
					r.push(`There's a mirror on the ceiling, so ${he} can see`);
				} else {
					r.push(`${He} can't see, so `);
					if (canHear(slave)) {
						r.push(`you're careful to describe`);
					} else {
						r.push(`${he} must, by ${himself}, get a feel for`);
					}
				}
				r.push(`${his} new appearance.`);
			}
			if (brandApplied) {
				r.push(`The smell of burnt flesh hangs in the air. Being branded <span class="health dec">has hurt ${his} health a little.</span>`);
				healthDamage(slave, 10);
				brandApplied = false;
			}
			if (scarApplied) {
				if (V.scarTarget.local === "entire body") {
					switch (V.scarDesign.local) {
						case "burn":
							r.push(`Your goal wasn't to make the distinct shape of a brand, but rather to permanently mar the skin with an open flame.`);
							break;
						case "surgical":
							if (V.PC.skill.medicine >= 100) {
								r.push(`Your medical mastery is perfect, so creating Frankenstein's monster was a deliberate work of art.`);
							} else if (V.PC.skill.medicine > 0) {
								r.push(`Your medical skills are progressing, and the Frankenstein effect reminds you of your earliest attempts.`);
							} else {
								r.push(`You really slashed away with your knife, but were careful not to allow ${him} to bleed out.`);
							}
							break;
						default:
							r.push(`The best way to apply scarring to the entire body is with a good old fashioned whip. ${His} body is a mess of crisscrossed `);
							if (hasAnyNaturalLimbs(slave)) {
								r.push(`lines, and ${his} `);
								if (getLimbCount(slave, piercingLevel) > 1) {
									r.push(`limbs twisted so violently in their restraints that they too have`);
								} else {
									r.push(`only limb twists so violently in its restraints that it too has`);
								}
								r.push(`become scarred.`);
							} else {
								r.push(`lines.`);
							}
					}
					r.push(`No matter how you chose to apply it, scarring so much of ${his} body has <span class="health dec"> hurt ${his} health.</span>`);
					healthDamage(slave, 20);
				} else {
					if (slave.scar[V.scarTarget.local][V.scarDesign.local] > 0) {
						r.push(`This is not the first time ${he} was scarred like this.`);
					}
					switch (V.scarDesign.local) {
						case "whip":
							r.push(`Targeting a single area with a whip is not easy. You set the mood by carefully arranging candles dripping on to a whimpering ${slave.slaveName}, then got ${his} attention with a quick`);
							if (canSee(slave)) {
								r.push(`wave`);
							} else if (canHear(slave)) {
								r.push(`crack`);
							} else {
								r.push(`tap`);
							}
							r.push(`of the whip. One by one, you carefully snuffed out the candles, flicking hot wax as you went. After pausing a moment, you prepared to leave your mark.`);
							if (["penis", "vagina"].includes(V.scarTarget.local)) {
								if (slave.dick > 4 && V.seeDicks) {
									r.push(`${His} dick was large enough that it was not too difficult to hit,`);
								} else if (slave.dick > 0 && V.seeDicks) {
									r.push(`${His} dick was a challengingly small target,`);
								} else {
									if (slave.clit > 0) {
										r.push(`${His} clit was a difficult target,`);
									} else {
										r.push(`${His} clit was an impossibly tiny target,`);
									}
								}
								r.push(`but the end was never in doubt. The tip connected with ${his} most intimate place on the first try, and plunged ${him} into absolute agony.`);
							} else {
								r.push(`The	end was never in doubt. A few strokes of the whip plunged ${him} into agony ${his} body will not allow ${him} to forget.`);
							}
							break;
						case "burn":
							r.push(`Your goal wasn't to make the distinct shape of a brand, but rather to permanently mar the ${slave.skin} skin of ${his} ${V.scarTarget.local} with an open flame.`);
							break;
						case "surgical":
							if (V.PC.skill.medicine >= 100) {
								r.push(`Your medical mastery is perfect, so creating such a scar was a deliberate act of degradation.`);
							} else if (V.PC.skill.medicine > 0) {
								r.push(`Your medical skills are progressing, and the sloppy scar reminds you of your earliest attempts.`);
							} else {
								r.push(`You really slashed away at ${V.scarTarget.local} with your knife, but were careful not to allow ${him} to bleed out.`);
							}
							break;
						default:
							r.push(`You had no shortage of kinky and medical tools for applying scars. ${His} ${slave.skin} ${V.scarTarget.local} is bleeding profusely.`);
					}

					r.push(`No matter how you chose to apply it, being scarred <span class="health dec"> hurt ${his} health a little.</span>`);
					healthDamage(slave, 10);
				}
				r.push(`Afterwards you seal the wounds with a white medical spray. Infection is no risk to ${slave.slaveName} thanks to your curatives, but in order to form obvious scar tissue you had to keep air out and delay normal healing as long as possible.`);
				scarApplied = false;
			}
			if (slave.fetish !== "mindbroken" && slave.fuckdoll === 0) {
				if (degradation > 1) {
					if (degradation > 5) {
						if (slave.devotion <= 50 && slave.trust < -50) {
							r.push(`${He} is appalled by the whorish spectacle you have made of ${him}. ${He} <span class="gold">fears</span> you all the more for this but is so terrified of you it does not affect ${his} submission.`);
							slave.trust -= 10;
						} else if (slave.devotion <= 50) {
							r.push(`${He} is appalled by the whorish spectacle you have made of ${him}. ${He} <span class="mediumorchid">hates</span> and <span class="gold">fears</span> you for this.`);
							slave.devotion -= 10;
							slave.trust -= 10;
						} else {
							r.push(`${He} is shocked by the whorish spectacle you have made of ${him}. However, ${he} is so submissive to your will that ${he} <span class="hotpink">accepts</span> that the slave `);
							if (canSee(slave)) {
								r.push(`in the mirror`);
							} else {
								r.push(`${he} pictures`);
							}
							r.push(`is who ${he} is now.`);
							slave.devotion += 4;
						}
					} else {
						if (slave.devotion < -20 && slave.trust < 20) {
							r.push(`${He} is <span class="gold">afraid</span> that ${he} has been permanently altered against ${his} will, but is also scared of your reaction to any objection and suppresses ${his} disgust.`);
							slave.trust -= 5;
						} else if (slave.devotion < -20) {
							r.push(`${He} is <span class="mediumorchid">angry</span> and <span class="gold">afraid</span> that ${he} has been permanently altered against ${his} will.`);
							slave.devotion -= 5;
							slave.trust -= 5;
						} else {
							r.push(`${He} is saddened to have been altered against ${his} will. However, ${he} realizes that ${he} is a slave, so ${he} <span class="hotpink">accepts</span> your work.`);
							slave.devotion += 2;
						}
					}
					degradation = 0;
				}
				if (modReaction) {
					r.push(modReaction);
				}
			}
			modReaction = "";
		}
		App.Events.addNode(el, r, "p");
		return el;
	}

	function piercings() {
		const el = new DocumentFragment();
		let r = [];
		const piercingLocations = ["ear", "nose", "eyebrow", "lips", "tongue", "nipples", "areolae", "navel", "corset", "clit", "vagina", "dick", "anus"];
		// DESCRIPTIONS
		App.UI.DOM.appendNewElement("h2", el, "Piercings");

		for (const piercing of piercingLocations.concat(["chastity"])) {
			if (piercing === "nipples") {
				r.push(App.UI.DOM.makeElement("div", App.Desc.piercing(slave, "nipple"))); // This is stupid, but the variable is slave.nipplesPiercing, plural nipples, but in almost all other places we refer to plural body parts as singular (eyebrow, ear, nose, etc).
			} else {
				r.push(App.UI.DOM.makeElement("div", App.Desc.piercing(slave, piercing)));
			}
		}
		if (r.length === 0) {
			r.push(App.UI.DOM.makeElement("div", `${His} smooth ${slave.skin} skin is completely unpierced.`));
		}
		App.Events.addNode(el, r);
		r = [];

		// Apply piercings
		r.push(`Choose piercing style:`);
		const piercingLevelNames = new Map([
			["Remove", 0],
			["Light", 1],
			["Heavy", 2],
			["Smart", 3]
		]);
		let linkArray = [];
		for (const [title, num] of piercingLevelNames) {
			if (piercingLevel === num) {
				linkArray.push(App.UI.DOM.disabledLink(title, ["Currently selected"]));
			} else {
				linkArray.push(
					App.UI.DOM.link(
						title,
						() => {
							piercingLevel = num;
							refresh();
						}
					)
				);
			}
		}
		r.push(App.UI.DOM.generateLinksStrip(linkArray));
		App.Events.addNode(el, r, "div");
		r = [];

		// Determine parts that cannot be pierced
		let validPiercingLocations = Array.from(piercingLocations);

		if (piercingLevel !== 0) { // Sometimes a piercing winds up in a place that is no longer valid. Make sure players can always remove an existing piercing.
			if (slave.nipples === "fuckable") {
				removePiercingLocation("nipples");
			}

			if (slave.vagina === -1) {
				removePiercingLocation("vagina");
			}

			if (slave.dick === 0) {
				removePiercingLocation("dick");
				if (slave.vagina === -1) {
					removePiercingLocation("clit");
				}
			}
		}

		function removePiercingLocation(location) {
			const index = validPiercingLocations.indexOf(location);
			validPiercingLocations.splice(index, 1);
		}

		if (piercingLevel < 3) {
			if (piercingLevel === 0) {
				r.push(`Remove piercings from:`);
			} else if (piercingLevel === 1) {
				r.push(`Lightly pierce ${his}:`);
			} else if (piercingLevel === 2) {
				r.push(`Heavily pierce ${his}:`);
			}
			// Entire body
			linkArray = [];
			linkArray.push(
				App.UI.DOM.link(
					"Entire body",
					() => {
						for (const location of validPiercingLocations) {
							if (slave[`${location}Piercing`] !== piercingLevel) {
								modReaction += App.Medicine.Modification.setPiercing(slave, location, piercingLevel);
								billPiercing(piercingLevel);
								if (piercingLevel > 1) {
									degradation += 1;
								}
							}
						}
						refresh();
					}
				)
			);

			// Each individual piercing
			for (const location of validPiercingLocations) {
				if (slave[`${location}Piercing`] !== piercingLevel) {
					linkArray.push(
						App.UI.DOM.link(
							capFirstChar(location),
							() => {
								modReaction = App.Medicine.Modification.setPiercing(slave, location, piercingLevel);
								billPiercing(piercingLevel);
								if (piercingLevel > 1) {
									degradation += 1;
								}
								refresh();
							}
						)
					);
				}
			}
			r.push(App.UI.DOM.generateLinksStrip(linkArray));
		} else if (piercingLevel === 3) {
			// Smart piercings
			if (slave.vagina !== -1 || slave.dick !== 0) {
				if (slave.clitPiercing !== 3) {
					r.push(`Give ${him} a`);
					r.push(
						App.UI.DOM.link(
							"smart piercing",
							() => {
								modReaction = App.Medicine.Modification.setPiercing(slave, "clit", 3);
								billPiercing(3);
								slave.clitSetting = "all";
								degradation += 1;
								refresh();
							},
							[],
							"",
							cheat ? "Unlocks options to mold sexuality" :
								`Costs ${cashFormat(V.SPcost)}, unlocks options to mold sexuality`
						)
					);
				} else {
					r.push(`${He} already has a smart piercing!`);
				}
			}
		}
		App.Events.addNode(el, r, "div");
		return el;
	}

	function tattoos() {
		const el = new DocumentFragment();
		let r = [];
		let noTats;
		const tattooLocations = new Map([
			["shoulder", "shoulders"],
			["lips", "lips"],
			["breast", "boobs"],
			["upper arm", "arms"],
			["back", "back"],
			["lower back", "stamp"],
			["buttock", "butt"],
			["vagina", "vagina"],
			["dick", "dick"],
			["anus", "anus"],
			["leg", "legs"]
		]);
		// DESCRIPTIONS
		App.UI.DOM.appendNewElement("h2", el, "Tattoos");

		for (let name of tattooLocations.keys()) {
			name = (name === "leg") ? "thigh" : name; // Leg/thigh is a weird naming exception for now.
			const desc = App.Desc.tattoo(slave, name);
			if (desc) {
				r.push(App.UI.DOM.makeElement("div", desc));
			}
		}
		if (r.length === 0) {
			r.push(App.UI.DOM.makeElement("div", `${His} smooth ${slave.skin} skin is completely unmarked.`));
			noTats = true;
		}
		App.Events.addNode(el, r);
		r = [];

		// Apply tattoos
		r.push(`Choose a tattoo style:`);
		const tattooChoiceNames = new Set([
			"tribal patterns",
			"flowers",
			"counting",
			"advertisements",
			"rude words",
			"degradation",
			"Asian art",
			"scenes",
			"bovine patterns",
			"permanent makeup",
			"sacrilege",
			"sacrament",
			"possessive",
			"paternalist",
		]);
		if (slave.vaginaTat === 0) {
			tattooChoiceNames.add("lewd crest");
		}
		if (slave.anusTat === 0) {
			tattooChoiceNames.add("bleached");
		}
		const sortedTattooChoiceNames = Array.from(tattooChoiceNames).sort((a, b) => a > b ? 1 : -1);
		let linkArray = [];
		for (const style of sortedTattooChoiceNames) {
			if (tattooChoice === style) {
				linkArray.push(App.UI.DOM.disabledLink(capFirstChar(style), ["Currently selected"]));
			} else {
				linkArray.push(
					App.UI.DOM.link(
						capFirstChar(style),
						() => {
							tattooChoice = style;
							refresh();
						}
					)
				);
			}
		}
		if (!noTats) {
			linkArray.push(
				App.UI.DOM.link(
					"Remove a tattoo",
					() => {
						tattooChoice = 0;
						refresh();
					}
				)
			);
		}
		r.push(App.UI.DOM.generateLinksStrip(linkArray));
		App.Events.addNode(el, r, "div");
		r = [];

		// Determine parts that cannot be marked
		let validTattooLocations;
		if (tattooChoice === "bleached") {
			validTattooLocations = ["anus"];
		} else if (tattooChoice === "permanent makeup") {
			validTattooLocations = ["lips"];
		} else if (tattooChoice === "lewd crest") {
			validTattooLocations = ["vagina"];
		} else {
			validTattooLocations = Array.from(tattooLocations.keys());
			if (!hasAnyNaturalArms(slave)) {
				removeTattooLocation("upper arm");
			}

			if (!hasAnyNaturalArms(slave)) {
				removeTattooLocation("thigh");
			}

			if (slave.dick === 0 || tattooChoice === "scenes") {
				removeTattooLocation("dick");
			}
			if ((tattooChoice === "Asian art" || tattooChoice === "scenes") && slave.anusTat === "bleached") { // leave existing bleached anus alone
				removeTattooLocation("anus");
			}
		}

		function removeTattooLocation(location) {
			const index = validTattooLocations.indexOf(location);
			validTattooLocations.splice(index, 1);
		}

		if (tattooChoice === 0) {
			r.push(`Clean the ink off of ${him}:`);
		} else if (tattooChoice === "counting") {
			r.push(`Add tallies of ${his} sexual exploits to ${him}:`);
		} else if (tattooChoice === "bleached") {
			r.push(`Bleach ${his}:`);
		} else if (tattooChoice) {
			r.push(`Add ${tattooChoice} to ${his}:`);
		}
		// Entire body
		linkArray = [];
		if (tattooChoice !== undefined && validTattooLocations.length > 1) {
			linkArray.push(
				App.UI.DOM.link(
					"Entire body",
					() => {
						for (const location of validTattooLocations) {
							if (slave[`${location}tattoo`] !== tattooChoice) {
								applyTat(location);
							}
						}
						refresh();
					}
				)
			);
		}
		// Each individual tattoo
		for (const location of validTattooLocations) {
			if (slave[`${tattooLocations.get(location)}Tat`] !== tattooChoice) {
				linkArray.push(
					App.UI.DOM.link(
						capFirstChar(location),
						() => {
							applyTat(location);
							refresh();
						}
					)
				);
			}
		}
		r.push(App.UI.DOM.generateLinksStrip(linkArray));
		App.Events.addNode(el, r, "div");

		el.append(oddTattoos());

		const customEl = document.createElement("div");
		customEl.id = "custom-el";
		customEl.append(
			App.UI.DOM.link(
				"Show custom tattoo locations",
				() => {
					jQuery("#custom-el").empty().append(customTats());
				}
			)
		);
		if (V.cheatMode && cheat) { // check both cheat gates so it does not show in SG.
			const options = new App.UI.OptionsGroup();
			options.addOption("Breeding mark", "breedingMark", slave)
				.addValue("On", 1).on()
				.addValue("Off", 0).off();
			el.append(options.render());
		}
		el.append(customEl);

		return el;

		function applyTat(location) {
			tattooChoice = (location === "lips" && tattooChoice === "scenes") ? "permanent makeup" : tattooChoice;
			modReaction += App.Medicine.Modification.setTattoo(slave, tattooLocations.get(location), tattooChoice, cheat);
			if (!["flowers", "paternalist", "tribal patterns", 0].includes(tattooChoice)) {
				degradation += 1;
			}
		}

		function oddTattoos() {
			const frag = new DocumentFragment();
			let linkArray = [];
			let r = [];

			// Has tat, display option to remove
			if (slave.bellyTat !== 0) {
				r.push(`${His} navel is tattooed with ${slave.bellyTat}.`);
				linkArray.push(
					App.UI.DOM.link(
						"Remove tattoos",
						() => {
							tattooChoice = 0;
							modReaction += App.Medicine.Modification.setTattoo(slave, "belly", tattooChoice, cheat);
							refresh();
						}
					)
				);
			}

			if (slave.belly >= 10000 && slave.bellyPreg < 450000 && slave.bellyFluid < 5000) {
				if (slave.bellyTat === 0) {
					const bellyTats = new Map([
						["Heart", "a heart"],
						["Star", "a star"],
						["Butterfly", "a butterfly"],
					]);
					r.push(`${He} has no navel tattoos.`);
					for (const [title, value] of bellyTats) {
						linkArray.push(
							App.UI.DOM.link(
								title,
								() => {
									tattooChoice = value;
									modReaction += App.Medicine.Modification.setTattoo(slave, "belly", tattooChoice, cheat);
									refresh();
								}
							)
						);
					}
				}
			} else if (slave.bellyPreg >= 450000) {
				r.push(`${His} middle is large and taut enough to be a suitable canvas for a navel focused tattoo, but ${his} brood is too active to permit the needle to do its work.`);
			} else if (slave.bellyFluid >= 10000) {
				r.push(`${His} middle is large and taut enough to be a suitable canvas for a navel focused tattoo, but the pressure applied to ${his} stomach will likely force ${him} to release its contents.`);
			} else {
				r.push(`${His} middle isn't large enough to be a suitable canvas for a navel focused tattoo.`);
			}
			r.push(App.UI.DOM.generateLinksStrip(linkArray));
			App.Events.addNode(frag, r, "div");

			r = [];
			linkArray = [];
			if (slave.birthsTat === -1) {
				r.push(`Have ${him} receive a tattoo each time ${he} gives birth.`);
				linkArray.push(
					App.UI.DOM.link(
						"Begin keeping track",
						() => {
							slave.birthsTat = 0;
							refresh();
						}
					)
				);
			} else {
				if (slave.birthsTat > 0) {
					if (slave.birthsTat > 1) {
						r.push(`${He} has a series of ${num(slave.birthsTat)} baby-shaped tattoos adorning ${his} stomach; one for each successful`);
						if (slave.pregKnown === 1) {
							r.push(`pregnancy and a temporary one for ${his} current pregnancy.`);
						} else {
							r.push(`pregnancy.`);
						}
					} else {
						r.push(`${He} has a single baby-shaped tattoo${(slave.pregKnown === 1) ? `, and one temporary one,` : ``} adorning ${his} stomach.`);
					}
					linkArray.push(
						App.UI.DOM.link(
							"Remove tattoos",
							() => {
								slave.birthsTat = -1;
								billMod();
								refresh();
							}
						)
					);
				} else if (slave.birthsTat === 0) {
					if (slave.pregKnown === 1) {
						r.push(`${He} has a single baby-shaped temporary tattoo adorning ${his} stomach.`);
						linkArray.push(
							App.UI.DOM.link(
								"Remove it",
								() => {
									slave.birthsTat = -1;
									refresh();
								}
							)
						);
					} else {
						r.push(`${He} is scheduled to receive a tattoo each time ${he} gives birth.`);
						linkArray.push(
							App.UI.DOM.link(
								"Cancel",
								() => {
									slave.birthsTat = -1;
									refresh();
								}
							)
						);
					}
				}
				if (slave.birthsTat !== slave.counter.births) {
					linkArray.push(
						App.UI.DOM.link(
							`Add tattoos to match (total) births (${slave.counter.births})`,
							() => {
								slave.birthsTat = slave.counter.births;
								billMod();
								refresh();
							}
						)
					);
				}
				if (slave.birthsTat > 1) {
					r.push(`Remove some marks:`);
					r.push(
						App.UI.DOM.makeTextBox(
							0,
							v => {
								if (slave.birthsTat > v) {
									slave.birthsTat -= v;
									if (v) {
										billMod();
									}
								}
								refresh();
							},
							true
						)
					);
				}
			}
			r.push(App.UI.DOM.generateLinksStrip(linkArray));
			App.Events.addNode(frag, r, "div");

			r = [];
			linkArray = [];
			if (slave.abortionTat === -1) {
				r.push(`Have ${him} receive a tattoo for each abortion or miscarriage ${he} has.`);
				linkArray.push(
					App.UI.DOM.link(
						"Begin keeping track",
						() => {
							slave.abortionTat = 0;
							refresh();
						}
					)
				);
			} else {
				if (slave.abortionTat > 0) {
					if (slave.abortionTat > 1) {
						r.push(`${He} has a series of ${num(slave.abortionTat)} crossed out baby-shaped tattoos${(slave.pregKnown === 1) ? `, and one uncrossed one,` : ``} adorning ${his} stomach.`);
					} else {
						r.push(`${He} has a single crossed out baby-shaped tattoo${(slave.pregKnown === 1) ? `, and one uncrossed one,` : ``} adorning ${his} stomach.`);
					}
					linkArray.push(
						App.UI.DOM.link(
							"Remove tattoos",
							() => {
								slave.abortionTat = -1;
								billMod();
								refresh();
							}
						)
					);
				} else if (slave.abortionTat === 0) {
					if (slave.pregKnown === 1) {
						r.push(`${He} has a single baby-shaped temporary tattoo adorning ${his} stomach.`);
						linkArray.push(
							App.UI.DOM.link(
								"Remove it",
								() => {
									slave.abortionTat = -1;
									refresh();
								}
							)
						);
					} else {
						r.push(`${He} is scheduled to receive a tattoo each time ${he} gets an abortion or miscarries.`);
						linkArray.push(
							App.UI.DOM.link(
								"Cancel",
								() => {
									slave.abortionTat = -1;
									refresh();
								}
							)
						);
					}
				}
				if (slave.abortionTat !== slave.counter.abortions) {
					linkArray.push(
						App.UI.DOM.link(
							`Add tattoos to match (total) abortion/miscarriage (${slave.counter.abortions})`,
							() => {
								slave.abortionTat = slave.counter.abortions;
								billMod();
								refresh();
							}
						)
					);
				}
				if (slave.abortionTat > 1) {
					r.push(`Remove some marks:`);
					r.push(
						App.UI.DOM.makeTextBox(
							0,
							v => {
								if (slave.abortionTat > v) {
									slave.abortionTat -= v;
									if (v) {
										billMod();
									}
								}
								refresh();
							},
							true
						)
					);
				}
			}

			r.push(App.UI.DOM.generateLinksStrip(linkArray));
			App.Events.addNode(frag, r, "div");
			return frag;
		}

		function customTats() {
			const frag = new DocumentFragment();
			App.UI.DOM.appendNewElement("h3", frag, "Custom Tattoos");
			const r = [];
			for (const location of validTattooLocations) {
				const varName = tattooLocations.get(location);
				if (varName) {
					r.push(App.UI.DOM.makeElement("div", `${capFirstChar(location)}: `));
					r.push(
						App.UI.DOM.makeElement(
							"div",
							App.UI.DOM.makeTextBox(
								slave[`${varName}Tat`],
								(v) => {
									modReaction += App.Medicine.Modification.setTattoo(slave, varName, v, cheat);
									refresh();
								}
							)
						)
					);
				}
			}

			r.push(App.UI.DOM.makeElement("div", `Custom: `));
			r.push(
				App.UI.DOM.makeElement(
					"div",
					App.UI.DOM.makeTextBox(
						slave.custom.tattoo,
						(v) => {
							slave.custom.tattoo = v;
							billMod();
							refresh();
						}
					)
				)
			);
			App.Events.addNode(frag, r, "div", "grid-2columns-auto");
			if (slave.custom.tattoo !== "") {
				frag.append(
					App.UI.DOM.link(
						"Remove custom Tattoo",
						() => {
							slave.custom.tattoo = "";
							billMod();
							refresh();
						}
					)
				);
			}
			return frag;
		}
	}

	function branding() {
		const el = new DocumentFragment();
		const selection = document.createElement("span");
		selection.id = "brand-selection";
		selection.append(brand());
		el.append(selection);

		if (slave.breedingMark === 1 && (V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
			const r = [];
			r.push(`${He} has an intricate tattoo on ${his} lower belly that suggests ${he} was made to be bred.`);
			r.push(
				App.UI.DOM.link(
					"Remove it",
					() => {
						slave.breedingMark = 0;
						refresh();
					}
				)
			);
			App.Events.addNode(el, r, "div");
		}
		return el;
	}

	function scar() {
		const el = new DocumentFragment();
		App.UI.DOM.appendNewElement("h2", el, "Scars");
		let r = [];
		const bodyPartData = App.Data.Slave.body.get(V.scarTarget.local);

		for (const scarName in slave.scar) {
			const scarDiv = document.createElement("div");
			scarDiv.append(`${His} ${scarName} is marked with ${App.Desc.expandScarString(slave, scarName)}: `);
			scarDiv.append(
				App.UI.DOM.link(
					"Remove Scar",
					() => {
						scarApplied = false;
						delete slave.scar[scarName];
						billSurgery();
						degradation -= 10;
						refresh();
					}
				)
			);
			r.push(scarDiv);
		}
		if (r.length > 0) {
			App.Events.addNode(el, r, "div");
		} else {
			App.UI.DOM.appendNewElement("div", el, `${His} skin is not scarred.`);
		}

		el.append(App.UI.scarSelect("local", slave, cheat));
		r = [];
		if (["exotic", "menacing"].includes(V.scarDesign.local) && !V.scarTarget.local.endsWith("cheek")) {
			r.push(`"${capFirstChar(V.scarDesign.local)}" scars can only be applied to cheeks.`);
		} else if (bodyPartData && bodyPartData.isPair) {
			r.push(`Choose a side to scar.`);
		} else {
			if (slave.scar.hasOwnProperty(V.scarTarget.local)) {
				if (slave.scar[V.scarTarget.local][V.scarDesign.local]) {
					r.push(`${He} already has ${V.scarDesign.local} scars on ${his} ${V.scarTarget.local}. You can make it worse.`);
				} else {
					// check how much scarring is on this part
					const scarTotalValue = (Object.values(slave.scar[V.scarTarget.local])).reduce((a, b) => a + b, 0);
					if (scarTotalValue) {
						r.push(`That would be a new kind of scar to add to the growing collection on ${his} ${V.scarTarget.local}. Life can always be worse for a slave.`);
					}
				}
			}
			r.push(
				App.UI.DOM.link(
					"Scar",
					() => {
						let scarArray;
						if (V.scarTarget.local === "entire body" && V.scarDesign.local.includes("whip")) {
							// Special case for whipping scene, produces two kinds of scars
							App.Medicine.Modification.addScourged(slave);
						} else {
							// Normal entire body scarring
							if (V.scarTarget.local === "entire body") {
								scarArray = ["left breast", "right breast", "back", "lower back", "left buttock", "right buttock"];
								if (getLeftArmID(slave) === 0) {
									scarArray.push("left upper arm");
								}
								if (getRightArmID(slave) === 0) {
									scarArray.push("right upper arm");
								}
								if (getLeftLegID(slave) === 0) {
									scarArray.push("left thigh");
								}
								if (getRightLegID(slave) === 0) {
									scarArray.push("right thigh");
								}
							} else { // Single scar
								scarArray = [V.scarTarget.local];
							}
							for (const scar of scarArray) {
								App.Medicine.Modification.addScar(slave, scar, V.scarDesign.local);
								degradation += 10;
							}
						}
						billMod();
						scarApplied = true;
						degradation += 10;
						refresh();
					}
				)
			);
			r.push(`with ${V.scarDesign.local} on the ${V.scarTarget.local}${(slave.scar[V.scarTarget.local]) ? `, adding to the scars that are already there?` : `.`}`);
		}
		App.Events.addNode(el, r, "div");

		return el;
	}

	function brand() {
		const el = new DocumentFragment();
		let p = document.createElement('p');
		let div = document.createElement('div');
		const bodyPartData = App.Data.Slave.body.get(V.brandTarget.local);

		App.UI.DOM.appendNewElement("h2", el, "Branding");

		for (const brandPlace in slave.brand) {
			div = document.createElement('div');
			div.append(`${His} ${brandPlace} is marked with ${slave.brand[brandPlace]}`);
			if (slave.brand[brandPlace] === V.brandDesign.official) {
				div.append(`, your `);
				div.append(App.UI.DOM.passageLink("official brand", "Universal Rules"));
			}
			div.append(": ");
			div.append(
				App.UI.DOM.link(
					"Remove Brand",
					() => {
						if (!cheat) {
							brandApplied = false;
							billSurgery();
							degradation -= 10;
						}
						delete slave.brand[brandPlace];
						refresh();
					},
				)
			);
			p.append(div);
		}

		if (jQuery.isEmptyObject(slave.brand)) {
			App.UI.DOM.appendNewElement("div", p, `${His} skin is unmarked.`);
		}

		if (!(Object.values(slave.brand).includes(V.brandDesign.official)) && !cheat) {
			div = document.createElement('div');
			div.append(`${He} lacks your `);
			div.append(App.UI.DOM.passageLink("official brand", "Universal Rules"));
			div.append(`, "${V.brandDesign.official}."`);
			p.append(div);
		}

		el.append(p);
		el.append(App.UI.brandSelect("local", slave, cheat));

		p = document.createElement('p');

		if (slave.brand[V.brandTarget.local] === V.brandDesign.local) {
			p.append(`${He} already has ${V.brandDesign.local} on ${his} ${V.brandTarget.local}.`);
		} else if (bodyPartData && bodyPartData.isPair) {
			p.append(`Choose a side to brand.`);
		} else {
			p.append(
				App.UI.DOM.link(
					"Brand",
					() => {
						if (!cheat) {
							brandApplied = true;
							billMod();
							degradation += 10;
						}
						slave.brand[V.brandTarget.local] = V.brandDesign.local;
						refresh();
					},
				)
			);
			p.append(` with ${V.brandDesign.local} on the ${V.brandTarget.local}`);
			if (slave.brand[V.brandTarget.local]) {
				p.append(`, covering the "${slave.brand[V.brandTarget.local]}" that is already there? `);
			} else {
				p.append(`. `);
			}
			App.UI.DOM.appendNewElement("span", p, `Branding will slightly reduce ${his} beauty but may slowly increase your reputation.`, "note");
		}
		el.append(p);
		return el;
	}

	function refresh() {
		jQuery("#body-modification").empty().append(createPage());
	}

	/**
	 * @param {number} weight
	 */
	function billPiercing(weight) {
		if (!cheat) {
			if (weight === 3) {
				// smart piercings cost more
				cashX(forceNeg(V.SPcost), "slaveMod", slave);
			} else {
				cashX(forceNeg(V.modCost), "slaveMod", slave);
			}
		}
	}

	function billMod() {
		if (!cheat) {
			cashX(forceNeg(V.modCost), "slaveMod", slave);
		}
	}

	function billSurgery() {
		if (!cheat) {
			cashX(forceNeg(V.surgeryCost), "slaveSurgery", slave);
		}
	}
};
/**
 * UI for the Body Modification system/studio. Refreshes without refreshing the passage.
 * @param {"local"|"official"} category
 * @param {App.Entity.SlaveState} [slave]
 * @param {boolean} [cheat] if true, will hide scenes, prevent damage to slaves, and keep the player from being billed for mods.
 * @returns {DocumentFragment}
 */
App.UI.brandSelect = function(category, slave, cheat = false) {
	const el = new DocumentFragment();
	let r = [];
	const isOfficial = category === "official";

	r.push(`Use <strong>${V.brandDesign[category]}</strong> or choose another brand:`);
	r.push(symbolOptions("personal"));
	App.Events.addNode(el, r, "div");
	r = [];

	r.push(symbolBlock("dirtyWord"));
	r.push(symbolBlock("genitalSymbol"));
	r.push(symbolBlock("silhouettes"));
	r.push(symbolBlock("FS"));
	App.Events.addNode(el, r, "div");
	r = [];

	r.push(`Or design your own: `);
	r.push(
		App.UI.DOM.makeTextBox(
			V.brandDesign[category],
			v => {
				V.brandDesign[category] = v;
				App.UI.reload();
			},
		)
	);
	App.Events.addNode(el, r, "div");
	r = [];

	App.UI.DOM.appendNewElement("div", el, "Choose a site for branding: ");
	if (category === "local") {
		r.push(App.UI.bodyPartSelector(V.brandTarget, "local", slave));
	} else {
		r.push(
			App.UI.DOM.makeElement("div", `One 'welcome' for a new slave is to have them branded. Where would you like such brands to be applied?`),
			App.UI.bodyPartSelector(V.brandTarget, "primary", true)
		);
		const bodyPartRoot = App.UI.bodyPartRoot(V.brandTarget.primary);
		const bodyPartData = App.Data.Slave.body.get(bodyPartRoot);
		if (bodyPartData && (bodyPartData).hasOwnProperty("requirements")) {
			r.push(`It's possible that <strong>${V.brandTarget.primary}</strong> may be missing from a slave. Choose a fallback in case it is not available: Current backup is <strong>${V.brandTarget.secondary}</strong>:`);
			App.Events.addNode(el, r, "div");
			r = [];
			r.push(App.UI.bodyPartSelector(V.brandTarget, "secondary", false));
		}
	}

	App.Events.addNode(el, r, "div");
	return el;

	function symbolBlock(brandList) {
		const div = document.createElement('div');
		div.classList.add("choices");
		div.append(symbolOptions(brandList));
		return div;
	}

	function symbolOptions(brandList) {
		const list = App.Medicine.Modification.Brands[brandList];
		const sortedKeys = Object.keys(list).sort((a, b) => list[a].displayName > list[b].displayName ? 1 : -1);
		const linkArray = [];
		for (const brand of sortedKeys) {
			const frag = new DocumentFragment();
			if (!cheat && list[brand].hasOwnProperty("requirements")) {
				if (!isOfficial && !list[brand].requirements(slave)) {
					continue;
				}
			}
			if (brandList === "FS") {
				App.UI.DOM.appendNewElement("span", frag, "FS ", "note");
			}
			frag.append(
				App.UI.DOM.link(
					list[brand].displayName,
					() => {
						V.brandDesign[category] = check(brand);
						App.UI.reload();
					}
				)
			);
			linkArray.push(frag);
		}
		return App.UI.DOM.generateLinksStrip(linkArray);
	}

	function check(brand) {
		switch (brand) {
			case "a big helping of your favorite food":
				return "a big helping of " + V.PC.refreshment;
			default:
				return brand;
		}
	}
};

/**
 * @param {"local"|"official"} category
 * @param {App.Entity.SlaveState} [slave]
 * @param {boolean} [cheat] if true, will hide scenes, prevent damage to slaves, and keep the player from being billed for mods.
 * @returns {DocumentFragment}
 */
App.UI.scarSelect = function(category, slave, cheat = false) {
	const el = new DocumentFragment();
	let r = [];
	r.push(`Use <strong>${V.scarDesign.local}</strong> or choose another scar:`);
	const scarTypes = new Set([
		"whip",
		"burn",
		"surgical",
		"menacing",
		"exotic"
	]); // Other common scars might be battle scars or c-Section but it makes little sense to include them here
	let linkArray = [];
	for (const scarType of scarTypes) {
		linkArray.push(
			App.UI.DOM.link(
				capFirstChar(scarType),
				() => {
					V.scarDesign.local = scarType;
					App.UI.reload();
				}
			)
		);
	}
	r.push(App.UI.DOM.generateLinksStrip(linkArray));
	App.Events.addNode(el, r, "div");

	r = [];
	r.push(`Or design your own:`);
	r.push(
		App.UI.DOM.makeTextBox(
			V.scarDesign.local,
			(v) => {
				V.scarDesign.local = v;
				App.UI.reload();
			}
		)
	);
	App.Events.addNode(el, r, "div");

	r = [];
	r.push(`Choose a site for scaring:`);
	if (category === "local") {
		r.push(App.UI.bodyPartSelector(V.scarTarget, "local", slave));
	} else {
		r.push(
			App.UI.DOM.makeElement("div", `One 'welcome' for a new slave is to have them scarred. Where would you like such scars to be applied?`),
			App.UI.bodyPartSelector(V.scarTarget, "primary", true)
		);
		const bodyPartRoot = App.UI.bodyPartRoot(V.scarTarget.primary);
		const bodyPartData = App.Data.Slave.body.get(bodyPartRoot);
		if (bodyPartData && bodyPartData.hasOwnProperty("requirements")) {
			r.push(`It's possible that <strong>${V.scarTarget.primary}</strong> may be missing from a slave. Choose a fallback in case it is not available: Current backup is <strong>${V.scarTarget.secondary}</strong>:`);
			App.Events.addNode(el, r, "div");
			r = [];
			r.push(App.UI.bodyPartSelector(V.scarTarget, "secondary", false));
		}
	}
	App.Events.addNode(el, r, "div");
	return el;
};

/**
 *
 * @param {Object} variable
 * @param {string} property
 * @param {boolean | App.Entity.SlaveState} selector false means missing limbs.  True means no missing limbs.  Slave state checks the slave.
 * @returns {HTMLElement}
 */
App.UI.bodyPartSelector = function(variable, property, selector) {
	let el = document.createElement("div");
	const links = {
		head: [],
		torso: [],
		arms: [],
		legs: [],
	};

	const makeLink = (bodyKey, bodyObj) => {
		links[bodyObj.category].push(
			App.UI.DOM.link(
				capFirstChar(bodyKey),
				() => {
					variable[property] = bodyKey;
					App.UI.reload();
				}
			)
		);
	};
	for (const [bodyKey, bodyObj] of App.Data.Slave.body) {
		if (selector === true) { // Always show all parts
			makeLink(bodyKey, bodyObj);
		} else if (selector === false) { // Looking for a backup slot for every slave, hide all parts that could be amputated
			if (!bodyObj.hasOwnProperty("requirements")) {
				makeLink(bodyKey, bodyObj);
			}
		} else if (!(bodyObj.hasOwnProperty("requirements")) || bodyObj.requirements(selector)) { // We have an actual slave here, run the check on them
			makeLink(bodyKey, bodyObj);
		}
	}
	for (const category in links) {
		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(links[category]), "choices");
	}

	// Choose a side
	const bodyPartData = App.Data.Slave.body.get(variable[property]);
	if (bodyPartData && bodyPartData.isPair) {
		const linkArray = [];
		for (const side of ["left", "right"]) {
			linkArray.push(App.UI.DOM.link(capFirstChar(side), () => {
				variable[property] = `${side} ${variable[property]}`;
				App.UI.reload();
			}));
		}
		App.UI.DOM.appendNewElement("div", el, "Side: ", "indent")
			.append(App.UI.DOM.generateLinksStrip(linkArray));
	}

	let div = document.createElement('div');
	div.append(`Or a custom site: `);
	div.append(
		App.UI.DOM.makeTextBox(
			variable[property],
			v => {
				variable[property] = v;
				App.UI.reload();
			},
		)
	);
	el.append(div);

	return el;
};

/**
 * Find the root of a string that may start with "left" or "right"
 * @param {string} bodyPart
 * @returns {string}
 */
App.UI.bodyPartRoot =  function(bodyPart) {
	if (bodyPart.startsWith("left")) {
		bodyPart = (bodyPart.replace(/left /g, ""));
	} else if (bodyPart.startsWith("right")) {
		bodyPart = (bodyPart.replace(/right /g, ""));
	}
	return bodyPart;
};


code:
App.Facilities.MadamSelect = function() {
	const f = document.createDocumentFragment();

	if (S.Madam) {
		f.append(`Madam: ${SlaveFullName(S.Madam)}, manages the brothel. `);
		f.append(App.UI.DOM.link("Remove Madam", () => {
			removeJob(S.Madam, Job.MADAM);
		},
		[], "Brothel"
		));
	} else {
		f.append(`No Madam assigned, appoint one from your devoted slaves.`);
	}

	f.append(App.UI.SlaveList.facilityManagerSelection(App.Entity.facilities.brothel, "Brothel"));
	return f;
};

repo_name: Elohiem/fc

license: mit


code:
App.Facilities.Brothel.brothel = class Brothel extends App.Facilities.Facility {
	constructor() {
		const brothel = App.Entity.facilities.brothel;
		const decommissionHandler = () => {
			V.brothel = 0;
			V.brothelUpgradeDrugs = 0;
			V.brothelDecoration = "standard";
			V.brothelAdsSpending = 0;

			App.Arcology.cellUpgrade(V.building, App.Arcology.Cell.Shop, "Brothel", "Shops");
		};

		super(
			brothel,
			decommissionHandler,
		);

		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		V.encyclopedia = "Brothel";
	}

	/** @returns {string} */
	get intro() {
		const text = [];

		text.push(this.facility.nameCaps, this.decorations);

		return text.join(' ');
	}

	/** @returns {string} */
	get decorations() {
		/** @type {FC.Facilities.Decoration} */
		const FS = {
			"Roman Revivalist": `is decorated as a Roman whorehouse. Refreshments are served at a bar, and someone is playing pipes in the back.`,
			"Neo-Imperialist": `is decorated as a seamless intersection of technology and tradition. Slaves dressed in tight bodysuits and skimpy nanoweave flit underneath the glow of sleek walls and flowing feudal banners.`,
			"Aztec Revivalist": `is decorated as an Aztec ode to fertility and nature. Clients may sacrifice a bit of blood to honor the goddess of Filth or to partake in a fertility ritual before joining the girl of their choosing.`,
			"Egyptian Revivalist": `is decorated as an ancient Egyptian fertility temple. Customers are bade relax on couches next to running water so that slaves may dance to entice them.`,
			"Edo Revivalist": `is furnished as an Edo period pleasure house, seedy by the standards of the time. Still, girls usually keep their clothes on until they lead patrons back behind the sliding paper screens, though this does not stop silhouettes of the activities within from being visible on them.`,
			"Arabian Revivalist": `is furnished as an Arabian fleshmarket, with the merchandise standing on little platforms, prices visible. Customers are permitted to fondle before making a decision and dragging a girl back behind a curtain.`,
			"Chinese Revivalist": `is furnished as an old Chinese pleasure house, with each girl set up in her own low room. They stand outside the doors, luring customers back one by one.`,
			"Chattel Religionist": `is decorated as a place of carnal worship. The air is scented by censers, and the slaves here maintain an air of holiness even when being sodomized in public.`,
			"Degradationist": `is decorated to look like a dungeon. The décor involves a lot of black leather and burnished steel, and the slaves on offer are mostly chained to beds and walls.`,
			"Repopulationist": `is clean and full of soft couches and chairs for its pregnant whores to lounge upon while showing off their assets. Several of the rooms are prepped to allow a whore to give birth in front of an audience. A supply of freshly squeezed breast milk is available on tap.`,
			"Eugenics": `is decorated to look like a club. Loud music is playing, and the whores that aren't with customers are stripping and pole dancing on a stage. Safe sex is greatly encouraged. The real action happens in several reserved rooms dedicated to society's best, since the loud music drowns out private conversations.`,
			"Asset Expansionist": `is decorated to look like a club. Loud music is playing, and the whores that aren't with customers are stripping and pole dancing on a stage.`,
			"Transformation Fetishist": `is sterile and clean. Interactive screens on the walls list the whores and their modifications in clinical detail.`,
			"Gender Radicalist": `is decorated to look like an old world bordello. The rich décor includes erotic photography and pornographic statuary, depicting every possible combination of human sexual congress.`,
			"Gender Fundamentalist": `is decorated to look like an old world whorehouse. Screens on the walls are showing pornography starring the whores, with prices flashing after each sex act.`,
			"Physical Idealist": `is decorated to look like a club. Loud music is playing, and the whores that aren't with customers are stripping on a stage. There is a distinct smell of sweat, and there is as much emphasis on the strippers' muscles as their breasts.`,
			"Supremacist": `is decorated like an old world gentleman's club. The pictures on the wall depict degradation of every race on earth, except ${V.arcologies[0].FSSupremacistRace} people.`,
			"Subjugationist": `is decorated to celebrate the degradation of ${V.arcologies[0].FSSubjugationistRace} whores. The whores greet customers in stereotypical ${V.arcologies[0].FSSubjugationistRace} accents.`,
			"Paternalist": `is decorated to look like a trendy bar. Whores are encouraged to meet customers for a drink and get to know them a little before heading back into a private room.`,
			"Pastoralist": `is decorated to look like a dairy. Though it isn't one, there is an intense sexual focus on boobs and lactation, and all the whores have their sizes and productivity proudly posted.`,
			"Maturity Preferentialist": `is decorated to look like a refined bar. It has a row of sturdy backless barstools, perfect for a delectable selection of succulent MILFs to perch on.`,
			"Youth Preferentialist": `is decorated to look like the sort of bar old world students visit on spring break. Vapid music is playing, and when whores aren't with customers, they dance and make out with each other to attract some.`,
			"Body Purist": `is decorated to look like an old world bordello. The rich décor includes erotic photography and pornographic statuary, depicting idealized human forms in the act of love.`,
			"Slimness Enthusiast": `is decorated to look like an old world bordello. The rich décor includes erotic photography and pornographic statuary, depicting slim, girlish figures playing, dancing, and loving.`,
			"Hedonistic": `is comfortable and full of soft couches and chairs for its overweight whores to lounge upon between clients ${V.arcologies[0].FSHedonisticDecadenceResearch === 1 ? `and enjoy a plate of snacks` : `and enjoy a big cup of slave food`}. The smells of fresh baked goods are pumped into the facility to mask the smell of sweat. It's not unusual for a client to fuck a whore right on her chosen couch, since the effort of moving is often too much.`,
			"Intellectual Dependency": `is decorated to look like a club. The sexually charged atmosphere has the whores horny and making out with each other. Customers are expected to come in, grab a bimbo to their tastes, and pull them off to have a good time.`,
			"Slave Professionalism": `is decorated to look like a high-class bordello. Courtesans are expected to greet and entice customers to join them for a night they'll never forget.`,
			"Petite Admiration": `is decorated to look like an old world whorehouse. The whores have personal platforms to stand on for inspection from their taller customers.`,
			"Statuesque Glorification": `is decorated to look like an old world bordello. The rich décor specializes in pornographic statuary, depicting towering human forms in the act of love.`,
			"standard": `is utilitarian. There's a businesslike foyer with an area for the merchandise to stand. Customers make their selection (or selections) and then lead the whores back into little rooms.`,
			"": ``,
		};

		if (!Object.keys(FS).includes(V.brothelDecoration)) {
			throw new Error(`Unknown V.brothelDecoration value of '${V.brothelDecoration}' found in decorations().`);
		}

		return FS[V.brothelDecoration];
	}

	/** @returns {FC.Facilities.Expand} */
	get expand() {
		// TODO: redo this without ternaries
		return {
			desc: `It can support ${num(V.brothel)} whores. There ${this.facility.hostedSlaves === 1 ? `is currently ${num(this.facility.hostedSlaves)} whore` : `are currently ${num(this.facility.hostedSlaves)} whores`} working in ${V.brothelName}. ${V.brothelAdsSpending > 0
				? `Screens outside the entrance are showing porn to advertise ${V.brothelName}. ${V.brothelAdsOld === 1
					? `The featured slave actresses are all MILFs.`
					: V.brothelAdsOld === -1
						? `The featured slave actresses are all nice and young.`
						: V.brothelAdsOld === -2
							? `The featured slave actresses are all teenagers.`
							: V.brothelAdsOld === -3
								? `The featured slave actresses are all lolis.`
								: `The featured slave actresses vary in age.`}${V.brothelAdsStacked === 1
					? `Lots of bouncing breasts and butts`
					: V.brothelAdsStacked === -1
						? `Lots of trim breasts and shapely butts`
						: `A variety of breast and butt sizes and shapes`} are on display, and ${V.brothelAdsImplanted === 100
					? `most of these are augmented by implants.`
					: V.brothelAdsImplanted === -1
						? `they're all natural.`
						: `some are augmented by implants.`} ${V.seePreg
					? V.brothelAdsPreg
						? `Most of the slaves have firm, rounded bellies.`
						: V.brothelAdsPreg === -1
							? `Most of the slaves have firm, flat bellies.`
							: `Some of the slaves are pregnant.` : ``} ${V.brothelAdsModded === 1
					? `Everything is heavily pierced and tattooed.`
					: V.brothelAdsModded === -1
						? `Everything is free of tattoos and piercings.`
						: `Some of these assets are tattooed and pierced, and some aren't.`} The slaves in the ads are ${V.brothelAdsXX === 1
					? `sucking dick and taking cock in their pussies and asses.`
					: V.brothelAdsXX === -1
						? `sucking dick and taking cock in their pussies and asses.`
						: V.brothelAdsXX
							? `sucking cock and being assfucked while their dicks flop around.`
							: `sucking dick and taking anal, and the ones that have pussies are being fucked there, too.`} The ads are shown on media across ${V.arcologies[0].name}. ${V.brothelAdsXX === 1 && V.brothelAdsImplanted === 1 && V.brothelAdsStacked === 1
					? `As a result, ${V.brothelName} is known as the place to go if you want to rent a${V.brothelAdsPreg ? ` pregnant` : ``} bimbo's pussy.`
					: V.brothelAdsXX === 1 && V.brothelAdsOld === 1
						? `As a result, ${V.brothelName} is known as the place to go if you want to rent a${V.brothelAdsPreg ? ` pregnant` : ``} MILF's tits.`
						: V.brothelAdsXX === 1 && V.brothelAdsOld === -3
							? `As a result, ${V.brothelName} is known as the place to go if you want to rent${V.brothelAdsPreg ? ` a pregnant` : ``}${V.brothelAdsStacked ? V.brothelAdsPreg !== 1 ? ` an` : `` : `oppai`} loli.`
							: V.brothelAdsXX === 1 && V.brothelAdsPreg === 1
								? `As a result, ${V.brothelName} is known as the place to go if you want to pound some pregnant pussy.`
								: V.brothelAdsXX === -1 && V.brothelAdsStacked === 1
									? `As a result, ${V.brothelName} is known as the place to go if you want to rent a shemale's asspussy.`
									: V.brothelAdsXX === -1 && V.brothelAdsStacked === -1
										? `As a result, ${V.brothelName} is known as the place to go if you want to rent a trap's tight ass.`
										: ``}`
				: ``} ${this.facility.hostedSlaves > 2
				? `${capFirstChar(V.brothelName)} is bustling with activity. Customers are coming and going and slave girls are displaying themselves. When a slave catches a customer's eye, he leads her back into a little cubicle.`
				: this.facility.hostedSlaves > 0 ?
					`${capFirstChar(V.brothelName)} is working steadily. Customers are present and slave girls are on offer. When a slave catches a customer's eye, he leads her back into a little cubicle.`
					: S.Madam
						? `${S.Madam.slaveName} is alone in ${V.brothelName}, and has nothing to do but keep the place clean and plan future sales efforts.`
						: `${capFirstChar(V.brothelName)} is empty and desolate.`}`,
			removeSlave: "whore",
		};
	}

	/** @returns {FC.IUpgrade[]}*/
	get upgrades() {
		return [
			{
				property: "brothelUpgradeDrugs",
				tiers: [
					{
						value: 0,
						upgraded: 1,
						text: `It is a standard brothel.`,
						link: `Upgrade the brothel with aphrodisiac injection systems`,
						cost: 10000 * V.upgradeMultiplierArcology * V.HackingSkillMultiplier,
						handler: () => V.PC.skill.engineering += .1,
						note: ` and will increase upkeep costs`,
					},
					{
						value: 0.1,
						text: `It has been upgraded with an injection system that can keep whores horny and ready to fuck at the drop of a hat.`,
					},
					{
						value: 1,
						text: `It has been upgraded with an injection system that can keep whores horny and ready to fuck at the drop of a hat.`,
					},
					{
						value: 2,
						text: `It has been upgraded with an injection system that can keep whores horny and ready to fuck at the drop of a hat.`,
					},
				],
			},
		];
	}

	/** @returns {FC.Facilities.Rule[]} */
	get rules() {
		return [
			{
				property: "brothelUpgradeDrugs",
				prereqs: [
					() => V.brothelUpgradeDrugs > 0,
				],
				options: [
					{
						get text() {
							return `It has been upgraded with aphrodisiac injection systems that monitor the whores and adjust dosage to keep them healthy but desperately horny and hopelessly addicted. The aphrodisiac systems are currently disabled.`;
						},
						link: `Deactivate`,
						value: 0.1,
					},
					{
						get text() {
							return `It has been upgraded with aphrodisiac injection systems that monitor the whores and adjust dosage to keep them healthy but desperately horny and hopelessly addicted. The aphrodisiac systems are currently applying a moderate dosage of aphrodisiac.`;
						},
						link: `Standard`,
						value: 1,
					},
					{
						get text() {
							return `It has been upgraded with aphrodisiac injection systems that monitor the whores and adjust dosage to keep them healthy but desperately horny and hopelessly addicted. The aphrodisiac systems are currently applying an extreme dosage of aphrodisiac.`;
						},
						link: `Extreme`,
						value: 2,
					},
				],
			},
			{
				property: "MadamIgnoresFlaws",
				prereqs: [
					() => !!S.Madam,
				],
				options: [
					{
						get text() {
							const {him} = getPronouns(S.Madam);
							return `${S.Madam.slaveName} will attempt to fix flaws in whores serving under ${him}.`;
						},
						link: `Fix flaws`,
						value: 0,
					},
					{
						get text() {
							const {him} = getPronouns(S.Madam);
							return `${S.Madam.slaveName} has been instructed to ignore flaws in the whores serving under ${him}.`;
						},
						link: `Ignore flaws`,
						value: 1,
					},
				],
			},
			{
				property: "MadamNoSex",
				prereqs: [
					() => !!S.Madam,
				],
				options: [
					{
						get text() {
							const {he, him, himself} = getPronouns(S.Madam);
							return `${S.Madam.slaveName} will whore ${himself} out when ${he} doesn't have enough whores serving under ${him}.`;
						},
						link: `Serve clients`,
						value: 0,
					},
					{
						get text() {
							const {he, himself} = getPronouns(S.Madam);
							return `${S.Madam.slaveName} will not whore ${himself} out even if ${he} has time.`;
						},
						link: `Don't serve clients`,
						value: 1,
					},
				],
			},
		];
	}

	/** @returns {HTMLDivElement} */
	get stats() {
		return App.UI.DOM.makeElement("div", App.Facilities.Brothel.Stats(true));
	}

	/** @returns {HTMLDivElement} */
	get ads() {
		const div = document.createElement("div");

		const profit = V.lastWeeksCashIncome.brothelAds + V.lastWeeksCashExpenses.brothelAds;

		App.Events.addNode(div, [`${capFirstChar(V.brothelName)} ${V.brothelAdsSpending > 0 ? `is the subject of an active ad campaign.` : `advertises by word of mouth.`} Last week this ${profit > 0
			? `made you an extra ${cashFormat(profit)},${this.facility.hostedSlaves > 0
				? ` as well as increasing business for your whores.` : ``}`
			: profit < 0
				? `cost you ${cashFormat(profit)},${this.facility.hostedSlaves > 0
					? ` but still increased business for your whores.` : ``}`
				: `didn't make you any extra money, but didn't lose you any, either.`}`]);

		App.UI.DOM.appendNewElement("div", div, App.UI.DOM.passageLink(`Manage brothel advertisements`, 'Brothel Advertisement', () => {
			V.nextLink = passage();
			V.nextButton = "Back";
		}), ['indent']);

		return div;
	}

	/** @returns {HTMLDivElement} */
	get attention() {
		const div = document.createElement("div");
		const options = new App.UI.OptionsGroup();

		App.UI.DOM.appendNewElement("h2", div, `Personal Attention`);
		div.append(`${capFirstChar(V.brothelName)} could be more profitable if you started spending time in the brothel you would otherwise spend in public. Your reputation will suffer, but paying customers may seek out your whores if they think it means you'll lend them your ear.`);

		options.addOption(null, "selected", V.brothelBoost)
			.addValue(`None`, 0)
			.addValue(`5%`, 1)
			.addValue(`10%`, 2)
			.addValue(`15%`, 3)
			.addValue(`20%`, 4)
			.addValue(`25%`, 5)
			.addValue(`30%`, 6)
			.addValue(`35%`, 7)
			.addValue(`40%`, 8)
			.addValue(`45%`, 9)
			.addValue(`50%`, 10);

		options.addComment(`Every 5% you attempt to boost income also increases the amount of customers by 2% and costs 50 reputation more. A minimum of 2500 reputation is required and every additional level increases this by another 500. You may choose whichever level you please; only the highest level you are eligible for will be used at any time.`);

		App.UI.DOM.appendNewElement("div", div, options.render(), ['indent']);

		return div;
	}

	/** @returns {HTMLDivElement[]}*/
	get customNodes() {
		return [
			this.attention,
		];
	}
};


code:
App.Facilities.Brothel.ads = function() {
	const div = document.createElement("div");

	App.UI.DOM.appendNewElement("h1", div, `${capFirstChar(V.brothelName)} Advertisements`, ['margin-bottom']);

	div.append(
		intro(),
		spending(),
		report(),
	);

	return div;

	function intro() {
		const div = App.UI.DOM.makeElement("div", null, ['scene-intro', 'margin-bottom']);
		const text = [];

		switch (V.brothelDecoration) {
			case "Roman Revivalist":
				text.push(`${capFirstChar(V.brothelName)} is decorated as a Roman whorehouse. Refreshments are served at a bar, and someone is playing pipes in the back.`);
				break;
			case "Neo-Imperialist":
				text.push(`${capFirstChar(V.brothelName)} is decorated as a seamless intersection of technology and tradition. Slaves dressed in tight bodysuits and skimpy nanoweave flit underneath the glow of sleek walls and flowing feudal banners.`);
				break;
			case "Aztec Revivalist":
				text.push(`${capFirstChar(V.brothelName)} is decorated as an Aztec ode to fertility and nature. Clients may sacrifice a bit of blood to honor the goddess of Filth or to partake in a fertility ritual before joining the girl of their choosing.`);
				break;
			case "Egyptian Revivalist":
				text.push(`${capFirstChar(V.brothelName)} is decorated as an ancient Egyptian fertility temple. Customers are bade relax on couches next to running water so that slaves may dance to entice them.`);
				break;
			case "Edo Revivalist":
				text.push(`${capFirstChar(V.brothelName)} is furnished as an Edo period pleasure house, seedy by the standards of the time. Still, girls usually keep their clothes on until they lead patrons back behind the sliding paper screens, though this does not stop silhouettes of the activities within from being visible on them.`);
				break;
			case "Arabian Revivalist":
				text.push(`${capFirstChar(V.brothelName)} is furnished as an Arabian slave market, with the merchandise standing on little platforms, prices visible. Customers are permitted to fondle before making a decision and dragging a girl back behind a curtain.`);
				break;
			case "Chinese Revivalist":
				text.push(`${capFirstChar(V.brothelName)} is furnished as an old Chinese pleasure house, with each girl set up in her own low room. They stand outside the doors, luring customers back one by one.`);
				break;
			case "Chattel Religionist":
				text.push(`${capFirstChar(V.brothelName)} is decorated as a place of worship. The air is scented by censers, and the slaves here maintain an air of holiness even when being sodomized in public.`);
				break;
			case "Degradationist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a dungeon. The décor involves a lot of black leather and burnished steel, and the slaves on offer are mostly chained to beds and walls.`);
				break;
			case "Repopulationist":
				text.push(`${capFirstChar(V.brothelName)} is clean and full of soft couches and chairs for its pregnant whores to lounge upon while showing off their assets. Several of the rooms are prepped to allow a whore to give birth in front of an audience. A supply of freshly squeezed breast milk is available on tap.`);
				break;
			case "Eugenics":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a club. Loud music is playing, and the whores that aren't with customers are stripping and pole dancing on a stage. Safe sex is greatly encouraged. The real action happens in several reserved rooms dedicated to society's best, since the loud music drowns out private conversations.`);
				break;
			case "Asset Expansionist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a club. Loud music is playing, and the whores that aren't with customers are stripping and pole dancing on a stage.`);
				break;
			case "Transformation Fetishist":
				text.push(`${capFirstChar(V.brothelName)} is sterile and clean. Interactive screens on the walls list the whores and their modifications in clinical detail.`);
				break;
			case "Gender Radicalist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like an old world bordello. The rich décor includes erotic photography and pornographic statuary, depicting every possible combination of human sexual congress.`);
				break;
			case "Gender Fundamentalist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like an old world whorehouse. Screens on the walls are showing pornography starring the whores, with prices flashing after each sex act.`);
				break;
			case "Physical Idealist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a club. Loud music is playing, and the whores that aren't with customers are stripping on a stage. There is a distinct smell of sweat, and there is as much emphasis on the strippers' muscles as their breasts.`);
				break;
			case "Subjugationist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to celebrate the degradation of ${V.arcologies[0].FSSubjugationistRace} whores. The whores greet customers in stereotypical ${V.arcologies[0].FSSubjugationistRace} accents.`);
				break;
			case "Supremacist":
				text.push(`${capFirstChar(V.brothelName)} is decorated like an old world gentleman's club. The pictures on the wall depict degradation of every race on earth, except ${V.arcologies[0].FSSupremacistRace} people.`);
				break;
			case "Paternalist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a normal bar. Whores are encouraged to meet customers for a drink and get to know them a little before heading back into a private room.`);
				break;
			case "Maturity Preferentialist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a refined bar. It has a row of sturdy backless barstools, perfect for a delectable selection of succulent MILFs to perch on.`);
				break;
			case "Youth Preferentialist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a the sort of bar old world students visit on spring break. Vapid music is playing, and when whores aren't with customers, they dance and make out with each other to attract some.`);
				break;
			case "Body Purist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like an old world bordello. The rich décor includes erotic photography and pornographic statuary, depicting idealized human forms in the act of love.`);
				break;
			case "Slimness Enthusiast":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like an old world bordello. The rich décor includes erotic photography and pornographic statuary, depicting slim, girlish figures playing, dancing, and loving.`);
				break;
			case "Pastoralist":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a dairy. Though it isn't one, there is an intense sexual focus on boobs and lactation, and all the whores have their sizes proudly posted.`);
				break;
			case "Hedonistic":
				text.push(`${capFirstChar(V.brothelName)} is comfortable and full of soft couches and chairs for its overweight whores to lounge upon between clients <<if ${V.arcologies[0].FSHedonisticDecadenceResearch} == 1>>and enjoy a plate of snacks<<else>>and enjoy a big cup of slave food<</if>>. The smells of fresh baked goods are pumped into the facility to mask the smell of sweat. It's not unusual for a client to fuck a whore right on her chosen couch, since the effort of moving is often too much.`);
				break;
			case "Intellectual Dependency":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a club. The sexually charged atmosphere has the whores horny and making out with each other. Customers are expected to come in, grab a bimbo to their tastes, and pull them off to have a good time.`);
				break;
			case "Slave Professionalism":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like a high-class bordello. Courtesans are expected to greet and entice customers to join them for a night they'll never forget.`);
				break;
			case "Petite Admiration":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like an old world whorehouse. The whores have personal platforms to stand on for inspection from their taller customers.`);
				break;
			case "Statuesque Glorification":
				text.push(`${capFirstChar(V.brothelName)} is decorated to look like an old world bordello. The rich décor specializes in pornographic statuary, depicting towering human forms in the act of love.`);
				break;
		}

		if (V.brothelAdsSpending > 0) {
			text.push(`Screens outside the entrance are showing porn to advertise the brothel.`);

			if (V.brothelAdsOld === 1) {
				text.push(`The featured slave actresses are all MILFs.`);
			} else if (V.brothelAdsOld === -1) {
				text.push(`The featured slave actresses are all nice and young.`);
			} else if (V.brothelAdsOld === -2) {
				text.push(`The featured slave actresses are all teenagers.`);
			} else if (V.brothelAdsOld === -3) {
				text.push(`The featured slave actresses are all lolis.`);
			} else {
				text.push(`The featured slave actresses vary in age.`);
			}

			if (V.brothelAdsStacked === 1) {
				text.push(`Lots of bouncing breasts and butts`);
			} else if (V.brothelAdsStacked === -1) {
				text.push(`Lots of trim breasts and shapely butts`);
			} else {
				text.push(`A variety of breast and butt sizes and shapes`);
			}

			text.push(`are on display, and`);

			if (V.brothelAdsImplanted === 1) {
				text.push(`most of these are augmented by implants.`);
			} else if (V.brothelAdsImplanted === -1) {
				text.push(`they're all natural.`);
			} else {
				text.push(`some are augmented by implants.`);
			}

			if (V.seePreg) {
				if (V.brothelAdsPreg === 1) {
					text.push(`Most of the slaves have firm, rounded bellies.`);
				} else if (V.brothelAdsPreg === -1) {
					text.push(`Most of the slaves have firm, flat bellies.`);
				} else {
					text.push(`Some of the slaves are pregnant.`);
				}
			}

			if (V.brothelAdsModded === 1) {
				text.push(`Everything is heavily pierced and tattooed.`);
			} else if (V.brothelAdsModded === -1) {
				text.push(`Everything is free of tattoos and piercings.`);
			} else {
				text.push(`Some of these assets are tattooed and pierced, and some aren't.`);
			}

			text.push(`The slaves in the ads are`);

			if (V.brothelAdsXX === 1) {
				text.push(`sucking dick and taking cock in their pussies and asses.`);
			} else if (V.brothelAdsXX === -1) {
				text.push(`sucking cock and being assfucked while their dicks flop around.`);
			} else {
				text.push(`sucking dick and taking anal, and the ones that have pussies are being fucked there, too.`);
			}
		}

		div.append(text.join(' '));

		return div;
	}

	function spending() {
		const div = App.UI.DOM.makeElement("div", null, ['margin-bottom']);
		let text = [];
		const links = [];

		if (V.brothelAdsSpending >= 5000) {
			text.push(`Advertisements based on these scenes run constantly in media across ${V.arcologies[0].name}, and there's an active merchandising campaign underway to promote your whores.`);
		} else if (V.brothelAdsSpending >= 4000) {
			text.push(`Advertisements based on these scenes run constantly in media across ${V.arcologies[0].name}.`);
		} else if (V.brothelAdsSpending >= 3000) {
			text.push(`Advertisements based on these scenes are run in media across ${V.arcologies[0].name}.`);
		} else if (V.brothelAdsSpending >= 2000) {
			text.push(`Advertisements based on these scenes run regularly in arcology media.`);
		} else if (V.brothelAdsSpending >= 1000) {
			text.push(`Advertisements based on these scenes run occasionally in arcology media.`);
		} else {
			text.push(`${V.arcologies[0].name} has a robust internal media that could be used to bring customers into the brothel.`);
		}
		App.Events.addNode(div, text, "p", "scene-intro");

		text = [];
		text.push(`You are spending ${cashFormatColor(V.brothelAdsSpending, true)} each week to advertise ${V.brothelName}.`);

		if (V.lastWeeksCashIncome.brothelAds > 0) {
			text.push(`Last week's ads increased profits by ${cashFormatColor(V.lastWeeksCashIncome.brothelAds)}${App.Entity.facilities.brothel.hostedSlaves > 1 ? ` and increased business for your whores` : ``}, and cost ${cashFormatColor(V.lastWeeksCashExpenses.brothelAds)}.`);
		}

		App.Events.addNode(div, text);

		if (V.brothelAdsSpending >= 1000) {
			links.push(App.UI.DOM.link(`Decrease`, () => {
				V.brothelAdsSpending -= 1000;
				App.UI.reload();
			}));
		} else {
			links.push(App.UI.DOM.disabledLink(`Decrease`, [`Minimum reached`]));
		}
		if (V.brothelAdsSpending < 5000) {
			links.push(App.UI.DOM.link(`Increase`, () => {
				V.brothelAdsSpending += 1000;
				App.UI.reload();
			}));
		} else {
			links.push(App.UI.DOM.disabledLink(`Increase`, [`Maximum reached`]));
		}

		App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);

		return div;
	}

	function report() {
		const div = document.createElement("div");

		App.UI.DOM.appendNewElement("h2", div, `Ad Campaign`);
		App.UI.DOM.appendNewElement("div", div, App.Ads.report("brothel", true));

		return div;
	}
};