From 0037148d16fe99741d5ea7385f018a04ad0a7a97 Mon Sep 17 00:00:00 2001
From: lowercasedonkey <lowercasedonkey@gmail.com>
Date: Wed, 16 Dec 2020 15:55:54 -0500
Subject: [PATCH] start

---
 src/facilities/salon/salon.js | 1405 +++++++++++++++++++++++++++++++++
 1 file changed, 1405 insertions(+)
 create mode 100644 src/facilities/salon/salon.js

diff --git a/src/facilities/salon/salon.js b/src/facilities/salon/salon.js
new file mode 100644
index 00000000000..9eaa3867379
--- /dev/null
+++ b/src/facilities/salon/salon.js
@@ -0,0 +1,1405 @@
+/**
+ * UI for the Salon.  Refreshes without refreshing the passage.
+ * @param {App.Entity.SlaveState} slave
+ * @param {boolean} cheat if true, will hide scenes and keep the player from being billed for changes.
+ */
+App.UI.salon = function(slave, cheat = false) {
+	const container = document.createElement("span");
+	container.id = "body-modification";
+	const {
+		He, His,
+		he, his, him, himself
+	} = getPronouns(slave);
+	Enunciate(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) {
+			if (V.seeImages > 0) {
+				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.`);
+		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 lines`);
+							if (hasAnyNaturalLimbs(slave)) {
+								r.push(`, 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`);
+							}
+							r.push(r.pop() + ".");
+					}
+					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);
+								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);
+								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);
+								slave.clitSetting = "all";
+								degradation += 1;
+								refresh();
+							},
+							[],
+							"",
+							`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 (const name of tattooLocations.keys()) {
+			if (name === "leg") {
+				r.push(App.UI.DOM.makeElement("div", App.Desc.tattoo(slave, "thigh")));
+			} else {
+				r.push(App.UI.DOM.makeElement("div", App.Desc.tattoo(slave, name)));
+			}
+		}
+		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.anusTat === 0) {
+			tattooChoiceNames.add("bleached");
+		}
+		let linkArray = [];
+		for (const style of tattooChoiceNames) {
+			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 pierced
+		let validTattooLocations;
+		if (tattooChoice === "bleached") {
+			validTattooLocations = ["anus"];
+		} 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 && tattooChoice !== "bleached") {
+			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());
+				}
+			)
+		);
+		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);
+			if (!["flowers", "paternalist", "tribal patterns", 0].includes(tattooChoice)) {
+				degradation += 1;
+			}
+		}
+
+		function oddTattoos() {
+			const el = 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);
+							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);
+									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(el, r, "div");
+
+			r = [];
+			linkArray = [];
+			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.`);
+				}
+				if (slave.bellyTat !== 0) {
+					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.`);
+					r.push(
+						App.UI.DOM.link(
+							"Remove it",
+							() => {
+								slave.birthsTat = -1;
+								refresh();
+							}
+						)
+					);
+					if (slave.abortionTat > -1) {
+						r.push(App.UI.DOM.makeElement("span", `This will only remove birth tracking`, "note"));
+					}
+				} else {
+					r.push(`${He} is scheduled to receive a tattoo each time ${he} gives birth.`);
+					r.push(
+						App.UI.DOM.link(
+							"Cancel",
+							() => {
+								slave.birthsTat = -1;
+								refresh();
+							}
+						)
+					);
+				}
+			} else {
+				r.push(`Have ${him} receive a tattoo each time ${he} gives birth.`);
+				r.push(
+					App.UI.DOM.link(
+						"Begin keeping track",
+						() => {
+							slave.birthsTat = 0;
+							refresh();
+						}
+					)
+				);
+			}
+			App.Events.addNode(el, r, "div");
+
+			r = [];
+
+			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.`);
+				}
+				r.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.`);
+					r.push(
+						App.UI.DOM.link(
+							"Remove it",
+							() => {
+								slave.abortionTat = -1;
+								refresh();
+							}
+						)
+					);
+					if (slave.birthsTat > -1) {
+						r.push(App.UI.DOM.makeElement("span", `This will only remove abortion tracking`, "note"));
+					}
+				} else {
+					r.push(`${He} is scheduled to receive a tattoo each time ${he} gets an abortion or miscarries.`);
+					r.push(
+						App.UI.DOM.link(
+							"Cancel",
+							() => {
+								slave.abortionTat = -1;
+								refresh();
+							}
+						)
+					);
+				}
+			} else {
+				r.push(`Have ${him} receive a tattoo for each abortion or miscarriage ${he} has.`);
+				r.push(
+					App.UI.DOM.link(
+						"Begin keeping track",
+						() => {
+							slave.abortionTat = 0;
+							refresh();
+						}
+					)
+				);
+			}
+			App.Events.addNode(el, r, "div");
+			return el;
+		}
+
+		function customTats() {
+			const el = new DocumentFragment();
+			App.UI.DOM.appendNewElement("h3", el, "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);
+									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(el, r, "div", "grid-2columns-auto");
+			if (slave.custom.tattoo !== "") {
+				el.append(
+					App.UI.DOM.link(
+						"Remove custom Tattoo",
+						() => {
+							slave.custom.tattoo = "";
+							billMod();
+							refresh();
+						}
+					)
+				);
+			}
+			return el;
+		}
+	}
+
+	function branding() {
+		const el = new DocumentFragment();
+		const selection = document.createElement("span");
+		selection.id = "brand-selection";
+		selection.append(brand(slave, cheat));
+		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 = [];
+
+		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.`);
+		}
+
+		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;
+						refresh();
+					}
+				)
+			);
+		}
+		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;
+					refresh();
+				}
+			)
+		);
+		App.Events.addNode(el, r, "div");
+
+		r = [];
+		r.push(`Choose a site for scaring:`);
+
+		let scarLocations = new Map();
+
+		if (["exotic", "menacing"].includes(V.scarDesign.local)) {
+			scarLocations.set("Cheeks", "cheek");
+		} else {
+			// Sorted head to toe
+			scarLocations.set("Entire body", "entire body");
+
+			// Head
+			if (slave.earShape !== "none") {
+				scarLocations.set("Ears", "ear");
+			}
+			scarLocations.set("Cheeks", "cheek");
+			scarLocations.set("Neck", "neck");
+
+			// Torso
+			scarLocations.set("Chest", "chest");
+			scarLocations.set("Breasts", "breast");
+			scarLocations.set("Back", "back");
+			scarLocations.set("Lower Back", "lower back");
+			scarLocations.set("Belly", "belly");
+			scarLocations.set("Pubic Mound", "pubic mound");
+
+			if (slave.dick > 0) {
+				scarLocations.set("Penis", "penis");
+			}
+			if (slave.balls > 0 && slave.scrotum > 0) {
+				scarLocations.set("Testicles", "testicle");
+			}
+
+			// Arms
+			scarLocations.set("Shoulders", "shoulder");
+			if (hasAnyNaturalArms(slave)) {
+				scarLocations.set("Arm, upper", "upper arm");
+				scarLocations.set("Arm, lower", "lower arm");
+				scarLocations.set("Wrists", "wrist");
+				scarLocations.set("Hands", "hand");
+			}
+
+			// Legs
+			scarLocations.set("Buttocks", "buttock");
+			if (hasAnyNaturalLegs(slave)) {
+				scarLocations.set("Thighs", "thigh");
+				scarLocations.set("Calves", "calf");
+				scarLocations.set("Ankles", "ankle");
+				scarLocations.set("Feet", "foot");
+			}
+		}
+
+		linkArray = [];
+		for (const [text, value] of scarLocations) {
+			linkArray.push(
+				App.UI.DOM.link(
+					text,
+					() => {
+						V.scarTarget.local = value;
+						refresh();
+					}
+				)
+			);
+		}
+		r.push(App.UI.DOM.generateLinksStrip(linkArray));
+		App.Events.addNode(el, r, "div");
+
+		r = [];
+		r.push(`Or a custom site:`);
+		r.push(
+			App.UI.DOM.makeTextBox(
+				V.scarTarget.local,
+				(v) => {
+					V.scarTarget.local = v;
+					refresh();
+				}
+			)
+		);
+		App.Events.addNode(el, r, "div");
+
+		// Correct some "bad" choices"
+		if (["exotic", "menacing"].includes(V.scarDesign.local)) {
+			if (V.scarTarget.local !== "cheek") {
+				V.scarTarget.local = "cheek";
+			}
+		}
+
+		r = [];
+
+		if (["ankle", "breast", "buttock", "calf", "cheek", "ear", "foot", "hand", "lower arm", "shoulder", "testicle", "thigh", "upper arm", "wrist"].includes(V.scarTarget.local)) {
+			const _leftTarget = ("left " + V.scarTarget.local);
+			const _rightTarget = ("right " + V.scarTarget.local);
+			if (slave.scar[_leftTarget]) {
+				r.push(`${His}${_leftTarget} is already marked with ${App.Desc.expandScarString(slave, _leftTarget)}.`);
+			}
+			if (slave.scar[_rightTarget]) {
+				r.push(`${His}${_rightTarget} is already marked with ${App.Desc.expandScarString(slave, _rightTarget)}.`);
+			}
+			r.push(`Scar ${him} now with ''${V.scarDesign.local}'' on the`);
+			let _left = 0, _right = 0;
+			// overwrite brand
+
+			if (!(["upper arm", "lower arm", "wrist", "hand"].includes(V.scarTarget.local) && getLeftArmID(slave) !== 1) && !(["thigh", "calf", "ankle", "foot"].includes(V.scarTarget.local) && getLeftLegID(slave) !== 1)) {
+				_left = 1;
+				// make next checks easier
+				r.push(
+					App.UI.DOM.link(
+						"left",
+						() => {
+							V.scarTarget.local = _leftTarget;
+							scarApplied = true;
+							App.Medicine.Modification.addScar(slave, _leftTarget, V.scarDesign.local);
+							billMod();
+							degradation += 10;
+							refresh();
+						}
+					)
+				);
+			}
+			if (!(["upper arm", "lower arm", "wrist", "hand"].includes(V.scarTarget.local) && getRightArmID(slave) !== 1) && !(["thigh", "calf", "ankle", "foot"].includes(V.scarTarget.local) && getRightLegID(slave) !== 1)) {
+				_right = 1;
+				// make next checks easier
+			}
+			if (_left && _right) {
+				r.push(`${V.scarTarget.local}, or the`);
+			}
+			if (_right) {
+				r.push(
+					App.UI.DOM.link(
+						"right",
+						() => {
+							V.scarTarget.local = _rightTarget;
+							scarApplied = true;
+							App.Medicine.Modification.addScar(slave, _rightTarget, V.scarDesign.local);
+							billSurgery();
+							degradation += 10;
+							refresh();
+						}
+					)
+				);
+			}
+			if (!_left || !_right) {
+				r.push(`${V.scarTarget.local}?`);
+			}
+		} 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(slave, cheat = false) {
+		const el = new DocumentFragment();
+		let p = document.createElement('p');
+		let div = document.createElement('div');
+
+		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(": ");
+			if (!cheat) {
+				div.append(
+					App.UI.DOM.link(
+						"Remove Brand",
+						() => {
+							brandApplied = false;
+							delete slave.brand[brandPlace];
+							billSurgery();
+							degradation -= 10;
+							refresh();
+						},
+					)
+				);
+			} else {
+				div.append(
+					App.UI.DOM.link(
+						"Remove Brand",
+						() => {
+							delete slave.brand[brandPlace];
+							brandRefresh();
+						},
+					)
+				);
+			}
+			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);
+		p = document.createElement('p');
+
+		div = document.createElement('div');
+		div.append(`Use ''${V.brandDesign.local}'' or choose another brand: `);
+		div.append(symbolOptions("personal"));
+		p.append(div);
+
+		p.append(symbolBlock("dirtyWord"));
+		p.append(symbolBlock("genitalSymbol"));
+		p.append(symbolBlock("silhouettes"));
+		p.append(symbolBlock("FS"));
+
+		div = document.createElement('div');
+		div.append(`Or design your own: `);
+		div.append(
+			App.UI.DOM.makeTextBox(
+				V.brandDesign.local,
+				v => {
+					V.brandDesign.local = v;
+					brandRefresh();
+				},
+			)
+		);
+		p.append(div);
+		el.append(p);
+
+		p = document.createElement('p');
+		App.UI.DOM.appendNewElement("div", p, "Choose a site for branding: ");
+		const body = slaveBody();
+		p.append(partLinks(body.head));
+		p.append(partLinks(body.torso));
+		p.append(partLinks(body.arms));
+		p.append(partLinks(body.legs));
+
+		div = document.createElement('div');
+		div.append(`Or a custom site: `);
+		div.append(
+			App.UI.DOM.makeTextBox(
+				V.brandTarget.local,
+				v => {
+					V.brandTarget.local = v;
+					brandRefresh();
+				},
+			)
+		);
+		p.append(div);
+		el.append(p);
+
+		p = document.createElement('p');
+
+		if (["ankle", "breast", "buttock", "calf", "cheek", "ear", "foot", "hand", "lower arm", "shoulder", "testicle", "thigh", "upper arm", "wrist"].includes(V.brandTarget.local)) {
+			const leftTarget = ("left " + V.brandTarget.local);
+			const rightTarget = ("right " + V.brandTarget.local);
+			if (slave.brand[leftTarget]) {
+				p.append(`${His} ${leftTarget} is already marked with ${slave.brand[leftTarget]}. `);
+			}
+			if (slave.brand[rightTarget]) {
+				p.append(`${His} ${rightTarget} is already marked with ${slave.brand[rightTarget]}. `);
+			}
+			p.append(`Brand ${him} now with ''${V.brandDesign.local}'' on the `); // todo: break out bold
+			let _left;
+			let _right;
+			if (
+				!(["upper arm", "lower arm", "wrist", "hand"].includes(V.brandTarget.local) && getLeftArmID(slave) !== 1) &&
+				!(["thigh", "calf", "ankle", "foot"].includes(V.brandTarget.local) && getLeftLegID(slave) !== 1)
+			) {
+				_left = 1;// make next checks easier
+				if (!cheat) {
+					p.append(
+						App.UI.DOM.link(
+							"left",
+							() => {
+								brandApplied = true;
+								slave.brand[leftTarget] = check(V.brandDesign.local);
+								billMod();
+								degradation += 10;
+								refresh();
+							},
+						)
+					);
+				} else {
+					p.append(
+						App.UI.DOM.link(
+							"left",
+							() => {
+								slave.brand[leftTarget] = check(V.brandDesign.local);
+								refresh();
+							},
+						)
+					);
+				}
+
+				if (!(["upper arm", "lower arm", "wrist", "hand"].includes(V.brandTarget.local) && getRightArmID(slave) !== 1) && !(["thigh", "calf", "ankle", "foot"].includes(V.brandTarget.local) && getRightLegID(slave) !== 1)) {
+					_right = 1; // make next checks easier
+				}
+				if (_left && _right) {
+					p.append(` ${V.brandTarget.local}, or the `);
+				}
+				if (_right) {
+					if (!cheat) {
+						p.append(
+							App.UI.DOM.link(
+								"right",
+								() => {
+									brandApplied = true;
+									slave.brand[rightTarget] = check(V.brandDesign.local);
+									billMod();
+									degradation += 10;
+									refresh();
+								},
+							)
+						);
+					} else {
+						p.append(
+							App.UI.DOM.link(
+								"right",
+								() => {
+									slave.brand[rightTarget] = check(V.brandDesign.local);
+									refresh();
+								},
+							)
+						);
+					}
+				}
+				p.append(`? `);
+				if (!_left || !_right) {
+					p.append(` ${V.brandTarget.local}`);
+					App.UI.DOM.appendNewElement("span", p, `Branding will slightly reduce ${his} beauty but may slowly increase your reputation.`, "note");
+				}
+			}
+		} else {
+			if (slave.brand[V.brandTarget.local] === V.brandDesign.local) {
+				p.append(`${He} already has ${V.brandDesign.local} on ${his} ${V.brandTarget.local}.`);
+			} else {
+				if (!cheat) {
+					p.append(
+						App.UI.DOM.link(
+							"Brand",
+							() => {
+								brandApplied = true;
+								slave.brand[V.brandTarget.local] = V.brandDesign.local;
+								billMod();
+								degradation += 10;
+								refresh();
+							},
+						)
+					);
+				} else {
+					p.append(
+						App.UI.DOM.link(
+							"Brand",
+							() => {
+								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 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 array = [];
+			for (const brand in list) {
+				const frag = new DocumentFragment();
+				if (!cheat && list[brand].hasOwnProperty("requirements")) {
+					if (!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.local = check(brand);
+							brandRefresh();
+						}
+					)
+				);
+				array.push(frag);
+			}
+			return App.UI.DOM.generateLinksStrip(array);
+		}
+
+		function slaveBody() {
+			const body = {};
+			// Sorted head to toe
+			// Head
+			body.head = {};
+			if (slave.earShape !== "none") {
+				body.head.ears = "Ears";
+			}
+			body.head.cheek = "Cheeks";
+			body.head.neck = "Neck";
+
+			// Torso
+			body.torso = {};
+			body.torso.chest = "Chest";
+			body.torso.breast = "Breasts";
+			body.torso.back = "Back";
+			body.torso["lower back"] = "Lower Back";
+			body.torso.belly = "Belly";
+			body.torso["pubic mound"] = "Pubic Mound";
+
+			if (slave.dick > 0) {
+				body.torso.penis = "Penis";
+			}
+			if (slave.balls > 0 && slave.scrotum > 0) {
+				body.torso.testicle = "Testicles";
+			}
+
+			// Arms
+			body.arms = {};
+			body.arms.shoulder = "Shoulders";
+			if (hasAnyNaturalArms(slave)) {
+				body.arms["upper arm"] = "Arm, upper";
+				body.arms["lower arm"] = "Arm, lower";
+				body.arms.wrist = "Wrists";
+				body.arms.hand = "Hands";
+			}
+
+			// Legs
+			body.legs = {};
+			body.legs.buttock = "Buttocks";
+			if (hasAnyNaturalLegs(slave)) {
+				body.legs.thigh = "Thighs";
+				body.legs.calf = "Calves";
+				body.legs.ankle = "Ankles";
+				body.legs.foot = "Feet";
+			}
+			return body;
+		}
+
+		function partLinks(bodyPartObj) {
+			const div = document.createElement("div");
+			div.classList.add("choices");
+			const array = [];
+			for (const bp in bodyPartObj) {
+				array.push(
+					App.UI.DOM.link(
+						bodyPartObj[bp],
+						() => {
+							V.brandTarget.local = check(bp);
+							brandRefresh();
+						}
+					)
+				);
+			}
+			div.append(App.UI.DOM.generateLinksStrip(array));
+			return div;
+		}
+
+		function check(brand) {
+			switch (brand) {
+				case "a big helping of your favorite food":
+					return "a big helping of " + V.PC.refreshment;
+				default:
+					return brand;
+			}
+		}
+
+		function brandRefresh() {
+			jQuery('#brand-selection').empty().append(brand(slave, cheat));
+		}
+	}
+
+
+	function refresh() {
+		jQuery("#body-modification").empty().append(createPage());
+	}
+
+	function billMod() {
+		if (!cheat) {
+			cashX(forceNeg(V.modCost), "slaveMod", slave);
+		}
+	}
+
+	function billSurgery() {
+		if (!cheat) {
+			cashX(forceNeg(V.surgeryCost), "slaveSurgery", slave);
+		}
+	}
+};
-- 
GitLab