code:
// WARNING This file defines objects referenced in slaveSummaryWidgets.js.
// Either keep this file above the slaveSummaryWidgets.js in the name ordered list
// (tweego process them in this order), or rework the references.

/* eslint-disable camelcase */

App.UI.SlaveSummaryImpl = function() {
	const data = App.Data.SlaveSummary;

	const helpers = function() {
		/**
		 * @param {HTMLElement} element
		 * @param {string|string[]} [classNames]
		 */
		function _addClassNames(element, classNames) {
			if (classNames != undefined) { /* eslint-disable-line eqeqeq */
				if (Array.isArray(classNames)) {
					element.classList.add(...classNames);
				} else {
					element.classList.add(classNames);
				}
			}
		}

		/**
		 * @param {Node} container
		 * @param {string} text
		 * @param {string|string[]} [classNames]
		 * @param {boolean} [stdDecor=false]
		 * @param {number} [value]
		 */
		function makeSpan(container, text, classNames, stdDecor = false, value) {
			let r = document.createElement("span");
			_addClassNames(r, classNames);
			if (value != undefined && V.summaryStats) { /* eslint-disable-line eqeqeq */
				text += `[${value}]`;
			}
			r.textContent = stdDecor ? `${capFirstChar(text)}. ` : text + ' ';
			if (container) {
				container.appendChild(r);
			}
			return r;
		}

		/**
		 * @param {Node} container
		 * @param {string} text
		 * @returns {Text}
		 */
		function addText(container, text) {
			const r = document.createTextNode(text);
			if (container) {
				container.appendChild(r);
			}
			return r;
		}

		/**
		 * @param {Node} [container]
		 * @param {string|string[]} [classNames]
		 */
		function makeBlock(container, classNames) {
			let r = document.createElement("span");
			r.classList.add("ssb");
			_addClassNames(r, classNames);
			if (container) {
				container.appendChild(r);
			}
			return r;
		}

		/**
		 * @param {Node} container
		 * @param {string|string[]} [classNames]
		 * @returns {HTMLParagraphElement}
		 */
		function makeParagraph(container, classNames) {
			let r = document.createElement("p");
			r.classList.add("si");
			_addClassNames(r, classNames);
			if (container) {
				container.appendChild(r);
			}
			return r;
		}

		/**
		 * @param {object} dict
		 * @param {*} value
		 * @param {*} [defaultValue]
		 * @returns {*|null}
		 */
		function getExactRating(dict, value, defaultValue = null) {
			const res = dict[value];
			return res ? res : defaultValue;
		}

		/**
		 * @param {object} ratings
		 * @param {number} value
		 * @returns {*|null}
		 */
		function getNumericRating(ratings, value) {
			for (const key in ratings) {
				if (parseInt(key) >= value) {
					return ratings[key];
				}
			}
			return null;
		}

		/**
		 * @param {object} ratings
		 * @param {number[]} values
		 * @returns {*|null}
		 */
		function getMultiNumericRating(ratings, values) {
			const firstRating = getNumericRating(ratings, values[0]);
			if (firstRating === null || typeof firstRating === "string" || firstRating.hasOwnProperty("desc")) {
				return firstRating;
			}
			return getMultiNumericRating(firstRating, values.slice(1));
		}

		/**
		 * @typedef {object} StyledDesc
		 * @property {string} desc
		 * @property {string|string[]} [style]
		 */

		/** @typedef {Object.<string, StyledDesc>} StyledRatings */
		/**
		 * @param {Node} container
		 * @param {StyledRatings} ratings
		 * @param {number} [value]
		 * @param {number} [offset] value offset in the ratings dictionary (to eliminate negative values)
		 * @param {boolean} [stdDecor=false]
		 */
		function makeRatedStyledSpan(container, ratings, value, offset = 0, stdDecor = false) {
			/** @type {StyledDesc} */
			const d = getNumericRating(ratings, value + offset);
			if (d) {
				makeSpan(container, d.desc, d.style, stdDecor, value);
			}
		}

		/**
		 * @param {Node} container
		 * @param {StyledDesc} styledDesc
		 * @param {number} [value]
		 * @param {boolean} [stdDecor]
		 */
		function makeStyledSpan(container, styledDesc, value, stdDecor = false) {
			if (styledDesc) {
				makeSpan(container, styledDesc.desc, styledDesc.style, stdDecor, value);
			}
		}

		/**
		 * @param {Node} container
		 * @param {StyledRatings} ratings
		 * @param {string|number} value
		 */
		function makeMappedStyledSpan(container, ratings, value) {
			const d = ratings[value];
			if (d) {
				makeSpan(container, d.desc, d.style);
			}
		}

		/**
		 * @param {Node} container
		 * @param {Object.<string, string>} ratings
		 * @param {string|number} value
		 * @param {string|string[]} [classNames]
		 */
		function makeMappedSpan(container, ratings, value, classNames) {
			const d = ratings[value];
			if (d) {
				makeSpan(container, d, classNames);
			}
		}

		/**
		 * Returns first three string characters with the first one uppercased (string -> Str)
		 * @param {string} s
		 * @returns {string}
		 */
		function firstThreeUc(s) {
			return s.charAt(0).toUpperCase() + s.charAt(1) + s.charAt(2);
		}

		/**
		 * @param {FC.ArcologyState} arcology
		 */
		function syncFSData(arcology) {
			arcology = arcology || V.arcologies[0];
			for (const fsp of App.Data.misc.FutureSocieties) {
				/** @type {FC.FSPolicy} */
				const policy = arcology[fsp];
				const p = fsp.slice(2);
				FSData.policy[p] = {
					active: policy === "unset" ? 0 : 1,
					strength: Math.trunc(policy === "unset" ? 0: policy / 10)
				};
			}

			const dislikeBigButts = (FSData.policy.TransformationFetishist.strength < 2) && (FSData.policy.HedonisticDecadence.strength < 2) && (FSData.policy.AssetExpansionist.strength < 2) && (arcology.FSIntellectualDependencyLawBeauty === 0);
			FSData.bigButts = dislikeBigButts ? -1 : 0;
		}
		/**
		 * @typedef {Object} FSDatum
		 * @property {number} active FS policy is active (0,1)
		 * @property {number} strength FS decoration level divided by 10
		 */
		const FSData = {
			/** @type {Object.<string, FSDatum>} */
			policy: {},
			bigButts: 0,
		};

		return {
			addText: addText,
			makeSpan: makeSpan,
			makeBlock: makeBlock,
			makeParagraph: makeParagraph,
			getExactRating: getExactRating,
			getNumericRating: getNumericRating,
			getMultiNumericRating: getMultiNumericRating,
			makeStyledSpan: makeStyledSpan,
			makeRatedStyledSpan: makeRatedStyledSpan,
			makeMappedStyledSpan: makeMappedStyledSpan,
			makeMappedSpan: makeMappedSpan,
			firstThreeUc: firstThreeUc,
			syncFSData: syncFSData,
			FSData: FSData,
		};
	}();

	const bits = function() {
		const addText = helpers.addText;
		const makeSpan = helpers.makeSpan;
		const makeBlock = helpers.makeBlock;
		const makeRatedStyledSpan = helpers.makeRatedStyledSpan;

		/**
		 * Returns index in the hips-ass rating table
		 * @param {App.Entity.SlaveState} slave
		 * @returns {number} 0 if butt is considered too small, 1 is too big, -1 otherwise.
		 */
		function hipsAssRating(slave) {
			const FSData = helpers.FSData;
			if (slave.hips < -1) {
				if (slave.butt > 2 && FSData.bigButts <= 0) {
					return 1;
				}
			} else if (slave.hips < 0) {
				if (slave.butt > 4 && FSData.bigButts <= 0) {
					return 1;
				}
			} else if (slave.hips > 2) {
				if (slave.butt <= 8) {
					return 0;
				}
			} else if (slave.hips > 1) {
				if (slave.butt <= 3 && (FSData.policy.SlimnessEnthusiast.active === 0 || (slave.boobs >= 500))) {
					return 0;
				}
			} else if (slave.hips > 0) {
				if (slave.butt > 8) {
					if (FSData.bigButts <= 0) {
						return 1;
					}
				} else if (slave.butt <= 2 && ((FSData.policy.SlimnessEnthusiast.active === 0) || (slave.boobs >= 500))) {
					return 0;
				}
			} else {
				if (slave.butt > 6) {
					if (FSData.bigButts <= 0) {
						return 1;
					}
				} else if (slave.butt <= 1 && (FSData.policy.SlimnessEnthusiast.active === 0 || (slave.boobs >= 500))) {
					return 0;
				}
			}
			return -1;
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {App.Data.SlaveSummary.SmartPiercing} spData
		 * @returns {string}
		 */
		function smartFetishStr(slave, spData) {
			if (slave.fetishKnown === 1) {
				if (slave.clitSetting === "off") {
					return spData.setting.off;
				} else if ((slave.energy <= 95) && (slave.clitSetting === "all")) {
					return spData.setting.all;
				} else if ((slave.energy > 5) && (slave.clitSetting === "none")) {
					return spData.setting.none;
				} else if (((slave.fetish !== "none") && (slave.clitSetting === "vanilla"))) {
					return spData.setting.vanilla;
				} else if (slave.fetishStrength <= 95 || slave.fetish !== slave.clitSetting) {
					const s = spData.setting[slave.clitSetting];
					if (s) {
						return s;
					}
				}
				if (!["anti-men", "anti-women", "men", "women"].includes(slave.clitSetting)) {
					return spData.setting.monitoring;
				}
			} else {
				return spData.setting[slave.clitSetting];
			}
			return null;
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {App.Data.SlaveSummary.SmartPiercing} spData
		 * @returns {string}
		 */
		function smartAttractionStr(slave, spData) {
			const sps = spData.setting;
			const cs = slave.clitSetting;
			if (slave.attrKnown === 1) {
				switch (cs) {
					case "women":
						if (slave.attrXX < 95) {
							return sps.women;
						} else {
							return sps.monitoring;
						}
					case "men":
						if (slave.attrXY < 95) {
							return sps.men;
						} else {
							return sps.monitoring;
						}
					case "anti-women":
						if (slave.attrXX > 0) {
							return sps["anti-women"];
						} else {
							return sps.monitoring;
						}
					case "anti-men":
						if (slave.attrXY > 0) {
							return sps["anti-men"];
						} else {
							return sps.monitoring;
						}
				}
			} else {
				switch (cs){
					// fall-through
					case "women":
					case "men":
					case "anti-women":
					case "anti-men":
						return sps[cs];
				}
			}
			return null;
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_health(slave, c) {
			if (slave.health.health < -20) {
				makeSpan(c, "H", ["red", "strong"], true, slave.health.health);
			} else if (slave.health.health <= 20) {
				makeSpan(c, "H", ["yellow", "strong"], true, slave.health.health);
			} else if (slave.health.health > 20) {
				makeSpan(c, "H", ["green", "strong"], true, slave.health.health);
			}
			if (passage() === "Clinic" && V.clinicUpgradeScanner) {
				if (slave.chem > 15) {
					makeSpan(c, `C${Math.ceil(slave.chem / 10)}`, ["cyan", "strong"]);
				} else if (slave.chem <= 15 && slave.assignment === Job.CLINIC) {
					makeSpan(c, `CSafe`, ["green", "strong"]);
				}
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_illness(slave, c) {
			if (slave.health.illness > 2) {
				makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness);
			} else if (slave.health.illness > 0) {
				makeSpan(c, `Ill${slave.health.illness}`, ["yellow", "strong"], true, slave.health.illness);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_tired(slave, c) {
			helpers.makeRatedStyledSpan(c, data.short.health.tiredness, slave.health.tired, 0, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_health(slave, c) {
			helpers.makeRatedStyledSpan(c, data.long.health.health, slave.health.health, 100, true);
			if (passage() === "Clinic" && V.clinicUpgradeScanner) {
				if (slave.chem > 15) {
					makeSpan(c, `Carcinogen buildup: ${Math.ceil(slave.chem / 10)}.`, "cyan");
				} else if (slave.chem <= 15 && slave.assignment === Job.CLINIC) {
					makeSpan(c, `Safe chem levels.`, "green");
				}
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_illness(slave, c) {
			makeRatedStyledSpan(c, data.long.health.illness, slave.health.illness, 0, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_tired(slave, c) {
			makeRatedStyledSpan(c, data.long.health.tiredness, slave.health.tired, 0, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_age(slave, c) {
			const style = "pink";
			makeSpan(c, V.showAgeDetail ? `Age ${slave.actualAge}` : helpers.getNumericRating(data.long.body.age, slave.actualAge), style, true);
			/*
			 ** No NCS, then do the standard, However because of the wrinkles of Incubators, as long as visual age is greater
			 ** than or equal to physical age, we do the old physical body/Looks for fresh out of the can NCS slaves.
			 */
			if (((slave.geneMods.NCS === 0) || (slave.visualAge >= slave.physicalAge))) {
				if (slave.actualAge !== slave.physicalAge) {
					makeSpan(c, `${slave.physicalAge} year old body`, style, true);
				}
				if (slave.visualAge !== slave.physicalAge) {
					makeSpan(c, `Looks ${slave.visualAge}`, style, true);
				}
			} else {
				/*
				 ** Now the rub. The use of physical Age for the year old body above, basically conflicts with the changes
				 ** that NCS introduces, so here to *distinguish* the changes, we use visual age with the 'year old body'
				 ** and appears, for example: Slave release from incubator at age 10, Her summary would show, 'Age 0. 10
				 ** year old body.' But if she's given NCS a few weeks after release, while she's still before her first
				 ** birthday, it'll appear the same. But once her birthday fires, if we ran with the above code it would
				 ** say: 'Age 1. 11 year old body.' -- this conflicts with the way NCS works though, because she hasn't
				 ** visually aged, so our change here makes it say 'Age 1. Appears to have a 10 year old body.'
				 */
				makeSpan(c, `Appears to have a ${slave.visualAge} year old body`, style, true);
			}
			if (slave.geneMods.NCS === 1) {
				makeSpan(c, "NCS", "orange");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_face(slave, c) {
			const r = helpers.getNumericRating(data.long.body.face, slave.face + 100);
			makeSpan(c, `${r.desc} ${slave.faceShape} face`, r.style, true, slave.face);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_eyes(slave, c) {
			if (!canSee(slave)) {
				makeSpan(c, "Blind.", "red");
			} else if (!canSeePerfectly(slave)) {
				makeSpan(c, "Nearsighted.", "yellow");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_ears(slave, c) {
			if (slave.hears <= -2) {
				makeSpan(c, "Deaf.", "red");
			} else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) {
				makeSpan(c, "Hard of hearing.", "yellow");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_lips(slave, c) {
			makeRatedStyledSpan(c, data.long.body.lips, slave.lips, 0, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_teeth(slave, c) {
			helpers.makeMappedStyledSpan(c, data.long.body.teeth, slave.teeth);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_muscles(slave, c) {
			const h = helpers;
			h.makeStyledSpan(c, h.getMultiNumericRating(data.long.body.muscles, [slave.muscles + 100, h.FSData.policy.PhysicalIdealist.active]), slave.muscles, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_voice(slave, c) {
			if (slave.voice === 0) {
				makeSpan(c, "Mute.", "red");
			} else {
				helpers.makeMappedStyledSpan(c, data.long.accent, slave.accent);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_tits_ass(slave, c) {
			const h = helpers;
			h.makeStyledSpan(c,
				h.getMultiNumericRating(data.long.body.titsAss,
					[slave.boobs, slave.butt, h.FSData.policy.AssetExpansionist.active, slave.weight + 100, slave.muscles + 100]));
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_hips(slave, c) {
			const di = hipsAssRating(slave);
			if (di >= 0) {
				helpers.makeMappedStyledSpan(c, data.long.body.hipsAss, di);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_waist(slave, c) {
			makeRatedStyledSpan(c, data.long.body.waist, slave.waist, 100, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_implants(slave, c) {
			const styles = "pink";
			if ((slave.boobsImplant !== 0) || (slave.buttImplant !== 0) || (slave.lipsImplant !== 0) || (slave.bellyImplant !== -1)) {
				makeSpan(c, "Implants.", styles);
			} else if ((slave.faceImplant >= 30) || (slave.waist < -95)) {
				makeSpan(c, "Surgery enhanced.", styles);
			} else {
				makeSpan(c, "All natural.", styles);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_lactation(slave, c) {
			if (slave.lactation === 1) {
				makeSpan(c, "Lactating naturally.", "pink");
			} else if (slave.lactation === 2) {
				makeSpan(c, "Heavy lactation.", "pink");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_mods(slave, c) {
			V.modScore = SlaveStatsChecker.modScore(slave);
			if (slave.corsetPiercing === 0 && V.piercingScore < 3 && V.tatScore < 2) {
				return;
			} else if (V.modScore > 15 || (V.piercingScore > 8 && V.tatScore > 5)) {
				makeSpan(c, "Extensive body mods.");
			} else if (V.modScore > 7) {
				makeSpan(c, "Noticeable body mods.");
			} else {
				makeSpan(c, "Light body mods.");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_age(slave, c) {
			let r = makeSpan(c, "", "pink");
			if (V.showAgeDetail === 1) {
				r.textContent += slave.actualAge.toString();
			} else if (slave.actualAge >= 18) {
				r.textContent += helpers.getNumericRating(data.short.body.age, slave.actualAge);
			}

			if (slave.actualAge !== slave.physicalAge) {
				r.textContent += ` w ${slave.physicalAge}y-bdy`;
			}
			if (slave.visualAge !== slave.physicalAge) {
				r.textContent += ` Lks${slave.visualAge}`;
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_face(slave, c) {
			makeRatedStyledSpan(c, data.short.body.face, slave.face, 100, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_eyes(slave, c) {
			if (!canSee(slave)) {
				makeSpan(c, "Blind", "red");
			} else if (!canSeePerfectly(slave)) {
				makeSpan(c, "Sight-", "yellow");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_ears(slave, c) {
			if (slave.hears === -2) {
				makeSpan(c, "Deaf", "red");
			} else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) {
				makeSpan(c, "Hearing-", "yellow");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_lips(slave, c) {
			makeRatedStyledSpan(c, data.short.body.lips, slave.lips, 0, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_teeth(slave, c) {
			helpers.makeMappedStyledSpan(c, data.short.body.teeth, slave.teeth);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_muscles(slave, c) {
			const h = helpers;
			h.makeStyledSpan(c, h.getMultiNumericRating(data.short.body.muscles, [slave.muscles + 100, h.FSData.policy.PhysicalIdealist.active]), slave.muscles, true);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_voice(slave, c) {
			if (slave.voice === 0) {
				makeSpan(c, "Mute", "red");
			} else {
				helpers.makeMappedStyledSpan(c, data.short.accent, slave.accent);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_tits_ass(slave, c) {
			const h = helpers;
			h.makeStyledSpan(c,
				h.getMultiNumericRating(data.short.body.titsAss,
					[slave.boobs, slave.butt, h.FSData.policy.AssetExpansionist.active, slave.weight + 100, slave.muscles + 100]));
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_hips(slave, c) {
			const di = hipsAssRating(slave);
			if (di >= 0) {
				helpers.makeMappedStyledSpan(c, data.short.body.hipsAss, di);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_waist(slave, c) {
			makeRatedStyledSpan(c, data.short.body.waist, slave.waist, 100, false);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_implants(slave, c) {
			if ((slave.boobsImplant === 0) && (slave.buttImplant === 0) && (slave.waist >= -95) && (slave.lipsImplant === 0) && (slave.faceImplant <= 5) && (slave.bellyImplant === -1)) {
				makeSpan(c, "Natr", "pink");
			} else {
				makeSpan(c, "Impl", "pink");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_lactation(slave, c) {
			if (slave.lactation === 1) {
				makeSpan(c, "Lact", "pink");
			} else if (slave.lactation === 2) {
				makeSpan(c, "Lact", "pink");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_mods(slave, c) {
			V.modScore = SlaveStatsChecker.modScore(slave);
			if (slave.corsetPiercing === 0 && V.piercingScore < 3 && V.tatScore < 2) {
				return;
			} else if (V.modScore > 15 || (V.piercingScore > 8 && V.tatScore > 5)) {
				makeSpan(c, "Mods++");
			} else if (V.modScore > 7) {
				makeSpan(c, "Mods+");
			} else {
				makeSpan(c, "Mods");
			}
			if (!jQuery.isEmptyObject(slave.brand)) {
				makeSpan(c, "Br");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_intelligence(slave, c) {
			if (slave.fetish === "mindbroken") {
				return;
			}
			const intelligence = slave.intelligence + slave.intelligenceImplant;
			const educationStr = helpers.getNumericRating(data.short.mental.education, slave.intelligenceImplant + 15);
			const intelligenceRating = helpers.getNumericRating(data.short.mental.intelligence, intelligence + 100);
			makeSpan(c, `${intelligenceRating.desc}${educationStr}`, intelligenceRating.style, true, intelligence);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_skills(slave, c) {
			const sd = data.short.skills;
			let _SSkills = (slave.skill.anal + slave.skill.oral);
			if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) {
				helpers.makeStyledSpan(c, sd.mss);
			} else {
				_SSkills += slave.skill.vaginal;
				helpers.makeStyledSpan(c, helpers.getMultiNumericRating(sd.sex, [_SSkills, slave.vagina >= 0 ? 1 : 0]), Math.trunc(_SSkills), true);
				helpers.makeRatedStyledSpan(c, sd.whoring, slave.skill.whoring, 0, true);
				helpers.makeRatedStyledSpan(c, sd.entertainment, slave.skill.entertainment, 0, true);
			}
			if (slave.skill.combat > 0) {
				helpers.makeStyledSpan(c, sd.fighter);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_prestige(slave, c) {
			helpers.makeMappedStyledSpan(c, data.short.prestige, slave.prestige);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_porn_prestige(slave, c) {
			helpers.makeMappedStyledSpan(c, data.short.pornPrestige, slave.porn.prestige);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_intelligence(slave, c) {
			if (slave.fetish === "mindbroken") {
				return;
			}
			const intelligence = slave.intelligence + slave.intelligenceImplant;
			const educationStr = helpers.getNumericRating(data.long.mental.education, slave.intelligenceImplant + 15);
			const intelligenceRating = helpers.getNumericRating(data.long.mental.intelligence, intelligence + 100);
			makeSpan(c, `${intelligenceRating.desc}${educationStr}`, intelligenceRating.style, true, intelligence);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_skills(slave, c) {
			const sd = data.long.skills;
			let _SSkills = (slave.skill.anal + slave.skill.oral);
			if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) {
				helpers.makeStyledSpan(c, sd.mss);
			} else {
				_SSkills += slave.skill.vaginal;
				helpers.makeStyledSpan(c, helpers.getMultiNumericRating(sd.sex, [_SSkills, slave.vagina >= 0 ? 1 : 0]), Math.trunc(_SSkills), true);
				helpers.makeRatedStyledSpan(c, sd.whoring, slave.skill.whoring, 0, true);
				helpers.makeRatedStyledSpan(c, sd.entertainment, slave.skill.entertainment, 0, true);
			}
			if (slave.skill.combat > 0) {
				helpers.makeStyledSpan(c, sd.fighter);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_prestige(slave, c) {
			helpers.makeMappedStyledSpan(c, data.long.prestige, slave.prestige);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_porn_prestige(slave, c) {
			helpers.makeMappedStyledSpan(c, data.long.pornPrestige, slave.porn.prestige);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_clothes(slave, c) {
			makeSpan(c, helpers.getExactRating(data.long.clothes, slave.clothes,  'Naked.'));
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_collar(slave, c) {
			helpers.makeMappedSpan(c, data.long.accessory.collar, slave.collar);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_mask(slave, c) {
			helpers.makeMappedSpan(c, data.long.accessory.faceAccessory, slave.faceAccessory);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_mouth(slave, c) {
			helpers.makeMappedSpan(c, data.long.accessory.mouthAccessory, slave.mouthAccessory);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_belly(slave, c) {
			helpers.makeMappedSpan(c, data.long.accessory.belly, slave.bellyAccessory);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_arms(slave, c) {
			if (["hand gloves", "elbow gloves"].includes(slave.armAccessory)) {
				makeSpan(c, slave.armAccessory, undefined, true);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_legs(slave, c) {
			if (slave.legAccessory === "short stockings") {
				makeSpan(c, "Short stockings.");
			} else if (slave.legAccessory === "long stockings") {
				makeSpan(c, "Long stockings.");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_shoes(slave, c) {
			if (["boots", "extreme heels", "extreme platform heels", "flats", "heels", "platform heels", "platform shoes", "pumps"].includes(slave.shoes)) {
				makeSpan(c, slave.shoes, undefined, true);
			} else if (slave.heels === 1) {
				makeSpan(c, "Crawling.", "yellow");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_chastity(slave, c) {
			if (slave.chastityAnus === 1 && slave.chastityPenis === 1 && slave.chastityVagina === 1) {
				makeSpan(c, "Full chastity.");
			} else if (slave.chastityPenis === 1 && slave.chastityVagina === 1) {
				makeSpan(c, "Genital chastity.");
			} else if ((slave.chastityAnus === 1 && slave.chastityVagina === 1) || (slave.chastityAnus === 1 && slave.chastityPenis === 1)) {
				makeSpan(c, "Combined chastity.");
			} else if (slave.chastityVagina === 1) {
				makeSpan(c, "Vaginal chastity.");
			} else if (slave.chastityPenis === 1) {
				makeSpan(c, "Chastity cage.");
			} else if (slave.chastityAnus === 1) {
				makeSpan(c, "Anal chastity.");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_vaginal_acc(slave, c) {
			if (slave.vaginalAttachment !== "vibrator") {
				helpers.makeMappedSpan(c, data.long.accessory.vaginal, slave.vaginalAccessory);
			}
			if (slave.vaginalAttachment !== "none") {
				switch (slave.vaginalAttachment) {
					case "vibrator":
						makeSpan(c, "Vibrating dildo.");
						break;
				}
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_dick_acc(slave, c) {
			switch (slave.dickAccessory) {
				case "sock":
					makeSpan(c, "Cock sock.");
					break;
				case "bullet vibrator":
					makeSpan(c, "Frenulum bullet vibrator.");
					break;
				case "smart bullet vibrator":
					makeSpan(c, "Smart frenulum bullet vibrator.");
					break;
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_buttplug(slave, c) {
			helpers.makeMappedSpan(c, data.long.accessory.buttplug, slave.buttplug);
			helpers.makeMappedSpan(c, data.long.accessory.buttplugAttachment, slave.buttplugAttachment);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_fetish(slave, c) {
			function fetishStr(slave) {
				const tbl = data.short.fetish[slave.fetish];
				if (!tbl || typeof tbl === 'string') {
					return tbl;
				}
				return helpers.getNumericRating(tbl, slave.fetishStrength);
			}
			const fStr = fetishStr(slave);
			if (fStr) {
				makeSpan(c, fStr, "lightcoral", true, slave.fetishStrength);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_attraction(slave, c) {
			const sd = data.short.sexDrive;
			// check for special cases first
			if (slave.energy > 95 && slave.attrXX > 95 && slave.attrXY > 95) {
				helpers.makeStyledSpan(c, sd.synergy.nymphomni);
			} else if (slave.attrXX > 95 && slave.attrXY > 95) {
				helpers.makeStyledSpan(c, sd.synergy.omni);
			} else {
				helpers.makeRatedStyledSpan(c, sd.XY, slave.attrXY, 0, true);
				helpers.makeRatedStyledSpan(c, sd.XX, slave.attrXX, 0, true);
				helpers.makeRatedStyledSpan(c, sd.energy, slave.energy, 0, true);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_smart_fetish(slave, c) {
			const s = smartFetishStr(slave, data.short.smartPiercing);
			if (s) {
				makeSpan(c, s);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_smart_attraction(slave, c) {
			/*
			if (slave.attrKnown === 1) {
				if ((slave.attrXX < 100) && (slave.clitSetting === "women")) {
					makeSpan(c, `SP: women.`);
				} else if ((slave.attrXY < 100) && (slave.clitSetting === "men")) {
					makeSpan(c, `SP: men.`);
				}
			} else {
				if (slave.clitSetting === "women") {
					makeSpan(c, `SP: women.`);
				} else if (slave.clitSetting === "men") {
					makeSpan(c, `SP: men.`);
				}
			}*/
			const s = smartAttractionStr(slave, data.short.smartPiercing);
			if (s) {
				makeSpan(c, s);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_behavior_flaw(slave, c) {
			helpers.makeMappedSpan(c, data.short.mental.behavioralFlaw, slave.behavioralFlaw, "red");
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_sex_flaw(slave, c) {
			helpers.makeMappedStyledSpan(c, data.short.mental.sexualFlaw, slave.sexualFlaw);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_behavior_quirk(slave, c) {
			helpers.makeMappedSpan(c, data.short.mental.behavioralQuirk, slave.behavioralQuirk, "green");
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_sex_quirk(slave, c) {
			helpers.makeMappedSpan(c, data.short.mental.sexualQuirk, slave.sexualQuirk, "green");
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_fetish(slave, c) {
			function fetishStr(slave) {
				const tbl = data.long.fetish[slave.fetish];
				if (!tbl || typeof tbl === 'string') {
					return tbl;
				}
				return helpers.getNumericRating(tbl, slave.fetishStrength);
			}
			const fStr = fetishStr(slave);
			if (fStr) {
				makeSpan(c, fStr, "lightcoral", true, slave.fetishStrength);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_attraction(slave, c) {
			const sd = data.long.sexDrive;

			// check for special cases first
			if (slave.energy > 95 && slave.attrXX > 95 && slave.attrXY > 95) {
				helpers.makeStyledSpan(c, sd.synergy.nymphomni);
			} else if (slave.attrXX > 95 && slave.attrXY > 95) {
				helpers.makeStyledSpan(c, sd.synergy.omni);
			} else {
				helpers.makeRatedStyledSpan(c, sd.XY, slave.attrXY, 0, true);
				helpers.makeRatedStyledSpan(c, sd.XX, slave.attrXX, 0, true);
				helpers.makeRatedStyledSpan(c, sd.energy, slave.energy, 0, true);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_smart_fetish(slave, c) {
			const s = smartFetishStr(slave, data.long.smartPiercing);
			if (s) {
				makeSpan(c, s);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_smart_attraction(slave, c) {
			const s = smartAttractionStr(slave, data.long.smartPiercing);
			if (s) {
				makeSpan(c, s);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_behavior_flaw(slave, c) {
			helpers.makeMappedSpan(c, data.long.mental.behavioralFlaw, slave.behavioralFlaw, "red");
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_sex_flaw(slave, c) {
			switch (slave.sexualFlaw) {
				case "hates oral":
				case "hates anal":
				case "hates penetration":
				case "shamefast":
					makeSpan(c, slave.sexualFlaw, "red", true);
					break;
				case "idealistic":
				case "repressed":
				case "apathetic":
				case "crude":
				case "judgemental":
					makeSpan(c, `Sexually ${slave.sexualFlaw}.`, "red");
					break;
				case "cum addict":
				case "anal addict":
				case "attention whore":
					makeSpan(c, slave.sexualFlaw, "yellow", true);
					break;
				case "breast growth":
					makeSpan(c, `Breast obsession.`, "yellow");
					break;
				case "abusive":
				case "malicious":
					makeSpan(c, `Sexually ${slave.sexualFlaw}.`, "yellow");
					break;
				case "self hating":
					makeSpan(c, `Self hatred.`, "yellow");
					break;
				case "neglectful":
					makeSpan(c, `Self neglectful.`, "yellow");
					break;
				case "breeder":
					makeSpan(c, `Breeding obsession.`, "yellow");
					break;
				default:
					slave.sexualFlaw = "none";
					break;
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_behavior_quirk(slave, c) {
			switch (slave.behavioralQuirk) {
				case "confident":
				case "cutting":
				case "funny":
				case "fitness":
				case "adores women":
				case "adores men":
				case "insecure":
				case "sinful":
				case "advocate":
					makeSpan(c, slave.behavioralQuirk, "green", true);
					break;
				default:
					slave.behavioralQuirk = "none";
					break;
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_sex_quirk(slave, c) {
			switch (slave.sexualQuirk) {
				case "gagfuck queen":
				case "painal queen":
				case "strugglefuck queen":
				case "tease":
				case "romantic":
				case "perverted":
				case "caring":
				case "unflinching":
				case "size queen":
					makeSpan(c, slave.sexualQuirk, "green", true);
					break;
				default:
					slave.sexualQuirk = "none";
					break;
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_family(slave, c) {
			let res = "";
			let handled = 0;
			if (slave.mother > 0) {
				const _ssj = V.slaves.findIndex(s => s.ID === slave.mother);
				if (_ssj !== -1) {
					res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).daughter}`;
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTermShort(slave);
						res += ` & ${friendShipShort}`;
						handled = 1;
					}
				}
				res += " ";
			} else if (slave.mother === -1) {
				res += `Your ${getPronouns(slave).daughter}`;
				if (slave.relationship < -1) {
					res += ` & ${PCrelationshipTerm(slave)}`;
					handled = 1;
				}
				res += " ";
			} else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) {
				res += `${V.missingTable[slave.mother].fullName}'s ${getPronouns(slave).daughter} `;
			}
			if (slave.father > 0 && slave.father !== slave.mother) {
				const _ssj = V.slaves.findIndex(s => s.ID === slave.father);
				if (_ssj !== -1) {
					res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).daughter}`;
					if (slave.relationshipTarget === V.slaves[_ssj].ID && handled !== 1) {
						const friendShipShort = relationshipTermShort(slave);
						res += ` & ${friendShipShort}`;
						handled = 1;
					}
				}
				res += " ";
			} else if (slave.father === -1 && slave.mother !== -1) {
				res += `Your ${getPronouns(slave).daughter}`;
				if (slave.relationship < -1) {
					res += ` & ${PCrelationshipTerm(slave)}`;
					handled = 1;
				}
				res += " ";
			} else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) {
				res += `${V.missingTable[slave.father].fullName}'s ${getPronouns(slave).daughter}`;
			}
			if (slave.daughters === 1) {
				let _ssj = V.slaves.findIndex(s => s.mother === slave.ID);
				if (_ssj !== -1) {
					res += `${SlaveFullName(V.slaves[_ssj])}'s mother`;
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTermShort(slave);
						res += ` & ${friendShipShort}`;
						handled = 1;
					}
				}
				res += " ";
				_ssj = V.slaves.findIndex(s => s.father === slave.ID);
				if (_ssj !== -1) {
					res += `${SlaveFullName(V.slaves[_ssj])}'s father`;
					if (slave.relationshipTarget === V.slaves[_ssj].ID && handled !== 1) {
						const friendShipShort = relationshipTermShort(slave);
						res += ` & ${friendShipShort}`;
						handled = 1;
					}
				}
				res += " ";
			} else if (slave.daughters > 1) {
				res += `multiple daughters `;
			}
			if (slave.sisters === 1) {
				const _ssj = V.slaves.findIndex(s => areSisters(s, slave) > 0);
				if (_ssj !== -1) {
					res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).sister}`;
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTermShort(slave);
						res += `& ${friendShipShort}`;
						handled = 1;
					}
				}
				res += " ";
			} else if (slave.sisters > 1) {
				res += `multiple sisters `;
			}
			if (slave.relationship > 0 && handled !== 1) {
				const _ssj = V.slaves.findIndex(s => s.ID === slave.relationshipTarget);
				if (_ssj !== -1) {
					res += `${SlaveFullName(V.slaves[_ssj])}'s`;
					const friendShipShort = relationshipTermShort(slave);
					res += ` ${friendShipShort}`;
				}
			} else if (slave.relationship === -3 && slave.mother !== -1 && slave.father !== -1) {
				res += `Your ${getPronouns(slave).wife}`;
			} else if (slave.relationship === -2) {
				res += `E Bonded`;
			} else if (slave.relationship === -1) {
				res += `E Slut`;
			}
			helpers.makeSpan(c, res);
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_clone(slave, c) {
			if (slave.clone !== 0) {
				makeSpan(c, "Clone");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function short_rival(slave, c) {
			if (slave.rivalry !== 0) {
				const block = makeBlock(c, "lightsalmon");
				const _ssj = V.slaves.findIndex(s => s.ID === slave.rivalryTarget);
				if (_ssj !== -1) {
					if (slave.rivalry <= 1) {
						block.textContent = `Disl ${SlaveFullName(V.slaves[_ssj])}`;
					} else if (slave.rivalry <= 2) {
						block.textContent = `${SlaveFullName(V.slaves[_ssj])}'s rival`;
					} else {
						block.textContent = `Hates ${SlaveFullName(V.slaves[_ssj])}`;
					}
				}
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_family(slave, c) {
			let handled = 0;
			const block = makeBlock();
			if (slave.mother > 0) {
				const _ssj = V.slaves.findIndex(s => s.ID === slave.mother);
				if (_ssj !== -1) {
					addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `);
					const tmpSpan = makeSpan(block, getPronouns(slave).daughter, "lightgreen");
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTerm(slave);
						tmpSpan.textContent += ` and ${friendShipShort}`;
						handled = 1;
					}
					tmpSpan.textContent += '.';
				}
			} else if (slave.mother === -1) {
				addText(block, `Your `);
				if (slave.relationship < -1) {
					makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen");
					handled = 1;
				} else {
					makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
				}
			} else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) {
				addText(block, `${V.missingTable[slave.mother].fullName}'s `);
				makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
			}
			if (slave.father > 0 && slave.father !== slave.mother) {
				const _ssj = V.slaves.findIndex(s => s.ID === slave.father);
				if (_ssj !== -1) {
					addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `);
					const tmpSpan = makeSpan(block, getPronouns(slave).daughter, "lightgreen");
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTerm(slave);
						tmpSpan.textContent += ` and ${friendShipShort}`;
						handled = 1;
					}
					tmpSpan.textContent += '.';
				}
			} else if (slave.father === -1 && slave.father !== slave.mother) {
				addText(block, `Your `);
				if (slave.relationship < -1) {
					makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen");
					handled = 1;
				} else {
					makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
				}
			} else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) {
				addText(block, `${V.missingTable[slave.father].fullName}'s `);
				makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
			}
			if (areSisters(V.PC, slave) > 0) {
				addText(block, `Your `);
				if (slave.relationship < -1) {
					makeSpan(block, `${relativeTerm(V.PC, slave)} and ${PCrelationshipTerm(slave)}.`, "lightgreen");
					handled = 1;
				} else {
					makeSpan(block, `${relativeTerm(V.PC, slave)}.`, "lightgreen");
				}
			}
			if (slave.daughters === 1) {
				let _ssj = V.slaves.findIndex(s => s.mother === slave.ID);
				if (_ssj !== -1) {
					addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `);
					const tmpSpan = makeSpan(block, "mother", "lightgreen");
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTerm(slave);
						tmpSpan.textContent += ` and ${friendShipShort}`;
						handled = 1;
					}
					tmpSpan.textContent += '.';
				}
				_ssj = V.slaves.findIndex(s => s.father === slave.ID);
				if (_ssj !== -1) {
					addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `);
					const tmpSpan = makeSpan(block, "father", "lightgreen");
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTerm(slave);
						tmpSpan.textContent += ` and ${friendShipShort}`;
						handled = 1;
					}
					tmpSpan.textContent += '.';
				}
			} else if (slave.daughters > 1) {
				if (slave.daughters > 10) {
					makeSpan(block, "Has tons of daughters.", "lightgreen");
				} else if (slave.daughters > 5) {
					makeSpan(block, "Has many daughters.", "lightgreen");
				} else {
					makeSpan(block, "Has several daughters.", "lightgreen");
				}
			}
			if (slave.sisters === 1) {
				const _ssj = V.slaves.findIndex(s => areSisters(s, slave) > 0);
				if (_ssj !== -1) {
					addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `);
					const tmpSpan = makeSpan(block, getPronouns(slave).sister, "lightgreen");
					if (slave.relationshipTarget === V.slaves[_ssj].ID) {
						const friendShipShort = relationshipTerm(slave);
						tmpSpan.textContent += ` and ${friendShipShort}`;
						handled = 1;
					}
					tmpSpan.textContent += '.';
				}
			} else if (slave.sisters > 1) {
				if (slave.sisters > 10) {
					makeSpan(block, "One of many sisters.", "lightgreen");
				} else if (slave.sisters > 5) {
					makeSpan(block, "Has many sisters.", "lightgreen");
				} else {
					makeSpan(block, "Has several sisters.", "lightgreen");
				}
			}
			if (slave.relationship > 0 && handled !== 1) {
				const _ssj = V.slaves.findIndex(s => s.ID === slave.relationshipTarget);
				if (_ssj !== -1) {
					const friendship = relationshipTerm(slave);
					addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `);
					makeSpan(block, `${friendship}.`, "lightgreen");
				}
			} else if (slave.relationship === -3 && slave.mother !== -1 && slave.father !== -1 && areSisters(V.PC, slave) === 0) {
				makeSpan(block, `Your ${getPronouns(slave).wife}.`, "lightgreen");
			} else if (slave.relationship === -2) {
				makeSpan(block, "Emotionally bonded to you.", "lightgreen");
			} else if (slave.relationship === -1) {
				makeSpan(block, "Emotional slut.", "lightgreen");
			}

			if (block.textContent.length > 0) {
				c.appendChild(block);
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_clone(slave, c) {
			if (slave.clone !== 0) {
				makeSpan(c, `Clone of ${slave.clone}.`, "skyblue");
			}
		}

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		function long_rival(slave, c) {
			if (slave.rivalry !== 0) {
				const block = makeBlock(c);
				const _ssj = V.slaves.findIndex(s => s.ID === slave.rivalryTarget);
				if (_ssj !== -1) {
					if (slave.rivalry <= 1) {
						makeSpan(block, "Dislikes", "lightsalmon");
						block.appendChild(document.createTextNode(` ${SlaveFullName(V.slaves[_ssj])}.`));
					} else if (slave.rivalry <= 2) {
						block.appendChild(document.createTextNode(`${SlaveFullName(V.slaves[_ssj])}'s `));
						makeSpan(block, "rival.", "lightsalmon");
					} else {
						makeSpan(block, "Hates", "lightsalmon");
						block.appendChild(document.createTextNode(` ${SlaveFullName(V.slaves[_ssj])}.`));
					}
				}
			}
		}

		return {
			long: {
				health: long_health,
				illness: long_illness,
				tired: long_tired,
				age: long_age,
				face: long_face,
				eyes: long_eyes,
				ears: long_ears,
				lips: long_lips,
				teeth: long_teeth,
				muscles: long_muscles,
				voice: long_voice,
				tits_ass: long_tits_ass,
				hips: long_hips,
				waist: long_waist,
				implants: long_implants,
				lactation: long_lactation,
				mods: long_mods,
				intelligence: long_intelligence,
				skills: long_skills,
				prestige: long_prestige,
				porn_prestige: long_porn_prestige,
				clothes: long_clothes,
				collar: long_collar,
				mask: long_mask,
				mouth: long_mouth,
				belly: long_belly,
				arms: long_arms,
				legs: long_legs,
				shoes: long_shoes,
				chastity: long_chastity,
				vaginal_acc: long_vaginal_acc,
				dick_acc: long_dick_acc,
				buttplug: long_buttplug,
				fetish: long_fetish,
				attraction: long_attraction,
				smart_fetish: long_smart_fetish,
				smart_attraction: long_smart_attraction,
				behavior_flaw: long_behavior_flaw,
				sex_flaw: long_sex_flaw,
				behavior_quirk: long_behavior_quirk,
				sex_quirk: long_sex_quirk,
				family: long_family,
				clone: long_clone,
				rival: long_rival
			},
			short: {
				health: short_health,
				illness: short_illness,
				tired: short_tired,
				age: short_age,
				face: short_face,
				eyes: short_eyes,
				ears: short_ears,
				lips: short_lips,
				teeth: short_teeth,
				muscles: short_muscles,
				voice: short_voice,
				tits_ass: short_tits_ass,
				hips: short_hips,
				waist: short_waist,
				implants: short_implants,
				lactation: short_lactation,
				mods: short_mods,
				intelligence: short_intelligence,
				skills: short_skills,
				prestige: short_prestige,
				porn_prestige: short_porn_prestige,
/*				clothes: short_clothes,
				collar: short_collar,
				belly: short_belly,
				arms: short_arms,
				legs: short_legs,
				shoes: short_shoes,
				chastity: short_chastity,
				vaginal_acc: short_vaginal_acc,
				dick_acc: short_dick_acc,
				buttplug: short_buttplug,
*/
				fetish: short_fetish,
				attraction: short_attraction,
				smart_fetish: short_smart_fetish,
				smart_attraction: short_smart_attraction,
				behavior_flaw: short_behavior_flaw,
				sex_flaw: short_sex_flaw,
				behavior_quirk: short_behavior_quirk,
				sex_quirk: short_sex_quirk,
				family: short_family,
				clone: short_clone,
				rival: short_rival
			}
		};
	}();

	return {
		helpers: helpers,
		bits: bits
	};
}();


code:
App.UI.SlaveSummaryRenderers = function() {
	const bits = App.UI.SlaveSummaryImpl.bits;
	const helpers = App.UI.SlaveSummaryImpl.helpers;
	const data = App.Data.SlaveSummary;

	const shortRenderers = {
		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 */
		devotion: function(slave, c) {
			const makeSpan = helpers.makeSpan;
			if (slave.fetish === "mindbroken") {
				makeSpan(c, "MB", "mindbroken");
			} else {
				helpers.makeRatedStyledSpan(c, data.short.mental.devotion, slave.devotion, 100, true);
				helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.short.mental.trust, [slave.trust + 100, slave.devotion + 100]),
					slave.trust, true);
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		rules: function(slave, c) {
			const makeSpan = helpers.makeSpan;
			const styles = "strong";
			switch (slave.rules.living) {
				case "luxurious":
					makeSpan(c, "LS:Lux", styles);
					break;
				case "normal":
					makeSpan(c, "LS:Nor", styles);
					break;
				default:
					makeSpan(c, "LS:Spa", styles);
					break;
			}
			if (canTalk(slave, false)) {
				switch (slave.rules.speech) {
					case "permissive":
						makeSpan(c, "SpR:P", styles);
						break;
					case "accent elimination":
						makeSpan(c, "SpR:NoAcc", styles);
						break;
					case "language lessons":
						makeSpan(c, "SpR:LL", styles);
						break;
					default:
						makeSpan(c, "SpR:R", styles);
						break;
				}
			}
			switch (slave.rules.relationship) {
				case "permissive":
					makeSpan(c, "ReR:P", styles);
					break;
				case "just friends":
					makeSpan(c, "ReR:Fr", styles);
					break;
				default:
					makeSpan(c, "ReR:R", styles);
					break;
			}
			switch (slave.rules.punishment) {
				case "confinement":
					makeSpan(c, "Pun:Conf", styles);
					break;
				case "whipping":
					makeSpan(c, "Pun:Whip", styles);
					break;
				case "chastity":
					makeSpan(c, "Pun:Chas", styles);
					break;
				default:
					makeSpan(c, "Pun:Situ", styles);
					break;
			}
			switch (slave.rules.reward) {
				case "relaxation":
					makeSpan(c, "Rew:Relx", styles);
					break;
				case "drugs":
					makeSpan(c, "Rew:Drug", styles);
					break;
				case "orgasm":
					makeSpan(c, "Rew:Orga", styles);
					break;
				default:
					makeSpan(c, "Rew:Situ", styles);
					break;
			}
			makeSpan(c, "MaR:" + App.Utils.releaseSummaryShort(slave), styles);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		health: function(slave, c) {
			const b = bits.short;
			b.health(slave, c);
			b.illness(slave, c);
			b.tired(slave, c);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		weight: function(slave, c) {
			helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.short.body.weight,
				[slave.weight + 100, helpers.FSData.policy.HedonisticDecadence.active, slave.hips + 2]), slave.weight, true);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		genitalia: function(slave, c) {
			const makeSpan = helpers.makeSpan;
			if (slave.dick > 0) {
				let dickDesc = slave.balls === 0 ? "Geld" : "";
				const dickBallsDesc = helpers.getMultiNumericRating(data.short.body.genitalia.dickBalls, [slave.dick, slave.balls]);
				if (dickBallsDesc) {
					dickDesc += ` ${dickBallsDesc}`;
				}
				if (dickDesc) {
					helpers.makeSpan(c, dickDesc, "pink");
				}
			}
			if (slave.vagina === 0) {
				makeSpan(c, "VV", "lime");
			} else if ((slave.pregKnown === 1) && canWalk(slave) && (slave.clothes === "no clothing" || slave.clothes === "body oil") && (slave.shoes === "none")) {
				makeSpan(c, "NBP", "pink");
			}
			if (slave.anus === 0) {
				makeSpan(c, "AV", "lime");
			}
			const holesDesc = helpers.getMultiNumericRating(data.long.body.genitalia.holes, [slave.vagina, slave.anus]);
			if (holesDesc) {
				makeSpan(c, holesDesc, "pink");
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		diet: function(slave, c) {
			const style = ["teal", "strong"];
			helpers.makeMappedSpan(c, data.short.diet, slave.diet, style);
			helpers.makeMappedSpan(c, data.short.specialDiet, slave.dietCum + 3 * slave.dietMilk, style);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		drugs: function(slave, c) {
			let drugDesc = data.short.drugs[slave.drugs];
			const makeSpan = helpers.makeSpan;
			if (drugDesc) {
				makeSpan(c, "Dr:" + drugDesc, ["tan", "strong"]);
			}
			if (slave.curatives === 2) {
				makeSpan(c, "Cura", ["lightgreen", "strong"]);
			} else if (slave.curatives === 1) {
				makeSpan(c, "Prev", ["lightgreen", "strong"]);
			}
			if (slave.aphrodisiacs !== 0) {
				if (slave.aphrodisiacs === 1) {
					makeSpan(c, "Aph", ["lightblue", "strong"]);
				} else if (slave.aphrodisiacs === 2) {
					makeSpan(c, "Aph++", ["lightblue", "strong"]);
				} else {
					makeSpan(c, "Anaph", ["lightblue", "strong"]);
				}
			}
			if (slave.addict !== 0) {
				makeSpan(c, "Add", "cyan");
			}
			let styles = ["lightsalmon", "strong"];
			if (slave.hormones > 1) {
				makeSpan(c, "Ho:F+", styles);
			} else if (slave.hormones > 0) {
				makeSpan(c, "Ho:F", styles);
			} else if (slave.hormones < -1) {
				makeSpan(c, "Ho:M+", styles);
			} else if (slave.hormones < 0) {
				makeSpan(c, "Ho:M", styles);
			}

			styles = ["mediumseagreen", "strong"];
			if ((slave.bellyImplant > -1)) {
				makeSpan(c, "Belly Imp", styles);
			} else if (((slave.preg <= -2) || (slave.ovaries === 0)) && (slave.vagina !== -1)) {
				makeSpan(c, "Barr", styles);
			} else if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) {
				makeSpan(c, "Prepub", styles);
			} else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) {
				makeSpan(c, "Meno", styles);
			} else if (slave.pregWeek < 0) {
				makeSpan(c, "Postpartum", styles);
			} else if (slave.preg === -1) {
				makeSpan(c, "CC", styles);
			} else if (slave.preg === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) {
				makeSpan(c, "Fert+", styles);
			} else if (((slave.preg < slave.pregData.normalBirth / 10) && (slave.preg > 0) && slave.pregKnown === 0) || slave.pregWeek === 1) {
				makeSpan(c, "Preg?", styles);
			} else if ((slave.preg >= 36) && (slave.broodmother > 0)) {
				makeSpan(c, "Perm preg", styles);
			} else if (slave.pregKnown === 1) {
				makeSpan(c, `${slave.pregWeek} wks preg`, styles);
			}
			if (slave.induce === 1) {
				makeSpan(c, "Early Labor", ["orange", "strong"]);
			}
			if (slave.pubertyXY === 0 && slave.balls > 0) {
				makeSpan(c, "Prepub balls", "strong");
			}
			if (slave.balls > 0 && slave.vasectomy === 1) {
				makeSpan(c, "Vasect", "strong");
			}
			styles = ["springgreen", "strong"];
			if (slave.inflation === 3) {
				makeSpan(c, `8 ltr ${slave.inflationType}`, styles);
			} else if (slave.inflation === 2) {
				makeSpan(c, `4 ltr ${slave.inflationType}`, styles);
			} else if (slave.inflation === 1) {
				makeSpan(c, `2 ltr ${slave.inflationType}`, styles);
			} else if (slave.bellyFluid > 0) {
				makeSpan(c, `${slave.bellyFluid}ccs ${slave.inflationType}`, styles);
			}
		},
		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		physicals: function(slave, c) {
			const b = bits.short;
			b.age(slave, c);
			b.face(slave, c);
			b.eyes(slave, c);
			b.ears(slave, c);
			if (slave.markings !== "none") {
				helpers.makeSpan(c, "Markings");
			}
			b.lips(slave, c);
			b.teeth(slave, c);
			b.muscles(slave, c);
			helpers.addText(c, App.Desc.shortLimbs(slave));
			b.voice(slave, c);
			b.tits_ass(slave, c);
			b.hips(slave, c);
			b.waist(slave, c);
			b.implants(slave, c);
			b.lactation(slave, c);
			b.mods(slave, c);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		hormoneBalance: function(slave, c) {
			if (slave.hormoneBalance <= -21) {
				helpers.makeSpan(c, "HB:M", ["deepskyblue", "strong"]);
			} else if (slave.hormoneBalance <= 20) {
				helpers.makeSpan(c, "HB:N", ["pink", "strong"]);
			} else if (slave.hormoneBalance <= 500) {
				helpers.makeSpan(c, "HB:F", ["pink", "strong"]);
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		mental: function(slave, c) {
			const b = bits.short;
			if (slave.fetish !== "mindbroken") {
				if (slave.fetishKnown === 1) {
					b.fetish(slave, c);
				}
				if (slave.attrKnown === 1) {
					b.attraction(slave, c);
				}
			}
			if (slave.clitPiercing === 3) {
				b.smart_fetish(slave, c);
				b.smart_attraction(slave, c);
			}
			b.behavior_flaw(slave, c);
			b.sex_flaw(slave, c);
			b.behavior_quirk(slave, c);
			b.sex_quirk(slave, c);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		skills: function(slave, c) {
			const b = bits.short;
			b.intelligence(slave, c);
			b.skills(slave, c);
			b.prestige(slave, c);
			b.porn_prestige(slave, c);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		race: function(slave, c) {
			const s = data.short.race[slave.race];
			helpers.makeSpan(c, s ? s : helpers.firstThreeUc(slave.race), "tan");
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		nationality: function(slave, c) {
			let tmp = data.short.nationality[slave.nationality];
			if (!tmp && slave.nationality === "Zimbabwean") {
				if (slave.race === "white") {
					tmp = 'Rhodesian.';
				} else {
					tmp = `${slave.nationality}.`;
				}
			}
			helpers.makeSpan(c, tmp ? tmp : slave.nationality.substr(0, 3), "tan");
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		skin: function(slave, c) {
			const s = data.short.skin[slave.skin];
			helpers.makeSpan(c, s ? s : helpers.firstThreeUc(slave.skin));
		},

		clothes: function() { },

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		rulesets: function(slave, c) {
			if (slave.useRulesAssistant === 0) {
				helpers.makeSpan(c, "RA-Exmt", "lightgreen");
			}
		},

		origins: function() { },

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		relations: function(slave, c) {
			const b = bits.short;
			b.family(slave, c);
			b.clone(slave, c);
			b.rival(slave, c);
		}

	};

	const longRenderers = {
		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		devotion: function(slave, c) {
			const makeSpan = helpers.makeSpan;
			if (slave.fetish === "mindbroken") {
				makeSpan(c, "Mindbroken.", "mindbroken");
			} else {
				helpers.makeRatedStyledSpan(c, data.long.mental.devotion, slave.devotion, 100, true);
				helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.long.mental.trust, [slave.trust + 100, slave.devotion + 100]),
					slave.trust, true);
			}
		},
		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		health: function(slave, c) {
			const b = bits.long;
			b.health(slave, c);
			b.illness(slave, c);
			b.tired(slave, c);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		weight: function(slave, c) {
			helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.long.body.weight,
				[slave.weight + 100, helpers.FSData.policy.HedonisticDecadence.active, slave.hips + 2]), slave.weight, true);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		rules: function(slave, c) {
			const addText = helpers.addText;
			addText(c, `Living standard: ${slave.rules.living}. `);
			if (canTalk(slave, false)) {
				addText(c, `Speech rules: ${slave.rules.speech}. `);
			}
			addText(c, `Relationship rules: ${slave.rules.relationship}. `);
			addText(c, `Typical punishment: ${slave.rules.punishment}. `);
			addText(c, `Typical reward: ${slave.rules.reward}. `);
			addText(c, `Release rules: ${App.Utils.releaseSummaryLong(slave)}. `);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		genitalia: function(slave, c) {
			if (slave.dick > 0) {
				let dickDesc = slave.balls === 0 ? "Gelded." : "";
				const dickBallsDesc = helpers.getMultiNumericRating(data.long.body.genitalia.dickBalls, [slave.dick, slave.balls]);
				if (dickBallsDesc) {
					dickDesc += ` ${dickBallsDesc}`;
				}
				if (dickDesc) {
					helpers.makeSpan(c, dickDesc, "pink");
				}
			}
			if (slave.vagina === 0) {
				helpers.makeSpan(c, "Virgin.", "lime");
			} else if ((slave.pregKnown === 1) && canWalk(slave) && (slave.clothes === "no clothing" || slave.clothes === "body oil") && (slave.shoes === "none")) {
				helpers.makeSpan(c, "Naked, barefoot, and pregnant.", "pink");
			}
			if (slave.anus === 0) {
				helpers.makeSpan(c, "Anal virgin.", "lime");
			}
			const holesDesc = helpers.getMultiNumericRating(data.long.body.genitalia.holes, [slave.vagina, slave.anus]);
			if (holesDesc) {
				helpers.makeSpan(c, holesDesc, "pink");
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		diet: function(slave, c) {
			helpers.makeMappedSpan(c, data.long.diet, slave.diet, "teal");
			const sd = data.long.specialDiet[slave.dietCum + 3 * slave.dietMilk];
			if (sd) {
				helpers.addText(c, "Diet base: ");
				helpers.makeSpan(c, sd, "cyan");
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		drugs: function(slave, c) {
			const makeSpan = helpers.makeSpan;
			let swd = WombGetLittersData(slave);
			if ((slave.drugs !== "no drugs") && (slave.drugs !== "none")) {
				makeSpan(c, `On ${slave.drugs}.`, "tan");
			}
			if (slave.curatives === 2) {
				makeSpan(c, "On curatives.", "lightgreen");
			} else if (slave.curatives === 1) {
				makeSpan(c, "On preventatives.", "lightgreen");
			}
			if (slave.aphrodisiacs > 0) {
				makeSpan(c, `On ${slave.aphrodisiacs > 1 ? 'extreme' : ''} aphrodisiacs.`, "lightblue");
			} else if (slave.aphrodisiacs === -1) {
				makeSpan(c, "On anaphrodisiacs.", "lightblue");
			}
			if (slave.addict !== 0) {
				makeSpan(c, "Addict.", "cyan");
			}
			if (slave.hormones > 1) {
				makeSpan(c, "Heavy female hormones.", "lightsalmon");
			} else if (slave.hormones > 0) {
				makeSpan(c, "Female hormones.", "lightsalmon");
			} else if (slave.hormones < -1) {
				makeSpan(c, "Heavy male hormones.", "lightsalmon");
			} else if (slave.hormones < 0) {
				makeSpan(c, "Male hormones.", "lightsalmon");
			}
			let styles = "mediumseagreen";
			if ((slave.bellyImplant > -1)) {
				makeSpan(c, "Belly Implant.", styles);
			} else if ((slave.preg <= -2) && (slave.ovaries === 1 || slave.mpreg === 1)) {
				makeSpan(c, "Barren.", styles);
			} else if ((slave.ovaries === 0) && (slave.vagina !== -1) && (slave.genes === "XX")) {
				makeSpan(c, "Barren.", styles);
			} else if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) {
				makeSpan(c, "Not ovulating yet.", styles);
			} else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) {
				makeSpan(c, "Menopausal.", styles);
			} else if (slave.pregWeek < 0) {
				makeSpan(c, "Postpartum.", styles);
			} else if (slave.preg === -1) {
				makeSpan(c, "On contraceptives.", styles);
			} else if (slave.preg === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) {
				makeSpan(c, "Fertile.", styles);
			} else if ((slave.preg >= 36) && (slave.broodmother > 0)) {
				makeSpan(c, "Permanently pregnant.", styles);
			} else if (swd.litters.length > 1) {
				let pregTxt = `Concurrent pregnancies: (${swd.litters.length} sets).`;
				pregTxt += ` Max:${swd.litters[0]} / Min:${swd.litters[swd.litters.length - 1]} week(s).`;
				makeSpan(c, pregTxt, "lime");
			} else if (((slave.preg < slave.pregData.normalBirth / 10) && (slave.preg > 0) && slave.pregKnown === 0) || slave.pregWeek === 1) {
				makeSpan(c, "May be pregnant.");
			} else if (slave.pregKnown === 1) {
				if (slave.pregType < 2 || slave.broodmother > 0) {
					makeSpan(c, `${slave.pregWeek} weeks pregnant.`);
				} else {
					let desc = `${slave.pregWeek} weeks pregnant with `;
					if (slave.pregType >= 40) {
						desc += `a tremendous brood of offspring.`;
					} else if (slave.pregType >= 20) {
						desc += `a brood of offspring.`;
					} else if (slave.pregType >= 10) {
						desc += `${slave.pregType} babies.`;
					} else if (slave.pregType === 9) {
						desc += `nonuplets.`;
					} else if (slave.pregType === 8) {
						desc += `octuplets.`;
					} else if (slave.pregType === 7) {
						desc += `septuplets.`;
					} else if (slave.pregType === 6) {
						desc += `sextuplets.`;
					} else if (slave.pregType === 5) {
						desc += `quintuplets.`;
					} else if (slave.pregType === 4) {
						desc += `quadruplets.`;
					} else if (slave.pregType === 3) {
						desc += `triplets.`;
					} else {
						desc += `twins.`;
					}
					makeSpan(c, desc);
				}
				if (slave.preg > slave.pregData.normalBirth && slave.broodmother === 0) {
					makeSpan(c, "(Overdue.)");
				}
			}
			if (slave.induce === 1) {
				makeSpan(c, "Showing signs of early labor.", "orange");
			}
			if (slave.pubertyXY === 0 && slave.balls > 0) {
				makeSpan(c, "Has not had first ejaculation.");
			}
			if (slave.balls > 0 && slave.vasectomy === 1) {
				makeSpan(c, "Vasectomy.");
			}
			if (slave.inflation === 3) {
				makeSpan(c, `Filled with 8 liters of ${slave.inflationType}.`, "springgreen");
			} else if (slave.inflation === 2) {
				makeSpan(c, `Filled with 4 liters of ${slave.inflationType}.`, "springgreen");
			} else if (slave.inflation === 1) {
				makeSpan(c, `Filled with 2 liters of ${slave.inflationType}.`, "springgreen");
			} else if (slave.bellyFluid > 0) {
				makeSpan(c, `Stuffed with ${slave.bellyFluid}ccs of ${slave.inflationType}.`, "springgreen");
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		physicals: function(slave, c) {
			const b = bits.long;
			b.age(slave, c);
			b.face(slave, c);
			b.eyes(slave, c);
			b.ears(slave, c);
			b.lips(slave, c);
			b.teeth(slave, c);
			b.muscles(slave, c);
			helpers.makeSpan(c, App.Desc.longLimbs(slave));
			b.voice(slave, c);
			b.tits_ass(slave, c);
			b.hips(slave, c);
			b.waist(slave, c);
			b.implants(slave, c);
			b.lactation(slave, c);
			b.mods(slave, c);
			if (!jQuery.isEmptyObject(slave.brand)) {
				helpers.makeSpan(c, 'Branded.');
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		hormoneBalance: function(slave, c) {
			const colorClass = slave.hormoneBalance <= -21 ? "deepskyblue" : "pink";
			const desc = helpers.getNumericRating(data.long.hormoneBalance, slave.hormoneBalance + 500);
			helpers.makeSpan(c, desc + " hormone balance.", colorClass);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		skills: function(slave, c) {
			const b = bits.long;
			b.intelligence(slave, c);
			b.skills(slave, c);
			b.prestige(slave, c);
			b.porn_prestige(slave, c);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		clothes: function(slave, c) {
			const b = bits.long;
			const dressingBlock = helpers.makeBlock(c);
			if (slave.choosesOwnClothes === 1) {
				helpers.makeSpan(dressingBlock, `Dressing ${getPronouns(slave).himself}.`);
			}
			b.clothes(slave, dressingBlock);
			b.collar(slave, dressingBlock);
			b.mask(slave, dressingBlock);
			b.mouth(slave, dressingBlock);
			b.belly(slave, dressingBlock);
			if (hasAnyArms(slave)) {
				b.arms(slave, dressingBlock);
			}
			if (hasAnyLegs(slave)) {
				b.legs(slave, dressingBlock);
				b.shoes(slave, dressingBlock);
			}
			b.chastity(slave, dressingBlock);
			b.vaginal_acc(slave, dressingBlock);
			b.dick_acc(slave, dressingBlock);
			b.buttplug(slave, dressingBlock);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		mental: function(slave, c) {
			const b = bits.long;
			if (slave.fetish !== "mindbroken") {
				if (slave.fetishKnown === 1) {
					b.fetish(slave, c);
				}
				if (slave.attrKnown === 1) {
					b.attraction(slave, c);
				}
			}
			if (slave.clitPiercing === 3) {
				b.smart_fetish(slave, c);
				b.smart_attraction(slave, c);
			}
			b.behavior_flaw(slave, c);
			b.sex_flaw(slave, c);
			b.behavior_quirk(slave, c);
			b.sex_quirk(slave, c);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		race: function(slave, c) {
			let raceStr = App.Data.SlaveSummary.long.race[slave.race];
			if (!raceStr) {
				raceStr = capFirstChar(slave.race);
			}
			helpers.makeSpan(c, raceStr + '.', "tan");
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		nationality: function(slave, c) {
			function descStr(slave) {
				switch (slave.nationality) {
					case "a Cook Islander":
						return `Cook Islander.`;
					case "a Liechtensteiner":
						return `Liechtensteiner.`;
					case "a New Zealander":
						return `New Zealander.`;
					case "a Solomon Islander":
						return `Solomon Islander.`;
					case "Zimbabwean":
						if (slave.race === "white") {
							return `Rhodesian.`;
						} else {
							return `${slave.nationality}.`;
						}
					case "slave":
					case "none":
					case "":
					case "Stateless":
						return `Stateless.`;
					default:
						return `${slave.nationality}.`;
				}
			}

			helpers.makeSpan(c, descStr(slave), "tan");
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		skin: function(slave, c) {
			helpers.makeSpan(c, `${capFirstChar(slave.skin)} skin.`);
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		rulesets: function(slave, c) {
			if (slave.useRulesAssistant === 0) {
				helpers.makeSpan(c, "RA-Exempt", "lightgreen");
			} else if ((slave.currentRules !== undefined) && (slave.currentRules.length > 0)) {
				c.innerHTML = `Rules: ${V.defaultRules.filter(x => ruleApplied(slave, x)).map(x => x.name).join(", ")}`;
			}
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		origins: function(slave, c) {
			const para = helpers.makeParagraph(c);
			para.classList.add("gray");
			const pronouns = getPronouns(slave);
			para.textContent = `${slave.origin.replace(/\$([A-Z]?[a-z]+)/g, (match, cap1) => pronouns[cap1] || match)}`;
		},

		/**
		 * @param {App.Entity.SlaveState} slave
		 * @param {Node} c
		 * @returns {void}
		 */
		relations: function(slave, c) {
			const b = bits.long;
			b.family(slave, c);
			b.clone(slave, c);
			b.rival(slave, c);
		}
	};

	return {
		short: shortRenderers,
		long: longRenderers,
		empty: function(){}
	};
}();

App.UI.SlaveSummary = function() {
	const emptyRenderer = (/** @type {App.Entity.SlaveState} */ slave, /** @type {Node} */ c) => { };
	const delegates = {
		clothes: emptyRenderer,
		devotion: emptyRenderer,
		rules: emptyRenderer,
		height: emptyRenderer,
		diet: emptyRenderer,
		health: emptyRenderer,
		drugs: emptyRenderer,
		race: emptyRenderer,
		nationality: emptyRenderer,
		genitalia: emptyRenderer,
		physicals: emptyRenderer,
		hormoneBalance: emptyRenderer,
		skills: emptyRenderer,
		mental: emptyRenderer,
		weight: emptyRenderer,
		skin: emptyRenderer,
		origins: emptyRenderer,
		rulesets: emptyRenderer,
		relations: emptyRenderer
	};

	/**
	 * @returns {App.UI.SlaveSummary.State}
	 */
	function makeNewState() {
		return {
			abbreviation: {
				clothes: 2,
				devotion: 2,
				diet: 2,
				drugs: 2,
				genitalia: 2,
				health: 2,
				hormoneBalance: 2,
				mental: 2,
				nationality: 2,
				origins: 2,
				physicals: 2,
				race: 2,
				rules: 2,
				rulesets: 2,
				skills: 2,
			}
		};
	}

	function delegateForSetting(name, setting) {
		switch (setting) {
			case 0: return App.UI.SlaveSummaryRenderers.empty;
			case 1: return App.UI.SlaveSummaryRenderers.short[name];
			case 2: return App.UI.SlaveSummaryRenderers.long[name];
		}
	}

	function initDelegates(settingsObj) {
		try {
			settingsObj = settingsObj || V.UI.slaveSummary;
			/** @type {App.UI.SlaveSummary.AbbreviationState} */
			const abbrSettings = settingsObj.abbreviation;
			for (const setting in abbrSettings) {
				delegates[setting] = delegateForSetting(setting, abbrSettings[setting]);
			}
			delegates.weight = delegateForSetting("weight", abbrSettings.diet);
			delegates.skin = delegateForSetting("skin", abbrSettings.physicals);
			delegates.relations = delegateForSetting("relations", abbrSettings.mental);
			// special settings
			if (!V.seeRace) {
				delegates.race = emptyRenderer;
			}
		} catch (ex){
			console.error(ex);
		}
	}

	function settingsChanged(newState) {
		try {
			const newStateIsOK = newState && newState.hasOwnProperty("UI") && newState.UI.hasOwnProperty("slaveSummary");
			const settingsObj = newStateIsOK ? newState.UI.slaveSummary : V.UI.slaveSummary;

			initDelegates(settingsObj);
			App.UI.SlaveSummaryImpl.helpers.syncFSData(newState ? newState.arcologies[0] : V.arcologies[0]);
		} catch (ex) {
			console.log(`Slave summary settings change handler encountered an error: ${ex}`);
		}
	}

	function societyChanged(arcology) {
		App.UI.SlaveSummaryImpl.helpers.syncFSData(arcology);
	}

	/**
	 * @param {App.Entity.SlaveState} slave
	 * @returns {DocumentFragment}
	 */
	function render(slave) {
		/** @type {App.UI.SlaveSummary.AbbreviationState} */
		const abbrSettings = V.UI.slaveSummary.abbreviation;
		const helpers = App.UI.SlaveSummaryImpl.helpers;

		const res = document.createDocumentFragment();
		let para = helpers.makeParagraph(res);

		delegates.devotion(slave, para);
		if (!slave.fuckdoll) {
			delegates.rules(slave, para);
		}
		delegates.weight(slave, para);
		delegates.diet(slave, para);
		delegates.health(slave, para);
		delegates.drugs(slave, para);

		para = helpers.makeParagraph(res);
		helpers.makeSpan(para, `${capFirstChar(SlaveTitle(slave))}${abbrSettings.physicals === 2 ? '.' : ''}`, ["coral", "strong"]);
		delegates.race(slave, para);
		delegates.nationality(slave, para);
		delegates.skin(slave, para);
		delegates.genitalia(slave, para);
		delegates.physicals(slave, para);
		delegates.hormoneBalance(slave, para);

		para = helpers.makeParagraph(res);
		delegates.skills(slave, para);
		delegates.mental(slave, para);
		if (slave.custom.label) {
			helpers.makeSpan(res, `${capFirstChar(slave.custom.label)}.`, ["yellow", "strong"]);
		}
		if ((slave.relationship !== 0) || (slave.relationship !== 0) || (abbrSettings.clothes === 2) || (abbrSettings.rulesets === 2)) {
			para = helpers.makeParagraph(res);
		}
		delegates.relations(slave, para);
		if (!slave.fuckdoll) {
			delegates.clothes(slave, para);
		}
		const RABlock = helpers.makeBlock(para);
		delegates.rulesets(slave, RABlock);
		if (slave.origin !== 0) {
			delegates.origins(slave, res);
		}
		return res;
	}

	/**
	 * @param {App.UI.OptionsGroup} optionsGroup
	 */
	function addOptions(optionsGroup) {
		// SAH is "Summarized, Abbreviated, Hidden"
		const SAHOptions = {
			"Summarized": 2,
			"Abbreviated": 1,
			"Hidden": 0
		};

		function appendOption(name, desc, options) {
			const option = optionsGroup.addOption(desc, name, V.UI.slaveSummary.abbreviation);
			for (const key in options) {
				option.addValue(key, options[key], initDelegates);
			}
		}

		function appendSAHOption(name, desc) {
			appendOption(name, desc, SAHOptions);
		}

		appendSAHOption("devotion", "Mental stats are");
		appendSAHOption("mental", "Mental attributes are");
		appendSAHOption("rules", "Rules are");
		appendSAHOption("health", "Health is");
		appendSAHOption("diet", "Diet and weight are");
		appendSAHOption("drugs", "Drugs and addiction are");
		appendSAHOption("hormoneBalance", "Hormone balance is");
		appendSAHOption("genitalia", "Genitalia are");
		appendSAHOption("physicals", "Physical traits are");
		appendSAHOption("skills", "Skills are");
		appendSAHOption("nationality", "Nationality is");
		appendSAHOption("race", "Race is");
		appendOption("rulesets", "Rules Assistant rulesets are", {
			"Summarized": 2,
			"Abbreviated": 1
		});
		appendOption("clothes", "Clothes are", {
			"Summarized": 2,
			"Hidden": 0
		});
		appendOption("origins", "Origins are", {
			"Summarized": 2,
			"Hidden": 0
		});
	}

	return {
		makeNewState: makeNewState,
		settingsChanged: settingsChanged,
		societyChanged: societyChanged,
		render: render,
		addOptions: addOptions
	};
}();

repo_name: KnightBoulegard/fc

license: mit


code:
/*
* <<span>> macro
* A minimal macro which allows to create <span> elements with dynamic IDs.
*
* Usage: <<span $variable>>...<</span>>
*/
Macro.add('span', {
	skipArgs: true,
	tags: null,

	handler() {
		const payload = this.payload[0].contents.replace(/(^\n+|\n+$)/, '');
		let statement = this.args.raw.trim();
		let result;

		if (statement.length === 0) {
			return this.error('invalid syntax, format: <<span id>>');
		}

		try {
			result = Scripting.evalTwineScript(statement);
		} catch (ex) {
			return this.error(`bad evaluation: ${typeof ex === 'object' ? ex.message : ex}`);
		}

		Config.debug && this.debugView.modes({block: true});

		jQuery(`<span id='${String(result)}' />`)
			.wiki(payload)
			.appendTo(this.output);
	}
});


code:
/**
 * True if slave has at least one eye
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyEyes = function(slave) {
	return !!slave.eye.right || !!slave.eye.left;
};

/**
 * True if slave has at least one eye that is natural
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalEyes = function(slave) {
	return getLeftEyeType(slave) === 1 || getRightEyeType(slave) === 1;
};

/**
 * True if slave has at least one eye that is prosthetic (cyber or glass)
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticEyes = function(slave) {
	return getLeftEyeType(slave) > 1 || getRightEyeType(slave) > 1;
};

/**
 * True if slave has at least one eye that is cybernetic
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyCyberneticEyes = function(slave) {
	return getLeftEyeType(slave) === 3 || getRightEyeType(slave) === 3;
};

/**
 * True if slave has both eyes
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothEyes = function(slave) {
	return !!slave.eye.right && !!slave.eye.left;
};

/**
 * True if slave has both eyes and they are natural
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothNaturalEyes = function(slave) {
	return getLeftEyeType(slave) === 1 && getRightEyeType(slave) === 1;
};

/**
 * True if slave has both eyes and they are prosthetic (cyber or glass)
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothProstheticEyes = function(slave) {
	return getLeftEyeType(slave) > 1 && getRightEyeType(slave) > 1;
};

/**
 * True if slave has both eyes and they are cybernetic
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothCyberneticEyes = function(slave) {
	return getLeftEyeType(slave) === 3 && getRightEyeType(slave) === 3;
};

/**
 * True if slave has left eye
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasLeftEye = function(slave) {
	return !!slave.eye.left;
};
/**
 * True if slave has right eye
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasRightEye = function(slave) {
	return !!slave.eye.right;
};

/**
 * Returns type of the left eye.
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getLeftEyeType = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.type;
	} else {
		return 0;
	}
};

/**
 * Returns type of the right eye.
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getRightEyeType = function(slave) {
	if (hasRightEye(slave)) {
		return slave.eye.right.type;
	} else {
		return 0;
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getLeftEyeVision = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.vision;
	} else {
		return 0;
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getRightEyeVision = function(slave) {
	if (hasRightEye(slave)) {
		return slave.eye.right.vision;
	} else {
		return 0;
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getBestVision = function(slave) {
	return Math.max(getRightEyeVision(slave), getLeftEyeVision(slave));
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getWorstVision = function(slave) {
	return Math.min(getRightEyeVision(slave), getLeftEyeVision(slave));
};

/**
 *
 * @param {App.Entity.SlaveState} slave
 * @param {number} vision
 * @returns {boolean}
 */
globalThis.anyVisionEquals = function(slave, vision) {
	return getRightEyeVision(slave) === vision || getLeftEyeVision(slave) === vision;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
globalThis.getLeftEyeColor = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.iris;
	} else {
		return "empty";
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
globalThis.getRightEyeColor = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.right.iris;
	} else {
		return "empty";
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
globalThis.getLeftEyePupil = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.pupil;
	} else {
		return "circular";
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
globalThis.getRightEyePupil = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.right.pupil;
	} else {
		return "circular";
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasVisibleHeterochromia = function(slave) {
	return hasAnyEyes(slave) && getLeftEyeColor(slave) !== getRightEyeColor(slave);
};

/**
 * Gives the genetic color of the specified eye.
 *
 * @param {App.Entity.SlaveState} slave
 * @param {string} side
 * @returns {string}
 */
globalThis.getGeneticEyeColor = function(slave, side) {
	if (side !== "left" && side !== "right") { return "ERROR:" + side; }

	if (slave.geneticQuirks.albinism === 2) {
		return slave.albinismOverride.eyeColor;
	} else {
		if (side === "left" && typeof slave.geneticQuirks.heterochromia === "string") {
			return slave.geneticQuirks.heterochromia;
		} else {
			return slave.eye.origColor;
		}
	}
};

/**
 * Counts the number of eyes that are not the genetic color
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getLenseCount = function(slave) {
	let count = 0;

	if (hasRightEye(slave) && getRightEyeColor(slave) !== getGeneticEyeColor(slave, "right")) {
		count++;
	}

	if (hasLeftEye(slave) && getLeftEyeColor(slave) !== getGeneticEyeColor(slave, "left")) {
		count++;
	}

	return count;
};


code:
/* limb checker */

/**
 * True if slave has no limbs, neither natural nor prosthetic
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.isAmputee = function(slave) {
	return !(slave.leg.right || slave.leg.left || slave.arm.right || slave.arm.left);
};

/**
 * True if slave has at least one natural limb
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalLimbs = function(slave) {
	return getLeftArmID(slave) === 1 || getRightArmID(slave) === 1 || getLeftLegID(slave) === 1 || getRightLegID(slave) === 1;
};

/**
 * True if slave has at least one prosthetic limb
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticLimbs = function(slave) {
	return getLeftArmID(slave) > 1 || getRightArmID(slave) > 1 || getLeftLegID(slave) > 1 || getRightLegID(slave) > 1;
};

/**
 * True if slave has at least one leg
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyLegs = function(slave) {
	return !!slave.leg.right || !!slave.leg.left;
};

/**
 * True if slave has at least one arm
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyArms = function(slave) {
	return !!slave.arm.right || !!slave.arm.left;
};

/**
 * True if slave has at least one leg that is natural
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalLegs = function(slave) {
	return getLeftLegID(slave) === 1 || getRightLegID(slave) === 1;
};

/**
 * True if slave has at least one arm that is natural
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalArms = function(slave) {
	return getLeftArmID(slave) === 1 || getRightArmID(slave) === 1;
};

/**
 * True if slave has at least one leg that is prosthetic
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticLegs = function(slave) {
	return getLeftLegID(slave) > 1 || getRightLegID(slave) > 1;
};

/**
 * True if slave has at least one arm that is prosthetic
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticArms = function(slave) {
	return getLeftArmID(slave) > 1 || getRightArmID(slave) > 1;
};

/**
 * True if slave has both legs
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothLegs = function(slave) {
	return !!slave.leg.right && !!slave.leg.left;
};

/**
 * True if slave has both arms
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothArms = function(slave) {
	return !!slave.arm.right && !!slave.arm.left;
};

/**
 * True if slave has both legs and they are natural
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothNaturalLegs = function(slave) {
	return getLeftLegID(slave) === 1 && getRightLegID(slave) === 1;
};

/**
 * True if slave has both arms and they are natural
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothNaturalArms = function(slave) {
	return getLeftArmID(slave) === 1 && getRightArmID(slave) === 1;
};

/**
 * True if slave has both arms and they are artificial
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothProstheticArms = function(slave) {
	return getLeftArmID(slave) > 1 && getRightArmID(slave) > 1;
};

/**
 * True if slave has both legs and they are artificial
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasBothProstheticLegs = function(slave) {
	return getLeftLegID(slave) > 1 && getRightLegID(slave) > 1;
};

/**
 * True if slave has any limbs
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAnyLimbs = function(slave) {
	return hasAnyArms(slave) || hasAnyLegs(slave);
};

/**
 * True if slave has all limbs
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAllLimbs = function(slave) {
	return hasBothLegs(slave) && hasBothArms(slave);
};

/**
 * True if slave has all limbs and all are natural
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasAllNaturalLimbs = function(slave) {
	return hasBothNaturalLegs(slave) && hasBothNaturalArms(slave);
};

/**
 * True if slave has left arm
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasLeftArm = function(slave) {
	return !!slave.arm.left;
};

/**
 * True if slave has right arm
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasRightArm = function(slave) {
	return !!slave.arm.right;
};

/**
 * True if slave has left leg
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasLeftLeg = function(slave) {
	return !!slave.leg.left;
};

/**
 * True if slave has right leg
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
globalThis.hasRightLeg = function(slave) {
	return !!slave.leg.right;
};

/**
 * Returns limb ID of the left arm. Uses new IDs.
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getLeftArmID = function(slave) {
	if (hasLeftArm(slave)) {
		return slave.arm.left.type;
	} else {
		return 0;
	}
};

/**
 * Returns limb ID of the right arm. Uses new IDs.
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getRightArmID = function(slave) {
	if (hasRightArm(slave)) {
		return slave.arm.right.type;
	} else {
		return 0;
	}
};

/**
 * Returns limb ID of the left leg. Uses new IDs.
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getLeftLegID = function(slave) {
	if (hasLeftLeg(slave)) {
		return slave.leg.left.type;
	} else {
		return 0;
	}
};

/**
 * Returns limb ID of the right leg. Uses new IDs.
 *
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.getRightLegID = function(slave) {
	if (hasRightLeg(slave)) {
		return slave.leg.right.type;
	} else {
		return 0;
	}
};

/**
 * Returns a very short description of the specified limb ID. Uses new limb IDs.
 *
 * @param {number} id
 * @returns {string}
 */
globalThis.idToDescription = function(id) {
	switch (id) {
		case 0:
			return "amputated";
		case 1:
			return "healthy";
		case 2:
			return "modern prosthetic";
		case 3:
			return "advanced, sex-focused prosthetic";
		case 4:
			return "advanced, beauty-focused prosthetic";
		case 5:
			return "advanced, combat-focused prosthetic";
		case 6:
			return "highly advanced cybernetic";
		default:
			return "unknown id: " + id;
	}
};

/**
 * Returns count of specified limb type. Uses new limb IDs:
 * 0: no limb
 * 1: natural
 * 2: basic
 * 3: sex
 * 4: beauty
 * 5: combat
 * 6: cybernetic
 *
 * Can also be used to check for groups:
 * 101: any limbs, that are not amputated
 * 102: prosthetic limbs off all kind
 * 103: sex-prosthetic
 * 104: beauty-prosthetic
 * 105: combat-prosthetic
 *
 * 103-105 mean the sum of 3-5 and 6 respectfully.
 *
 * No ID means all limbs = 101
 *
 * @param {App.Entity.SlaveState} slave
 * @param {number} [id]
 * @returns {number}
 */
globalThis.getLimbCount = function(slave, id = 101) {
	if (id < 100) {
		let n = 0;
		if (getLeftArmID(slave) === id) {
			n++;
		}
		if (getRightArmID(slave) === id) {
			n++;
		}
		if (getLeftLegID(slave) === id) {
			n++;
		}
		if (getRightLegID(slave) === id) {
			n++;
		}
		return n;
	}
	switch (id) {
		case 101:
			return getLimbCount(slave, 1) + getLimbCount(slave, 102);
		case 102:
			return getLimbCount(slave, 2) + getLimbCount(slave, 3) + getLimbCount(slave, 4) + getLimbCount(slave, 5) + getLimbCount(slave, 6);
		case 103:
			return getLimbCount(slave, 3) + getLimbCount(slave, 6);
		case 104:
			return getLimbCount(slave, 4) + getLimbCount(slave, 6);
		case 105:
			return getLimbCount(slave, 5) + getLimbCount(slave, 6);
	}
	// unknown id defaults to 0
	return 0;
};

/**
 * Returns count of specified leg type. Uses new limb IDs.
 *
 * @param {App.Entity.SlaveState} slave
 * @param {number} id
 * @returns {number}
 */
globalThis.getLegCount = function(slave, id) {
	let n = 0;

	if (getLeftLegID(slave) === id) {
		n++;
	}
	if (getRightLegID(slave) === id) {
		n++;
	}

	return n;
};

/**
 * Returns count of specified arm type. Uses new limb IDs.
 *
 * @param {App.Entity.SlaveState} slave
 * @param {number} id
 * @returns {number}
 */
globalThis.getArmCount = function(slave, id) {
	let n = 0;

	if (getLeftArmID(slave) === id) {
		n++;
	}
	if (getRightArmID(slave) === id) {
		n++;
	}

	return n;
};

/**
 * Returns a string depending on the limbs a slave has.
 * By default a variation of "arms and legs", but this can be changed via parameters.
 * Expects the slave to at least have one limb.
 *
 * @param {App.Entity.SlaveState} slave
 * @param {string} [arms]
 * @param {string} [arm]
 * @param {string} [legs]
 * @param {string} [leg]
 */
globalThis.armsAndLegs = function(slave, arms = "arms", arm = "arm", legs = "legs", leg = "leg") {
	let r = "";
	if (hasAnyArms(slave)) {
		if (hasBothArms(slave)) {
			r += arms;
		} else {
			r += arm;
		}
		if (hasAnyLegs(slave)) {
			r += " and ";
		}
	}

	if (hasBothLegs(slave)) {
		r += legs;
	} else if (hasAnyLegs(slave)) {
		r += leg;
	}

	return r;
};