diff --git a/.eslintrc.json b/.eslintrc.json
index 94018bf881d335f6a4665bed094564d110d718da..6be7f200842659a5c386755a791dd90f1b90971d 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -30,12 +30,13 @@
 	"rules": {
 		"semi": ["warn", "always"],
 		"semi-spacing": "warn",
-		"semi-style": "warn",	
+		"semi-style": "warn",
 		"block-spacing": ["warn", "always"],
 		"curly": ["warn", "all"], 
 		"eqeqeq": "warn",
 		"no-fallthrough": "error",
 		"space-before-function-paren": ["warn", "never"],
+		"template-curly-spacing": ["warn", "never"],
 		"no-trailing-spaces": "warn",
 		"no-unneeded-ternary": "warn",
 		"camelcase": "warn",
diff --git a/src/art/vector/VectorArtJS.js b/src/art/vector/VectorArtJS.js
index e407e3c92982b87e51ec985e63aaf79d62ac789f..accfb9ada6eeb858346a161a135cf56c0facff49 100644
--- a/src/art/vector/VectorArtJS.js
+++ b/src/art/vector/VectorArtJS.js
@@ -1276,16 +1276,16 @@ window.VectorArt = (function() {
 			} else if (slave.clothes === "restrictive latex") {
 				/* slave wears restrictive latex - display most skin as if it was rubber */
 				/* nice latex does not cover any privates. */
-				T.boobSkinStyle = "fill:" + T.skinColour + ";";
-				T.penisSkinStyle = "fill:" + T.skinColour + ";";
-				T.scrotumSkinStyle = "fill:" + T.skinColour + ";";
-				T.torsoSkinStyle = "fill:" + T.skinColour + ";";
+				T.boobSkinStyle = `fill:${T.skinColour};`;
+				T.penisSkinStyle = `fill:${T.skinColour};`;
+				T.scrotumSkinStyle = `fill:${T.skinColour};`;
+				T.torsoSkinStyle = `fill:${T.skinColour};`;
 				/* rest of body is covered in latex */
 				T.skinColour = outfitBaseColour;
 				T.bellybuttonStyle = outfitBaseColour;
 			} else if (slave.clothes === "a latex catsuit") {
 				/* nice latex does not cover head. */
-				T.headSkinStyle = "fill:" + T.skinColour + ";";
+				T.headSkinStyle = `fill:${T.skinColour};`;
 				/* rest of body is covered in latex */
 				T.skinColour = outfitBaseColour;
 				/* catsuit covers areolae and crotch, too */
@@ -1296,7 +1296,7 @@ window.VectorArt = (function() {
 				T.bellybuttonStyle = outfitBaseColour;
 			} else if (slave.clothes === "a comfortable bodysuit") {
 				/* nice bodysuit does not cover head. */
-				T.headSkinStyle = "fill:" + T.skinColour + ";";
+				T.headSkinStyle = `fill:${T.skinColour};`;
 				/* rest of body is covered in bodysuit */
 				T.skinColour = outfitBaseColour;
 				T.bellySkinStyle = outfitBaseColour;
@@ -1600,10 +1600,10 @@ window.VectorArt = (function() {
 			/* override color in case of full body latex outfit, or custom color*/
 			if (slave.clothes === "a Fuckdoll suit" || slave.clothes === "restrictive latex") {
 				T.shoeColour = T.skinColour;
-				T.shoeShadowColour = T.shoeColour + ";opacity: 0.5"; /* TODO: do not abuse "color" variable for style definitions. do not rely on dark background for shadow effect either. */
+				T.shoeShadowColour = `${T.shoeColour};opacity: 0.5`; /* TODO: do not abuse "color" variable for style definitions. do not rely on dark background for shadow effect either. */
 			} else if (slave.shoeColor !== undefined) {
-				T.shoeColour = slave.shoeColor + ";opacity: 0.4"; /* shoe color selected by user */
-				T.shoeShadowColour = T.shoeColour + ";opacity: 0.5"; /* TODO: do not abuse "color" variable for style definitions. do not rely on dark background for shadow effect either. */
+				T.shoeColour = `${slave.shoeColor};opacity: 0.4`; /* shoe color selected by user */
+				T.shoeShadowColour = `${T.shoeColour};opacity: 0.5`; /* TODO: do not abuse "color" variable for style definitions. do not rely on dark background for shadow effect either. */
 			} else {
 				T.shoeShadowColour = "#616a6b";
 				if (slave.shoes === "none") {
@@ -2674,7 +2674,7 @@ window.VectorArt = (function() {
 
 window.LegacyVectorArt = function(slave, artSize) {
 	const filePath = "'resources/vector";
-	const skinFilePath = filePath + "/body/white";
+	const skinFilePath = `${filePath}/body/white`;
 	const wearingLatex = slave.clothes === "a Fuckdoll suit" || slave.clothes === "restrictive latex" || slave.clothes === "a latex catsuit";
 	let hairStyle, underArmHStyle, leftArmType, rightArmType, buttSize, legSize, shoesType, torsoSize, boobSize, ballSize, penisSize;
 	let needBoobs = true;
@@ -2789,7 +2789,7 @@ window.LegacyVectorArt = function(slave, artSize) {
 		legSize = "wide";
 	}
 	if (slave.amp === 1) {
-		legSize = "stump " + legSize;
+		legSize = `stump ${legSize}`;
 	}
 
 	if (wearingLatex === true && slave.amp !== 1) {
diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js
index 67783593c4beb01ae3890baddbd16e77e20fec6a..a6cdfb3dffe3335ab49b659bbc983e4d9994760a 100644
--- a/src/js/DefaultRules.js
+++ b/src/js/DefaultRules.js
@@ -2913,7 +2913,7 @@ window.DefaultRules = (function() {
 	 */
 	function ProcessLabel(slave, rule) {
 		if (rule.label !== "no default setting" && !slave.custom.label.includes(`[${rule.label}]`)) {
-			slave.custom.label = `${slave.custom.label }[${ rule.label }]`;
+			slave.custom.label = `${slave.custom.label}[${rule.label}]`;
 			r += `<br>${slave.slaveName} has been tagged as ${rule.label}`;
 		}
 
diff --git a/src/js/rulesAssistantOptions.js b/src/js/rulesAssistantOptions.js
index 1cb7754af63af8b2a38dc95f5bf8a5174c012719..0a019e87f8d088dfa48b58c11676251615738eea 100644
--- a/src/js/rulesAssistantOptions.js
+++ b/src/js/rulesAssistantOptions.js
@@ -167,7 +167,7 @@ window.rulesAssistantOptions = (function() {
 	// it can be "bound" to a variable by setting its "onchange" method
 	class EditorWithShortcuts extends Element {
 		constructor(prefix, data = [], editor = false, ...args) {
-			super(`${prefix }: `, editor, ...args);
+			super(`${prefix}: `, editor, ...args);
 			this.selectedItem = null;
 			data.forEach(item => this.appendChild(new ListItem(...item)));
 		}
@@ -305,7 +305,7 @@ window.rulesAssistantOptions = (function() {
 		render(label) {
 			const elem = document.createElement("div");
 			const lelem = document.createElement("em");
-			lelem.innerText = `${label }: `;
+			lelem.innerText = `${label}: `;
 			elem.appendChild(lelem);
 			return elem;
 		}
@@ -434,7 +434,7 @@ window.rulesAssistantOptions = (function() {
 					V.defaultRules.push(rule);
 				reload(this.root);
 			} catch (e) {
-				alert(`Couldn't import that rule:\n${ e.message}`);
+				alert(`Couldn't import that rule:\n${e.message}`);
 			}
 		}
 	}
@@ -708,7 +708,7 @@ window.rulesAssistantOptions = (function() {
 
 			const min = document.createElement("input");
 			min.setAttribute("type", "text");
-			min.value = `${ data.value[0]}`;
+			min.value = `${data.value[0]}`;
 			min.onkeypress = e => { if (returnP(e)) this.setmin(min.value); };
 			min.onblur = e => this.setmin(min.value);
 			this.min = min;
@@ -722,7 +722,7 @@ window.rulesAssistantOptions = (function() {
 
 			const max = document.createElement("input");
 			max.setAttribute("type", "text");
-			max.value = `${ data.value[1]}`;
+			max.value = `${data.value[1]}`;
 			max.onkeypress = e => { if (returnP(e)) this.setmax(max.value); };
 			max.onblur = e => this.setmax(max.value);
 			this.max = max;
@@ -795,7 +795,7 @@ window.rulesAssistantOptions = (function() {
 		}
 
 		info(attribute) {
-			return `Insert a valid JSON array. Known values: ${ {
+			return `Insert a valid JSON array. Known values: ${{
 				"fetish": "buttslut, cumslut, masochist, sadist, dom, submissive, boobs, pregnancy, none (AKA vanilla)",
 				"amp": "Amputated: 1, Not amputated: 0",
 				"genes": "XX, XY",
@@ -1317,9 +1317,9 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					bellies.push([acc.name, acc.value]);
 				else if (acc.fs === "repopulation" && V.arcologies[0].FSRepopulationFocus !== "unset")
-					bellies.push([`${acc.name } (FS)`, acc.value]);
+					bellies.push([`${acc.name} (FS)`, acc.value]);
 				else if (acc.rs === "boughtBelly" && V.clothesBoughtBelly === 1)
-					bellies.push([`${acc.name } (Purchased)`, acc.value]);
+					bellies.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Corsetage", bellies);
 			this.setValue(current_rule.set.bellyAccessory);
@@ -1361,7 +1361,7 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					accs.push([acc.name, acc.value]);
 				else if (acc.rs === "buyBigDildos" && V.toysBoughtDildos === 1)
-					accs.push([`${acc.name } (Purchased)`, acc.value]);
+					accs.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Vaginal accessories for virgins", accs);
 			this.setValue(current_rule.set.virginAccessory);
@@ -1376,7 +1376,7 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					accs.push([acc.name, acc.value]);
 				else if (acc.rs === "buyBigDildos" && V.toysBoughtDildos === 1)
-					accs.push([`${acc.name } (Purchased)`, acc.value]);
+					accs.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Vaginal accessories for anal virgins", accs);
 			this.setValue(current_rule.set.aVirginAccessory);
@@ -1391,7 +1391,7 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					accs.push([acc.name, acc.value]);
 				else if (acc.rs === "buyBigDildos" && V.toysBoughtDildos === 1)
-					accs.push([`${acc.name } (Purchased)`, acc.value]);
+					accs.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Vaginal accessories for other slaves", accs);
 			this.setValue(current_rule.set.vaginalAccessory);
@@ -1406,7 +1406,7 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					accs.push([acc.name, acc.value]);
 				else if (acc.rs === "buyVaginalAttachments" && V.toysBoughtVaginalAttachments === 1)
-					accs.push([`${acc.name } (Purchased)`, acc.value]);
+					accs.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Vaginal attachments for slaves with vaginal accessories", accs);
 			this.setValue(current_rule.set.vaginalAttachment);
@@ -1463,7 +1463,7 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					accs.push([acc.name, acc.value]);
 				else if (acc.rs === "buyBigPlugs" && V.toysBoughtButtPlugs === 1)
-					accs.push([`${acc.name } (Purchased)`, acc.value]);
+					accs.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Buttplugs for anal virgins", accs);
 			this.setValue(current_rule.set.aVirginButtplug);
@@ -1478,7 +1478,7 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					accs.push([acc.name, acc.value]);
 				else if (acc.rs === "buyBigPlugs" && V.toysBoughtButtPlugs === 1)
-					accs.push([`${acc.name } (Purchased)`, acc.value]);
+					accs.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Buttplugs for other slaves", accs);
 			this.setValue(current_rule.set.buttplug);
@@ -1493,7 +1493,7 @@ window.rulesAssistantOptions = (function() {
 				if (acc.fs === undefined && acc.rs === undefined)
 					accs.push([acc.name, acc.value]);
 				else if (acc.rs === "buyTails" && V.toysBoughtButtPlugTails === 1)
-					accs.push([`${acc.name } (Purchased)`, acc.value]);
+					accs.push([`${acc.name} (Purchased)`, acc.value]);
 			});
 			super("Buttplug attachments for slaves with buttplugs", accs);
 			this.setValue(current_rule.set.buttplugAttachment);
diff --git a/src/js/slaveGenerationJS.js b/src/js/slaveGenerationJS.js
index 02d54368600bed8e951be2dc238065f0eb67f906..867e78ee5e518ad36a98bdc985ace9680ba5ef0b 100644
--- a/src/js/slaveGenerationJS.js
+++ b/src/js/slaveGenerationJS.js
@@ -27,7 +27,7 @@ window.generateName = function generateName(nationality, race, male, filter) {
 	filter = filter || _.stubTrue; /* default: allow all */
 	const lookup = (male ? setup.malenamePoolSelector : setup.namePoolSelector);
 	const result = jsEither(
-		(lookup[`${nationality }.${ race}`] || lookup[nationality] ||
+		(lookup[`${nationality}.${race}`] || lookup[nationality] ||
 			(male ? setup.whiteAmericanMaleNames : setup.whiteAmericanSlaveNames)).filter(filter));
 	/* fallback for males without specific male name sets: return female name */
 	if (male && !result) {
@@ -39,12 +39,12 @@ window.generateName = function generateName(nationality, race, male, filter) {
 window.generateSurname = function generateSurname(nationality, race, male, filter) {
 	filter = filter || _.stubTrue; /* default: allow all */
 	const result = jsEither(
-		(setup.surnamePoolSelector[`${nationality }.${ race}`] ||
+		(setup.surnamePoolSelector[`${nationality}.${race}`] ||
 			setup.surnamePoolSelector[nationality] ||
 			setup.whiteAmericanSlaveSurnames).filter(filter));
 	if (male) {
 		/* see if we have male equivalent of that surname, and return that if so */
-		const maleLookup = setup.maleSurnamePoolSelector[`${nationality }.${ race}`] || setup.maleSurnamePoolSelector[nationality];
+		const maleLookup = setup.maleSurnamePoolSelector[`${nationality}.${race}`] || setup.maleSurnamePoolSelector[nationality];
 		if (maleLookup && maleLookup[result]) {
 			return maleLookup[result];
 		}
@@ -53,7 +53,7 @@ window.generateSurname = function generateSurname(nationality, race, male, filte
 };
 
 window.isMaleName = function isMaleName(name, nationality, race) {
-	const names = setup.malenamePoolSelector[`${nationality }.${ race}`] ||
+	const names = setup.malenamePoolSelector[`${nationality}.${race}`] ||
 		setup.malenamePoolSelector[nationality] ||
 		setup.whiteAmericanMaleNames;
 	return names && names.includes(name);
diff --git a/src/js/slaveStatsChecker.js b/src/js/slaveStatsChecker.js
index cbc92b028c7098e58b1dcd56816215051623bfec..3a225da8e2683093a5d319d4e5d2c07c571675f0 100644
--- a/src/js/slaveStatsChecker.js
+++ b/src/js/slaveStatsChecker.js
@@ -386,16 +386,14 @@ window.canAchieveErection = function(slave) {
 		return false;
 	} else if (slave.aphrodisiacs > 1 || (slave.inflationType == "aphrodisiac" && slave.inflation >= 2)) {
 		return true;
-	} else if (slave.balls > 0 ? slave.hormoneBalance < 100 : slave.hormoneBalance <= -100) {
-		return false;
-	} else if (slave.ballType !== "sterile") {
+	} else if (slave.ballType === "sterile") {
 		return false;
+	} else if ((slave.balls > 0 ? slave.hormoneBalance < 100 : slave.hormoneBalance <= -100) && slave.drugs !== "hormone blockers") {
+		return true;
 	} else if (slave.aphrodisiacs > 0 || slave.inflationType == "aphrodisiac") {
 		return true;
-	} else if (slave.drugs === "hormone blockers") { 
-		return false;
 	}
-	return true;
+	return false;
 };
 
 /**
diff --git a/src/js/utilJS.js b/src/js/utilJS.js
index 5968ba64cc1187d9a81e759d26d7e941fff06750..f0b7d4dc80386b92ac99b10386c3bc4833c9c3e5 100644
--- a/src/js/utilJS.js
+++ b/src/js/utilJS.js
@@ -539,7 +539,7 @@ window.Height = (function() {
 
 	// Helper method - table lookup for nationality/race combinations
 	const nationalityMeanHeight = function(table, nationality, race, def) {
-		return table[`${nationality }.${ race}`] || table[nationality] || table[`.${ race}`] || table[""] || def;
+		return table[`${nationality}.${race}`] || table[nationality] || table[`.${race}`] || table[""] || def;
 	};
 
 	// Helper method: Generate a skewed normal random variable with the skew s
@@ -648,11 +648,11 @@ window.Height = (function() {
 			case "YY":
 			case "YYY":
 				// eslint-disable-next-line no-console
-				console.log(`Height.mean(): non-viable genes ${ genes}`);
+				console.log(`Height.mean(): non-viable genes ${genes}`);
 				break;
 			default:
 				// eslint-disable-next-line no-console
-				console.log(`Height.mean(): unknown genes ${ genes }, returning mean between XX and XY`);
+				console.log(`Height.mean(): unknown genes ${genes}, returning mean between XX and XY`);
 				result = nationalityMeanHeight(xxMeanHeight, nationality, race) * 0.5 + nationalityMeanHeight(xyMeanHeight, nationality, race) * 0.5;
 				break;
 		}
@@ -992,7 +992,7 @@ window.num = function(x) {
 				if (x > 0) {
 					return string;
 				} else {
-					return `negative ${ string}`;
+					return `negative ${string}`;
 				}
 			} else {
 				return commaNum(x);
@@ -1001,7 +1001,7 @@ window.num = function(x) {
 			if (x > 0) {
 				return string;
 			} else {
-				return `negative ${ string}`;
+				return `negative ${string}`;
 			}
 		}
 	} else {
@@ -1016,9 +1016,9 @@ window.cashFormat = function(s) {
 		s = Math.trunc(s);
 	}
 	if (s > 0) {
-		return `¤${ num(s)}`;
+		return `¤${num(s)}`;
 	} else {
-		return `¤${ s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`;
+		return `¤${s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`;
 	}
 };
 
@@ -1027,11 +1027,11 @@ window.repFormat = function(s) {
 	/* if (!s) { s = 0; }*/
 	if (V.cheatMode === 1 || V.debugMode === 1) {
 		if (s > 0) {
-			return `<span class="green">${ num(Math.round(s * 100) / 100) } rep</span>`;
+			return `<span class="green">${num(Math.round(s * 100) / 100)} rep</span>`;
 		} else if (s < 0) {
-			return `<span class="red">${ num(Math.round(s * 100) / 100) } rep</span>`;
+			return `<span class="red">${num(Math.round(s * 100) / 100)} rep</span>`;
 		} else {
-			return `${num(Math.round(s * 100) / 100) } rep`;
+			return `${num(Math.round(s * 100) / 100)} rep`;
 		}
 	} else {
 		/* In order to calculate just how much any one category matters so we can show a "fuzzy" symbolic value to the player, we need to know how "busy" reputation was this week. To calculate this, I ADD income to expenses. Why? 100 - 100 and 10000 - 10000 BOTH are 0, but a +50 event matters a lot more in the first case than the second. I exclude overflow from the calculation because it's not a "real" expense for our purposes, and divide by half just to make percentages a bit easier. */
@@ -1358,7 +1358,7 @@ window.cmToFootInchString = function(s) {
 	if (Math.round(s / 2.54) < 12) {
 		return cmToInchString(s);
 	}
-	return `${Math.trunc(Math.round(s/2.54)/12) }'${ Math.round(s/2.54)%12 }"`;
+	return `${Math.trunc(Math.round(s/2.54)/12)}'${Math.round(s/2.54)%12}"`;
 };
 
 // takes a dick value e.g. $activeSlave.dick, returns a string in the format 6 inches
@@ -1392,60 +1392,60 @@ window.ballsToCM = function(s) {
 // takes a dick value e.g. $activeSlave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
 window.dickToEitherUnit = function(s) {
 	if (State.variables.showInches === 1) {
-		return `${dickToCM(s) }cm (${ dickToInchString(s) })`;
+		return `${dickToCM(s)}cm (${dickToInchString(s)})`;
 	}
 	if (State.variables.showInches === 2) {
 		return dickToInchString(s);
 	}
-	return `${dickToCM(s) }cm`;
+	return `${dickToCM(s)}cm`;
 };
 
 // takes a ball value e.g. $activeSlave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
 window.ballsToEitherUnit = function(s) {
 	if (State.variables.showInches === 1) {
-		return `${ballsToCM(s) }cm (${ ballsToInchString(s) })`;
+		return `${ballsToCM(s)}cm (${ballsToInchString(s)})`;
 	}
 	if (State.variables.showInches === 2) {
 		return ballsToInchString(s);
 	}
-	return `${ballsToCM(s) }cm`;
+	return `${ballsToCM(s)}cm`;
 };
 
 // takes an int in centimeters e.g. $activeSlave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm`
 window.heightToEitherUnit = function(s) {
 	if (State.variables.showInches === 1) {
-		return `${s }cm (${ cmToFootInchString(s) })`;
+		return `${s}cm (${cmToFootInchString(s)})`;
 	}
 	if (State.variables.showInches === 2) {
 		return cmToFootInchString(s);
 	}
-	return `${s }cm`;
+	return `${s}cm`;
 };
 
 // takes an int in centimeters e.g. $activeSlave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm`
 window.lengthToEitherUnit = function(s) {
 	if (State.variables.showInches === 1) {
-		return `${s }cm (${ cmToInchString(s) })`;
+		return `${s}cm (${cmToInchString(s)})`;
 	}
 	if (State.variables.showInches === 2) {
 		return cmToInchString(s);
 	}
-	return `${s }cm`;
+	return `${s}cm`;
 };
 
 window.ordinalSuffix = function ordinalSuffix(i) {
 	let j = i % 10;
 	let k = i % 100;
 	if (j === 1 && k !== 11) {
-		return `${i }st`;
+		return `${i}st`;
 	}
 	if (j === 2 && k !== 12) {
-		return `${i }nd`;
+		return `${i}nd`;
 	}
 	if (j === 3 && k !== 13) {
-		return `${i }rd`;
+		return `${i}rd`;
 	}
-	return `${i }th`;
+	return `${i}th`;
 };
 
 window.removeDuplicates = function removeDuplicates(array) {