// 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
	};
}();

src/js/slaveSummaryHelpers.js