diff --git a/compile.bat b/compile.bat
index 2a06b08270c083d45c949ee958095a248b32b52d..d91b67280d69782543c0faf24b4cc5683b099907 100644
--- a/compile.bat
+++ b/compile.bat
@@ -4,7 +4,7 @@
 :: Set working directory
 pushd %~dp0
 if not exist "bin\resources" mkdir bin\resources
-CALL devTools/concatFiles.bat js/ "*.js" bin/fc.js
+CALL devTools/concatFiles.bat js\ "*.js" bin\fc.js
 :: Run the appropriate compiler for the user's CPU architecture.
 if %PROCESSOR_ARCHITECTURE% == AMD64 (
 	CALL "%~dp0devTools\tweeGo\tweego_win64.exe" -o "%~dp0bin/FC_pregmod.html" --module=bin/fc.js --head devTools/head.html "%~dp0src"
diff --git a/devTools/FC.d.ts b/devTools/FC.d.ts
index abc179ce417d94675542045bb8b6d52c0cc05446..c1ffd2605ddeb79957033c4a4ddf9f1c835dd0b0 100644
--- a/devTools/FC.d.ts
+++ b/devTools/FC.d.ts
@@ -35,6 +35,32 @@ declare namespace App {
 				noun: string;
 			}
 		}
+
+		namespace SlaveSummary{
+			class SmartPiercing{
+				setting: {
+					off: string,
+					submissive: string,
+					lesbian: string,
+					oral: string,
+					humiliation: string,
+					anal: string,
+					boobs: string,
+					sadist: string,
+					masochist: string,
+					dom: string,
+					pregnancy: string,
+					vanilla: string,
+					all: string,
+					none: string,
+					monitoring: string,
+					men: string,
+					women: string,
+					"anti-men": string,
+					"anti-women": string,
+				}
+			}
+		}
 	}
 
 	namespace Debug {}
@@ -254,8 +280,28 @@ declare namespace App {
 		}
 		namespace View { }
 		namespace SlaveSummary {
-			type StringRenderer = (slave: App.Entity.SlaveState) => String;
 			type AppendRenderer = (slave: App.Entity.SlaveState, parentNode: Node) => void;
+
+			class AbbreviationState {
+				clothes: number;
+				devotion: number;
+				diet: number;
+				drugs: number;
+				genitalia: number;
+				health: number;
+				hormoneBalance: number;
+				mental: number;
+				nationality: number;
+				origins: number;
+				physicals: number;
+				race: number;
+				rules: number;
+				rulesets: number;
+				skills: number;
+			}
+			class State {
+				abbreviation: AbbreviationState;
+			}
 		}
 	}
 
diff --git a/devTools/concatFiles.bat b/devTools/concatFiles.bat
index cce8129af35c054ac8f951eef9818ff8ae4aa08d..3da9527a846693b3f9a9c87fea44c70c5b48e6f4 100644
--- a/devTools/concatFiles.bat
+++ b/devTools/concatFiles.bat
@@ -2,12 +2,13 @@
 :: Concatenates files from dir %1 specified with wildcard %2 and outputs result to %3
 
 :: TODO Proper temp file instead of bin\list.txt
+IF EXIST %3 DEL %3
 SET _LISTFILE="bin\list.txt"
 >%_LISTFILE% (FOR /R "%~1" %%F IN (%2) DO echo "%%F")
 sort /O %_LISTFILE% %_LISTFILE%
->%3 (FOR /F "usebackq delims=" %%F IN (`type "%_LISTFILE%"`) DO (
-	echo /* %%F */
-	type %%F
+(FOR /F "usebackq delims=" %%F IN (`type "%_LISTFILE%"`) DO (
+	echo /* %%F */ >> %3
+	copy /b %3+%%F %3 1>NUL
 	)
 )
 
diff --git a/js/002-config/fc-js-init.js b/js/002-config/fc-js-init.js
index 6ee8fcd98120639ab35fa353a5727ed02858bd4e..5eff87da0a2f291b10f185220944887ec5494106 100644
--- a/js/002-config/fc-js-init.js
+++ b/js/002-config/fc-js-init.js
@@ -17,7 +17,7 @@ App.Arcology.Cell = {},
 App.Art = {};
 App.Corporate = {};
 App.Data = {};
-App.Data.HeroSlaves = { };
+App.Data.HeroSlaves = {};
 App.Data.Weather = {};
 App.Debug = {};
 App.Desc = {};
diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 27885b3ef3a8ee399e4eb6d95abd487c30f0c0ed..ff132f1b933430dfe5d1d1be0313437b6fc2b3b5 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -32,26 +32,33 @@ App.Data.defaultGameStateVariables = {
 	cheater: 0,
 	cash: 0,
 	cashLastWeek: 0,
+	taintedSaveFile: 0,
 
 	// UI content
+	UI: {
+		slaveSummary: {
+			abbreviation: {
+				clothes: 2,
+				devotion: 2,
+				diet: 2,
+				drugs: 2,
+				genitalia: 2,
+				health: 2,
+				mental: 2,
+				nationality: 2,
+				origins: 2,
+				physicals: 2,
+				race: 2,
+				rules: 2,
+				rulesets: 2,
+				skills: 2,
+			}
+		}
+	},
 	FSNamePref: 0,
 	HGFormality: 1,
 	HGSeverity: 0,
-	abbreviateClothes: 2,
-	abbreviateDevotion: 2,
-	abbreviateDiet: 2,
-	abbreviateDrugs: 2,
-	abbreviateGenitalia: 2,
-	abbreviateHealth: 2,
-	abbreviateMental: 2,
-	abbreviateNationality: 2,
-	abbreviateOrigins: 2,
-	abbreviatePhysicals: 2,
-	abbreviateRace: 2,
-	abbreviateRules: 2,
-	abbreviateRulesets: 2,
 	abbreviateSidebar: 2,
-	abbreviateSkills: 2,
 	adamPrinciple: 0,
 	allowFamilyTitles: 0,
 	allowMaleSlaveNames: false,
@@ -1385,7 +1392,6 @@ App.Data.resetOnNGPlus = {
 		compact: 1, Cash: 1, Upkeep: 1, SexSlaveCount: 1, roomPop: 1, Rep: 1, GSP: 1, Authority: 1, Security: 1, Crime: 1, confirmWeekEnd: 0,
 	},
 	DefaultBirthDestination: "individually decided fates",
-	abbreviateHormoneBalance: 2,
 	legendaryFacility: 0,
 	heroSlavesPurchased : [],
 	fcnn: [
diff --git a/js/003-data/slaveSummaryData.js b/js/003-data/slaveSummaryData.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e2a4f5ab0c4d580823b324aa459dc564a1af5cc
--- /dev/null
+++ b/js/003-data/slaveSummaryData.js
@@ -0,0 +1,1404 @@
+// these ratings tables are consumed by App.UI.SlaveSummaryImpl.helpers. getNumericRating()
+// this function takes a value for rating and iterates a table (in the declaration order)
+// until it finds a key greater or equal to the value; the found record is returned from the function
+// Briefly, each dictionary entry key -> value is read as "the highest rating that still suits -> value" or
+// "up to, including"
+// The idea behind this is too decrease number of comparisons and retain compatibility with simple dictionaries
+// without sparse keys
+App.Data.SlaveSummary = {
+	long: {
+		body: {
+			age: {
+				17: "Underage.",
+				18: "Eighteen.",
+				19: "Nineteen.",
+				24: "Early twenties.",
+				29: "Late twenties.",
+				34: "Early thirties.",
+				39: "Late thirties.",
+				999: "Forties."
+			},
+			face: { // face value + 100
+				4: {desc: "Very ugly", style: "red"},
+				59: {desc: "Ugly", style: "red"},
+				89: {desc: "Unattractive", style: "red"},
+				110: {desc: "Average"},
+				140: {desc: "Attractive", style: "pink"},
+				195: {desc: "Beautiful", style: "pink"},
+				200: {desc: "Very beautiful", style: "pink"},
+			},
+			lips: {
+				10: {desc: "Thin lips", style: "red"},
+				20: {desc: "Normal lips"},
+				40: {desc: "Pretty lips"},
+				70: {desc: "Big lips"},
+				95: {desc: "Huge lips"},
+				100: {desc: "Facepussy"}
+			},
+			teeth: {
+				"crooked": {desc: "Crooked teeth.", style: "yellow"},
+				"gapped": {desc: "Tooth gap.", style: "yellow"},
+				"cosmetic braces": {desc: "Cosmetic braces."},
+				"straightening braces": {desc: "Braces."},
+				"removable": {desc: "Removable teeth."},
+				"pointy": {desc: "Sharp fangs."},
+				"baby": {desc: "Baby teeth."},
+				"mixed": {desc: "Mixed teeth."}
+			},
+			waist: { // waist value + 100
+				4: {desc: "Absurdly narrow waist", style: "pink"},
+				60: {desc: "Hourglass waist", style: "pink"},
+				89: {desc: "Feminine waist", style: "pink"},
+				110: {desc: "Average waist"},
+				140: {desc: "Unattractive waist", style: "red"},
+				195: {desc: "Ugly waist", style: "red"},
+				200: {desc: "Masculine waist", style: "red"}
+			},
+			genitalia: {
+				dickBalls: { // indices [dick, balls]
+					3: {
+						3: null,
+						4: "Big balls.",
+						5: "Huge balls.",
+						8: "Monstrous balls.",
+						99: "Hyper balls."
+					},
+					4: {
+						3: "Big dick.",
+						99: "Big dick & balls."
+					},
+					5: {
+						4: "Huge dick.",
+						99: "Huge dick & balls."
+					},
+					8: {
+						5: "Monster dong.",
+						99: "Monster dick & balls."
+					},
+					99: {
+						8: "Hyper dong.",
+						99: "Hyper dick & balls."
+					}
+				},
+				holes: { // indices [vagina, anus]
+					2: {
+						2: null,
+						3: "Gaping anus.",
+						4: "Permagaped anus."
+					},
+					3: {
+						2: "Loose pussy.",
+						3: "High mileage."
+					},
+					4: {
+						3: "Cavernous pussy.",
+						4: "Blown out holes."
+					}
+				}
+			},
+			titsAss: { // indices: [boobs, butt, FSAssetExpansionist(0,1), weight+100, muscles+100]
+				499: {
+					2: {
+						2: { // FSAssetExpansionist doesn't matter
+							109: {
+								130: {desc: "Girlish figure.", style: "pink"},
+							},
+						},
+					}
+				},
+				799: {
+					4: null,
+					6: {desc: "Big ass.", style: "pink"},
+					8: {desc: "Huge ass.", style: "pink"},
+					9: {desc: "Titanic ass.", style: "pink"},
+					999: {desc: "Hyper ass.", style: "pink"},
+				},
+				1999: {
+					4: {desc: "Big tits.", style: "pink"},
+					999: {desc: "Big T&A.", style: "pink"}
+				},
+				3999: {
+					6: {desc: "Huge tits.", style: "pink"},
+					999: {desc: "Huge T&A.", style: "pink"}
+				},
+				11999: {
+					8: {desc: "Monstrous tits.", style: "pink"},
+					999: {desc: "Enormous T&A.", style: "pink"},
+				},
+				100000: {
+					9: {desc: "Immobilizing tits.", style: "pink"},
+					999: {desc: "Hyper T&A.", style: "pink"}
+				}
+			},
+			weight: { // indices: [weigh + 100, FSHedonisticDecadence (0,1), hips + 2, ]
+				4: {desc: "Emaciated", style: "red"},
+				69: {
+					2: { // FSHedonisticDecadence doesn't matter
+						0: {desc: "Model-thin"},
+						5: {desc: "Very thin", style: "red"}
+					}
+				},
+				89: {desc: "Thin"},
+				110: {desc: "Trim"},
+				130: {desc: "Plush"},
+				195: {
+					0: {desc: "Overweight", style: "red"},
+					1: {
+						3: {desc: "Overweight", style: "red"},
+						5: {desc: "Nicely chubby"}
+					}
+				},
+				230: {
+					0: {desc: "Fat", style: "red"},
+					1: {
+						4: {desc: "Fat", style: "red"},
+						5: {desc: "Pleasantly soft and shapely"}
+					}
+				},
+				260: {
+					0: {desc: "Obese", style: "red"},
+					1: {desc: "Amazingly voluptuous"}
+				},
+				290: {
+					0: {desc: "Super Obese", style: "red"},
+					1: {desc: "SSBBW"}
+				},
+				999: {
+					0: {desc: "Dangerously Obese", style: "red"},
+					1: {desc: "Perfectly massive"}
+				}
+			},
+			hipsAss: {
+				0: {desc: "Disproportionately small butt.", style: "red"},
+				1: {desc: "Disproportionately big butt.", style: "red"}
+			},
+			muscles: { // indices: [muscles + 100, FSPhysicalIdealist(0,1) ]
+				4: {desc:"Frail", style:"red"},
+				69: {
+					0: {desc: "Very weak"},
+					1: {desc: "Very weak", style: "red"}
+				},
+				94: {
+					0: {desc: "Weak"},
+					1: {desc: "Weak", style: "red"}
+				},
+				105: {desc: "Soft"},
+				130: {desc: "Toned"},
+				150: {desc: "Fit"},
+				195: {desc: "Muscular"},
+				100: {desc: "Hugely muscular"}
+			}
+		},
+		mental: {
+			devotion: { // devotion value + 100
+				4: {desc: "Very hateful", style: ["devotion", "hateful"]},
+				49: {desc: "Hateful", style: ["devotion", "hateful"]},
+				79: {desc: "Resistant", style: ["devotion", "resistant"]},
+				120: {desc: "Ambivalent", style: ["devotion", "ambivalent"]},
+				150: {desc: "Accepting", style: ["devotion", "accept"]},
+				195: {desc: "Devoted", style: ["devotion", "devoted"]},
+				200: {desc: "Worshipful", style: ["devotion", "worship"]}
+			},
+			trust: { // first key: trust + 100, second key: devotion + 100
+				4: {desc: "Extremely terrified", style: ["trust", "extremely-terrified"]},
+				49: {desc: "Terrified", style: ["trust", "terrified"]},
+				79: {desc: "Frightened", style: ["trust", "frightened"]},
+				120: {desc: "Fearful", style: ["trust", "fearful"]},
+				150: {
+					79: {desc: "Careful", style: ["defiant", "careful"]},
+					200: {desc: "Careful", style: ["trust", "careful"]},
+				},
+				195: {
+					79: {desc: "Bold", style: ["defiant", "bold"]},
+					200: {desc: "Trusting", style: ["trust", "trusting"]},
+				},
+				200: {
+					79: {desc: "Defiant", style: ["defiant", "full"]},
+					200: {desc: "Profoundly trusting", style: ["trust", "prof-trusting"]},
+				}
+			},
+			education: { // index: intelligenceImplant + 15
+				0: ", hindered",
+				29: "",
+				44: ", educated",
+				99: ", well educated"
+			},
+			intelligence: { // index: intelligence + 100
+				4: {desc: "Moronic", style: "orangered"},
+				49: {desc: "Very slow", style: "orangered"},
+				84: {desc: "Slow", style: "orangered"},
+				115: {desc: "Average intelligence"},
+				150: {desc: "Smart", style: "deepskyblue"},
+				195: {desc: "Very smart", style: "deepskyblue"},
+				230: {desc: "Brilliant", style: "deepskyblue"},
+				999: {desc: "Genius", style: "deepskyblue"},
+			},
+			behavioralFlaw: {
+				"arrogant": "Arrogant.",
+				"bitchy": "Bitchy.",
+				"odd": "Odd.",
+				"hates men": "Hates men.",
+				"hates women": "Hates women.",
+				"gluttonous": "Stress eater.",
+				"anorexic": "Anorexic",
+				"devout": "Devoutly religious.",
+				"liberated": "Mentally liberated.",
+			}
+		},
+		fetish: { // indices [fetish, fetishStrength]
+			"submissive": {
+				60: "Submissive tendencies",
+				95: "Submissive",
+				100: "Complete submissive"
+			},
+			"cumslut": {
+				60: "Prefers oral",
+				95: "Oral fixation",
+				100: "Cumslut"
+			},
+			"humiliation": {
+				60: "Interest in humiliation",
+				95: "Exhibitionist",
+				100: "Humiliation slut"
+			},
+			"buttslut": {
+				60: "Prefers anal",
+				95: "Anal fixation",
+				100: "Buttslut"
+			},
+			"boobs": {
+				60: "Loves boobs",
+				95: "Breast fixation",
+				100: "Boobslut"
+			},
+			"sadist": {
+				60: "Sadistic tendencies",
+				95: "Sadist",
+				100: "Complete sadist"
+			},
+			"masochist": {
+				60: "Masochistic tendencies",
+				95: "Masochist",
+				100: "Complete masochist"
+			},
+			"dom": {
+				60: "Dominant tendencies",
+				95: "Dominant",
+				100: "Complete dom"
+			},
+			"pregnancy": {
+				60: "Interest in impregnation",
+				95: "Pregnancy kink",
+				100: "Pregnancy fetish"
+			},
+			"none": "Sexually vanilla"
+		},
+		clothes: {
+			"Western clothing": "Chaps.",
+			"a Santa dress": "Santa dress.",
+			"a ball gown": "Ball gown.",
+			"a bimbo outfit": "Bimbo outfit.",
+			"a biyelgee costume": "Biyelgee costume.",
+			"a bra": "Nice bra.",
+			"a bunny outfit": "Bunny outfit.",
+			"a burkini": "Burkini.",
+			"a burqa": "Burqa.",
+			"a button-up shirt and panties": "Button-up shirt, panties.",
+			"a button-up shirt": "Nice button-up shirt.",
+			"a chattel habit": "Chattel habit.",
+			"a cheerleader outfit": "Cheerleader.",
+			"a comfortable bodysuit": "Bodysuit.",
+			"a courtesan dress": "Courtesan dress.",
+			"a cybersuit": "Cybersuit.",
+			"a dirndl": "Dirndl.",
+			"a fallen nuns habit": "Slutty habit.",
+			"a gothic lolita dress": "Gothic lolita dress.",
+			"a halter top dress": "Halter top dress.",
+			"a hanbok": "Hanbok.",
+			"a hijab and abaya": "Hijab and abaya.",
+			"a hijab and blouse": "Hijab and blouse.",
+			"a huipil": "Huipil.",
+			"a kimono": "Kimono.",
+			"a klan robe": "Klan robe.",
+			"a latex catsuit": "Nice latex.",
+			"a leotard": "Leotard.",
+			"a long qipao": "Long Qipao.",
+			"a maternity dress": "Maternity dress.",
+			"a military uniform": "Military uniform.",
+			"a mini dress": "Mini dress.",
+			"a monokini": "Monokini.",
+			"a mounty outfit": "Mounty outfit.",
+			"a nice maid outfit": "Nice maid.",
+			"a nice nurse outfit": "Nice nurse.",
+			"a nice pony outfit": "Nice pony outfit.",
+			"a niqab and abaya": "Niqab and abaya.",
+			"a one-piece swimsuit": "Swimsuit.",
+			"a penitent nuns habit": "Cilice.",
+			"a police uniform": "Police uniform.",
+			"a red army uniform": "Red Army uniform.",
+			"a scalemail bikini": "Scalemail bikini.",
+			"a schoolgirl outfit": "Schoolgirl outfit.",
+			"a schutzstaffel uniform": "Schutzstaffel uniform.",
+			"a skimpy loincloth": "Skimpy loincloth.",
+			"a slave gown": "Slave gown.",
+			"a slutty klan robe": "Slutty klan robe.",
+			"a slutty maid outfit": "Slutty maid.",
+			"a slutty nurse outfit": "Slutty nurse.",
+			"a slutty outfit": "Slutty outfit.",
+			"a slutty pony outfit": "Slutty pony outfit.",
+			"a slutty qipao": "Slutty qipao.",
+			"a slutty schutzstaffel uniform": "Slutty Schutzstaffel uniform.",
+			"a sports bra": "Sports bra.",
+			"a string bikini": "String bikini.",
+			"a striped bra": "Striped bra.",
+			"a succubus outfit": "Succubus outfit.",
+			"a sweater and cutoffs": "Jean shorts, sweater.",
+			"a sweater and panties": "Sweater, panties.",
+			"a sweater": "Nice sweater.",
+			"a t-shirt and jeans": "Blue jeans, t-shirt.",
+			"a t-shirt and panties": "Panties, t-shirt.",
+			"a t-shirt and thong": "Thong, t-shirt.",
+			"a t-shirt": "T-shirt.",
+			"a tank-top and panties": "Tank-top, panties.",
+			"a tank-top": "Nice tank-top.",
+			"a thong": "Nice thong.",
+			"a toga": "Toga.",
+			"a tube top and thong": "Tube top, thong.",
+			"a tube top": "Nice tube top.",
+			"an apron": "Apron.",
+			"an oversized t-shirt and boy shorts": "Over-sized t-shirt, boy shorts.",
+			"an oversized t-shirt": "Nice over-sized t-shirt.",
+			"attractive lingerie for a pregnant woman": "Preggo lingerie.",
+			"attractive lingerie": "Nice lingerie.",
+			"battlearmor": "Battlearmor.",
+			"battledress": "Battledress.",
+			"body oil": "Body oil.",
+			"boyshorts": "Boy shorts.",
+			"chains": "Chains.",
+			"clubslut netting": "Netting.",
+			"conservative clothing": "Conservative clothing.",
+			"cutoffs and a t-shirt": "Cutoffs, t-shirt.",
+			"cutoffs": "Jean shorts.",
+			"harem gauze": "Harem outfit.",
+			"jeans": "Tight blue jeans.",
+			"kitty lingerie": "Kitty lingerie.",
+			"leather pants and a tube top": "Leather pants, tube top.",
+			"leather pants and pasties": "Leather pants, pasties.",
+			"leather pants": "Nice leather pants.",
+			"lederhosen": "Lederhosen.",
+			"nice business attire": "Nice suit.",
+			"overalls": "Overalls.",
+			"panties and pasties": "Pasties, panties.",
+			"panties": "Nice panties.",
+			"pasties": "Pasties.",
+			"restrictive latex": "Bondage latex.",
+			"shibari ropes": "Shibari.",
+			"slutty business attire": "Slutty suit.",
+			"slutty jewelry": "Bangles.",
+			"spats and a tank top": "Spats, tank top.",
+			"sport shorts and a sports bra": "Shorts, bra.",
+			"sport shorts and a t-shirt": "Nice sport shorts, shirt.",
+			"sport shorts": "Shorts.",
+			"stretch pants and a crop-top": "Stretch pants, crop-top.",
+			"striped panties": "Striped panties.",
+			"striped underwear": "Striped underwear",
+			"uncomfortable straps": "Leather straps.",
+
+		},
+		accessory: {
+			collar: {
+				"ancient Egyptian": "Wesekh.",
+				"ball gag": "Ball gag.",
+				"bell collar": "Bell collar.",
+				"bit gag": "Bit gag.",
+				"bowtie": "Bowtie collar.",
+				"cruel retirement counter": "Cruel counter collar.",
+				"dildo gag": "Dildo gag.",
+				"heavy gold": "Gold collar.",
+				"leather with cowbell": "Cowbell collar.",
+				"massive dildo gag": "Throat-bulging dildo gag.",
+				"neck corset": "Neck corset.",
+				"nice retirement counter": "Nice counter collar.",
+				"porcelain mask": "Porcelain mask.",
+				"preg biometrics": "Pregnancy biometrics collar.",
+				"pretty jewelry": "Pretty collar.",
+				"satin choker": "Satin choker.",
+				"shock punishment": "Shock collar.",
+				"silk ribbon": "Silken ribbon.",
+				"stylish leather": "Stylish leather collar.",
+				"tight steel": "Steel collar.",
+				"uncomfortable leather": "Leather collar.",
+			},
+			belly: {
+				"a corset": "Corset.",
+				"a huge empathy belly": "Huge fake belly.",
+				"a large empathy belly": "Large fake belly.",
+				"a medium empathy belly": "Medium fake belly.",
+				"a small empathy belly": "Small fake belly.",
+				"a support band": "Support band.",
+				"an extreme corset": "Extreme corsetage.",
+				"shapewear": "Shapewear.",
+			},
+			vaginal: {
+				"bullet vibrator": "Attached bullet vibrator.",
+				"smart bullet vibrator": "Attached smart bullet vibrator.",
+				"dildo": "Vaginal dildo.",
+				"large dildo": "Large vaginal dildo.",
+				"huge dildo": "Huge vaginal dildo.",
+				"long dildo": "Long vaginal dildo.",
+				"long, large dildo": "Long and large vaginal dildo.",
+				"long, huge dildo": "Long and wide vaginal dildo.",
+			},
+			buttplug: {
+				"plug": "Buttplug.",
+				"large plug": "Large buttplug.",
+				"huge plug": "Huge buttplug.",
+				"long plug": "Long buttplug.",
+				"long, large plug": "Large, long buttplug.",
+				"long, huge plug": "Enormous buttplug.",
+			},
+			buttplugAttachment: {
+				"tail": "Attached tail.",
+				"cat tail": "Attached cat tail.",
+				"fox tail": "Attached fox tail.",
+				"cow tail": "Attached cow tail.",
+			}
+		},
+		diet: {
+			"restricted": "Dieting.",
+			"fattening": "Gaining weight.",
+			"corrective": "Corrective.",
+			"XX": "Estrogen rich.",
+			"XY": "Testosterone rich.",
+			"XXY": "Futanari mix.",
+			"muscle building": "Pumping iron.",
+			"slimming": "Slimming down.",
+			"cum production": "Cum production.",
+			"cleansing": "Cleansing.",
+			"fertility": "Fertility.",
+		},
+		specialDiet: { // index: dietCum + 3 * dietMilk
+			0: null,
+			1: "Cum Added.",
+			2: "Cum Based.",
+			3: "Milk Based.",
+			4: "Milk & Cum Added.",
+			5: "Cum Based with Milk.",
+			6: "Milk Added.",
+			7: "Milk Based with Cum.",
+			8: "Cum and Milk Based."
+		},
+		race: {
+			"white": "Caucasian",
+			"asian": "Asian",
+			"indo-aryan": "Indo-aryan",
+			"latina": "Latina",
+			"middle eastern": "Middle Eastern",
+			"black": "Black",
+			"pacific islander": "Pacific Islander",
+			"malay": "Malay",
+			"amerindian": "Amerindian",
+			"semitic": "Semitic",
+			"southern european": "Southern European",
+			"mixed race": "Mixed race",
+		},
+		hormoneBalance: { // rating is hormoneBalance value + 500
+			100: "Overwhelmingly masculine",
+			200: "Extremely masculine",
+			300: "Heavily masculine",
+			400: "Very masculine",
+			479: "Masculine",
+			520: "Neutral",
+			599: "Feminine",
+			699: "Very feminine",
+			799: "Heavily feminine",
+			899: "Extremely feminine",
+			1000: "Overwhelmingly feminine"
+		},
+		health: {
+			illness: {
+				0: null,
+				1: {desc: "Sick", style: "yellow"},
+				2: {desc: "Ill", style: ["red", "strong"]},
+				3: {desc: "Very ill", style: ["red", "strong"]},
+				4: {desc: "Terribly ill", style: ["red", "strong"]},
+				5: {desc: "Dangerously ill", style: ["red", "strong"]}
+			},
+			tiredness: {
+				30: {desc: "Energetic", style: "green"},
+				60: {desc: "Tired", style: "yellow"},
+				90: {desc: "Fatigued", style: "orange"},
+				100: {desc: "Exhausted", style: ["red", "strong"]}
+			},
+			health: { // health + 100
+				10: {desc: "On the edge of death", style: ["red", "strong"]},
+				49: {desc: "Extremely unhealthy", style: ["red", "strong"]},
+				79: {desc: "Unhealthy", style: ["red", "strong"]},
+				120: {desc: "healthy", style: "yellow"},
+				150: {desc: "Very healthy", style: "green"},
+				190: {desc: "Extremely healthy", style: "green"},
+				200: {desc: "Unnaturally healthy", style: "green"}
+			},
+		},
+		prestige: {
+			0: null,
+			1: {desc: "Prestigious.", style: "green"},
+			2: {desc: "Very prestigious.", style: "green"},
+			3: {desc: "Extremely prestigious.", style: "green"},
+		},
+		pornPrestige: {
+			0: null,
+			1: {desc: "Porn amateur.", style: "green"},
+			2: {desc: "Porn slut.", style: "green"},
+			3: {desc: "Porn star.", style: "green"},
+		},
+		accent: {
+			0: null,
+			1: {desc: "Cute accent.", style: "pink"},
+			2: {desc: "Accent."},
+			3: {desc: "Bad accent.", style: "red"},
+			4: {desc: "No language skills.", style: "red"},
+		},
+		smartPiercing: {
+			setting: {
+				"off": "SP off.",
+				"submissive": "SP: submissive.",
+				"lesbian": "SP: lesbian",
+				"oral": "SP: oral.",
+				"humiliation": "SP: humiliation.",
+				"anal": "SP: anal.",
+				"boobs": "SP: breasts.",
+				"sadist": "SP: sadism.",
+				"masochist": "SP: masochism.",
+				"dom": "SP: dominance.",
+				"pregnancy": "SP: pregnancy.",
+				"vanilla": "SP: vanilla.",
+				"all": "SP: all.",
+				"none": "SP: none.",
+				"monitoring": "SP: monitoring",
+				"men": "SP: men",
+				"women": "SP: women",
+				"anti-men": "SP: anti men",
+				"anti-women": "SP: anti women",
+			}
+		},
+		skills: {
+			entertainment: {
+				10: null,
+				29: {desc: "Basic entertainer", style: "aquamarine"},
+				59: {desc: "Skilled entertainer", style: "aquamarine"},
+				99: {desc: "Expert entertainer", style: "aquamarine"},
+				999: {desc: "Masterful entertainer", style: "aquamarine"}
+			},
+			sex: { // indices: [sexSkills, hasVagina(0,1)]
+				30: {desc: "Sexually unskilled", style: "aquamarine"},
+				90: {desc: "Sexually skilled", style: "aquamarine"},
+				120: {desc: "Sexual expert", style: "aquamarine"},
+				180: {
+					0: {desc: "Masterful shemale", style: "aquamarine"},
+					1: {desc: "Sexual expert", style: "aquamarine"}
+				},
+				9999: {desc: "Sex master", style: "aquamarine"}
+			},
+			whoring: {
+				10: null,
+				29: {desc: "Basic whore", style: "aquamarine"},
+				59: {desc: "Skilled whore", style: "aquamarine"},
+				99: {desc: "Expert whore", style: "aquamarine"},
+				999: {desc: "Masterful whore", style: "aquamarine"}
+			},
+			mss: {desc: "Masterful Sex Slave.", style: "aquamarine"},
+			fighter: {desc: "Trained fighter.", style: "aquamarine"},
+		},
+		sexDrive: {
+			XX: {
+				5: {desc: "Disgusted by men", style: "red"},
+				15: {desc: "Turned off by men", style: "red"},
+				35: {desc: "Not attracted to men", style: "red"},
+				65: {desc: "Indifferent to men"},
+				85: {desc: "Attracted to men", style: "green"},
+				95: {desc: "Aroused by men", style: "green"},
+				999: {desc: "Passionate about men", style: "green"}
+			},
+			XY: {
+				5: {desc: "disgusted by women", style: "red"},
+				15: {desc: "turned off by women", style: "red"},
+				35: {desc: "not attracted to women", style: "red"},
+				65: {desc: "indifferent to women"},
+				85: {desc: "attracted to women", style: "green"},
+				95: {desc: "aroused by women", style: "green"},
+				999: {desc: "passionate about women", style: "green"}
+			},
+			energy: {
+				20: {desc: "No sex drive", style: "red"},
+				40: {desc: "Poor sex drive", style: "red"},
+				60: {desc: "Average sex drive", style: "yellow"},
+				80: {desc: "Good sex drive", style: "green"},
+				95: {desc: "Powerful sex drive", style: "green"},
+				999: {desc: "Nymphomaniac!", style: "green"}
+			},
+			synergy: {
+				omni: {desc: "Omnisexual!", style: "green"},
+				nymphomni: {desc: "Omnisexual nymphomaniac!", style: "green"}
+			}
+		}
+	},
+	short: {
+		body: {
+			age: {
+				18: "18",
+				19: "19",
+				24: "Ea20s",
+				29: "Lt20s",
+				34: "Ea30s",
+				39: "Lt30s",
+				999: "40s"
+			},
+			face: { // face value + 100
+				4: {desc: "Face---", style: "red"},
+				59: {desc: "Face--", style: "red"},
+				89: {desc: "Face-", style: "red"},
+				110: {desc: "Face"},
+				140: {desc: "Face+", style: "pink"},
+				195: {desc: "Face++", style: "pink"},
+				200: {desc: "Face+++", style: "pink"},
+			},
+			lips: {
+				10: {desc: "Lips-", style: "red"},
+				20: {desc: "Lips"},
+				40: {desc: "Lips+"},
+				70: {desc: "Lips++"},
+				95: {desc: "Lips+++"},
+				100: {desc: "Facepussy"}
+			},
+			teeth: {
+				"crooked": {desc: "Cr Teeth", style: "yellow"},
+				"gapped": {desc: "Gap", style: "yellow"},
+				"cosmetic braces": {desc: "Cos Braces"},
+				"straightening braces": {desc: "Braces"},
+				"removable": {desc: "Rem Teeth"},
+				"pointy": {desc: "Fangs"},
+				"baby": {desc: "Baby"},
+				"mixed": {desc: "Mixed"}
+			},
+			waist: { // waist value + 100
+				4: {desc: "Wst+++", style: "pink"},
+				60: {desc: "Wst++", style: "pink"},
+				89: {desc: "Wst+", style: "pink"},
+				110: {desc: "Wst"},
+				140: {desc: "Wst-", style: "red"},
+				195: {desc: "Wst--", style: "red"},
+				200: {desc: "Wst---", style: "red"}
+			},
+			genitalia:{
+				dickBalls: { // indices [dick, balls]
+					3: {
+						3: null,
+						4: "Balls",
+						5: "Balls+",
+						8: "Balls++",
+						99: "Balls+++"
+					},
+					4: {
+						3: "Dick",
+						99: "Junk"
+					},
+					5: {
+						4: "Dick+",
+						99: "Junk+"
+					},
+					8: {
+						5: "Dick++",
+						99: "Junk++"
+					},
+					99: {
+						8: "Dick+++",
+						99: "Junk+++"
+					}
+				},
+				holes: { // indices [vagina, anus]
+					2: {
+						2: null,
+						3: "A+",
+						4: "A++"
+					},
+					3: {
+						2: "V+",
+						3: "V+A+"
+					},
+					4: {
+						3: "V++",
+						4: "V++A++"
+					}
+				}
+			},
+			titsAss: { // indices: [boobs, butt, FSAssetExpansionist(0,1), weight+100, muscles+100]
+				499: {
+					2: {
+						2: { // FSAssetExpansionist doesn't matter
+							109: {
+								130: {desc: "Girlish", style: "pink"},
+							},
+						},
+					}
+				},
+				799: {
+					4: null,
+					6: {desc: "Ass", style: "pink"},
+					8: {desc: "Ass+", style: "pink"},
+					9: {desc: "Ass++", style: "pink"},
+					999: {desc: "Ass+++", style: "pink"},
+				},
+				1999: {
+					4: {desc: "Boobs", style: "pink"},
+					999: {desc: "T&A", style: "pink"}
+				},
+				3999: {
+					6: {desc: "Boobs+", style: "pink"},
+					999: {desc: "T&A+", style: "pink"}
+				},
+				11999: {
+					8:{desc: "Boobs++", style: "pink"},
+					999: {desc: "T&A++", style: "pink"},
+				},
+				100000: {
+					9: {desc: "Boobs+++", style: "pink"},
+					999: {desc:	"T&A+++", style: "pink"}
+				}
+			},
+			weight: { // indices: [weigh + 100, FSHedonisticDecadence (0,1), hips + 2, ]
+				4: {desc: "W---", style: ["red", "strong"]},
+				69: {
+					99: { // FSHedonisticDecadence doesn't matter
+						0: {desc: "W--", style: "strong"},
+						5: {desc: "W--", style: ["red", "strong"]}
+					}
+				},
+				89: {desc: "W-", style: "strong"},
+				110: {desc: "W", style: "strong"},
+				130: {desc: "W+", style: "strong"},
+				195: {
+					0: {desc: "W++", style: ["red", "strong"]},
+					1: {
+						3: {desc: "W++", style: ["red", "strong"]},
+						5: {desc: "W++", style: "strong"}
+					}
+				},
+				230: {
+					0: {desc: "W+++", style: ["red", "strong"]},
+					1: {
+						4: {desc: "W+++", style: ["red", "strong"]},
+						5: {desc: "W+++", style: "strong"}
+					}
+				},
+				260: {
+					0: {desc: "W++++", style: ["red", "strong"]},
+					1: {desc: "W++++", style: "strong"}
+				},
+				290: {
+					0: {desc: "W+++++", style: ["red", "strong"]},
+					1: {desc: "W+++++", style: "strong"}
+				},
+				999: {
+					0: {desc: "W++++++", style: ["red", "strong"]},
+					1: {desc: "W++++++", style: "strong"}
+				}
+			},
+			hipsAss: {
+				0: {desc: "Disp-", style: "red"},
+				1: {desc: "Disp+", style: "red"}
+			},
+			muscles: { // indices: [muscles + 100, FSPhysicalIdealist(0,1) ]
+				4: {desc:"Weak++", style:"red"},
+				69: {
+					0: {desc: "Soft+"},
+					1: {desc: "Weak+", style: "red"}
+				},
+				94: {
+					0: {desc: "Soft"},
+					1: {desc: "Weak", style: "red"}
+				},
+				105: {desc: "Soft"},
+				130: {desc: "Toned"},
+				150: {desc: "Fit"},
+				195: {desc: "Musc+"},
+				100: {desc: "Musc++"}
+			}
+		},
+		mental: {
+			devotion: { // devotion value + 100
+				4: {desc: "VHate", style: ["devotion", "hateful"]},
+				49: {desc: "Hate", style: ["devotion", "hateful"]},
+				79: {desc: "Res", style: ["devotion", "resistant"]},
+				120: {desc: "Ambiv", style: ["devotion", "ambivalent"]},
+				150: {desc: "Accept", style: ["devotion", "accept"]},
+				195: {desc: "Devo", style: ["devotion", "devoted"]},
+				200: {desc: "Wor", style: ["devotion", "worship"]}
+			},
+			trust: { // first key: trust + 100, second key: devotion + 100
+				4: {desc: "ETerr", style: ["trust", "extremely-terrified"]},
+				49: {desc: "Terr", style: ["trust", "terrified"]},
+				79: {desc: "Fright", style: ["trust", "frightened"]},
+				120: {desc: "Fear", style: ["trust", "fearful"]},
+				150: {
+					79: {desc: "Caref", style: ["defiant", "careful"]},
+					200: {desc: "Caref", style: ["trust", "careful"]},
+				},
+				195: {
+					79: {desc: "Bold", style: ["defiant", "bold"]},
+					200: {desc: "Trust", style: ["trust", "trusting"]},
+				},
+				200: {
+					79: {desc: "Defiant", style: ["defiant", "full"]},
+					200: {desc: "VTrust", style: ["trust", "prof-trusting"]},
+				}
+			},
+			education: { // index: intelligenceImplant + 15
+				0: "(e-)",
+				29: "",
+				44: "(e)",
+				99: "(e+)"
+			},
+			intelligence: { // index: intelligence + 100
+				4: {desc: "I---", style: "orangered"},
+				49: {desc: "I--", style: "orangered"},
+				84: {desc: "I-", style: "orangered"},
+				115: {desc: "I"},
+				150: {desc: "I+", style: "deepskyblue"},
+				195: {desc: "I++", style: "deepskyblue"},
+				230: {desc: "I+++", style: "deepskyblue"},
+				999: {desc: "I++++", style: "deepskyblue"},
+			},
+			behavioralFlaw: {
+				"arrogant": "Arrog",
+				"bitchy": "Bitchy",
+				"odd": "Odd",
+				"hates men": "Men-",
+				"hates women": "Women-",
+				"gluttonous": "Glut",
+				"anorexic": "Ano",
+				"devout": "Dev",
+				"liberated": "Lib",
+			},
+			sexualFlaw: {
+				"hates oral": {desc: "Oral-", style: "red"},
+				"hates anal": {desc: "Anal-", style: "red"},
+				"hates penetration": {desc: "Fuck-", style: "red"},
+				"shamefast": {desc: "Shame", style: "red"},
+				"idealistic": {desc: "Ideal", style: "red"},
+				"repressed": {desc: "Repre", style: "red"},
+				"apathetic": {desc: "Apath", style: "red"},
+				"crude": {desc: "Crude", style: "red"},
+				"judgemental": {desc: "Judge", style: "red"},
+				"cum addict": {desc: "CumAdd", style: "yellow"},
+				"anal addict": {desc: "AnalAdd", style: "yellow"},
+				"attention whore": {desc: "Attention", style: "yellow"},
+				"breast growth": {desc: "BoobObsess", style: "yellow"},
+				"abusive": {desc: "Abusive", style: "yellow"},
+				"malicious": {desc: "Malice", style: "yellow"},
+				"self hating": {desc: "SelfHatr", style: "yellow"},
+				"neglectful": {desc: "SelfNeglect", style: "yellow"},
+				"breeder": {desc: "BreedObsess", style: "yellow"},
+			},
+			behavioralQuirk: {
+				"confident": "Confid",
+				"cutting": "Cutting",
+				"funny": "Funny",
+				"fitness": "Fit",
+				"adores women": "Women+",
+				"adores men": "Men+",
+				"insecure": "Insec",
+				"sinful": "Sinf",
+				"advocate": "Advoc",
+			},
+			sexualQuirk: {
+				"gagfuck queen": "Gagfuck",
+				"painal queen": "Painal",
+				"strugglefuck queen": "Struggle",
+				"tease": "Tease",
+				"romantic": "Romantic",
+				"perverted": "Perverted",
+				"caring": "Caring",
+				"unflinching": "Unflinch",
+				"size queen": "SizeQ",
+			}
+		},
+		health: {
+			tiredness: {
+				30: {desc: "Ene", style: "green"},
+				60: {desc: "Tir", style: "yellow"},
+				90: {desc: "Tir+", style: "orange"},
+				100: {desc: "Exh", style: ["red", "strong"]}
+			}
+		},
+		accent: {
+			1: {desc: "Acc", style: "pink"},
+			2: {desc: "Acc-"},
+			3: {desc: "Acc--"},
+			4: {desc: "Acc--", style: "red"},
+		},
+		fetish: { // indices [fetish, fetishStrength]
+			"submissive": {
+				60: "Sub",
+				95: "Sub+",
+				100: "Sub++"
+			},
+			"cumslut": {
+				60: "Oral",
+				95: "Oral+",
+				100: "Oral++"
+			},
+			"humiliation": {
+				60: "Humil",
+				95: "Humil+",
+				100: "Humil++"
+			},
+			"buttslut": {
+				60: "Anal",
+				95: "Anal+",
+				100: "Anal++"
+			},
+			"boobs": {
+				60: "Boobs",
+				95: "Boobs+",
+				100: "Boobs++"
+			},
+			"sadist": {
+				60: "Sadist",
+				95: "Sadist+",
+				100: "Sadist++"
+			},
+			"masochist": {
+				60: "Pain",
+				95: "Pain+",
+				100: "Pain++"
+			},
+			"dom": {
+				60: "Dom",
+				95: "Dom+",
+				100: "Dom++"
+			},
+			"pregnancy": {
+				60: "Preg",
+				95: "Preg+",
+				100: "Preg++"
+			},
+			"none" : "Vanilla"
+		},
+		prestige: {
+			0: null,
+			1: {desc: "Prest", style: "green"},
+			2: {desc: "Prest+", style: "green"},
+			3: {desc: "Prest++", style: "green"},
+		},
+		pornPrestige: {
+			0: null,
+			1: {desc: "PPrest", style: "green"},
+			2: {desc: "PPrest+", style: "green"},
+			3: {desc: "PPrest++", style: "green"},
+		},
+		diet: {
+			"restricted": "Di:W-",
+			"fattening": "Di:W+",
+			"corrective": "Di:W=",
+			"XX": "Di:XX+",
+			"XY": "Di:XY+",
+			"XXY": "Di:XXY+",
+			"muscle building": "Di:M+",
+			"slimming": "Di:M-",
+			"cum production": "Di:C+",
+			"cleansing": "Di:H+",
+			"fertility": "Di:F+",
+
+		},
+		specialDiet: { // index: dietCum + 3 * dietMilk
+			0: null,
+			1: "Cum+",
+			2: "Cum++",
+			3: "Milk+",
+			4: "Cum+ Milk+",
+			5: "Cum++ Milk+",
+			6: "Milk++",
+			7: "Cum+ Milk++",
+			8: "Cum++ Milk++"
+		},
+		drugs: {
+			"breast injections": "Boobs+",
+			"intensive breast injections": "Boobs++",
+			"hyper breast injections": "Boobs+++",
+			"nipple enhancers": "Nipple+",
+			"butt injections": "Butt+",
+			"intensive butt injections": "Butt++",
+			"hyper butt injections": "Butt+++",
+			"lip injections": "Lip+",
+			"fertility drugs": "Fert+",
+			"super fertility drugs": "Fert++",
+			"penis enhancement": "Dick+",
+			"intensive penis enhancement": "Dick++",
+			"hyper penis enhancement": "Dick+++",
+			"testicle enhancement": "Balls+",
+			"intensive testicle enhancement": "Balls++",
+			"hyper testicle enhancement": "Balls+++",
+			"psychosuppressants": "Psych-",
+			"psychostimulants": "Psych+",
+			"steroids": "Ster",
+			"female hormone injections": "HormXX++",
+			"male hormone injections": "HormXY++",
+			"hormone enhancers": "Horm+",
+			"hormone blockers": "Horm-",
+			"anti-aging cream": "Age-",
+			"appetite suppressors": "ApSup",
+			"penis atrophiers": "Dick-",
+			"testicle atrophiers": "Balls-",
+			"clitoris atrophiers": "Clit-",
+			"labia atrophiers": "Labia-",
+			"nipple atrophiers": "Nipple-",
+			"lip atrophiers": "Lip-",
+			"breast redistributors": "Breast-",
+			"butt redistributors": "Butt-",
+			"sag-B-gone": "AntiSag",
+			"growth stimulants": "GroStim",
+			"priapism agents": "Erection"
+		},
+		nationality: { // NOTE this dictionary lacks "Zimbabwean" key, which is a spacial case
+			"Afghan": "Afg",
+			"Albanian": "Alb",
+			"Algerian": "Alg",
+			"American": "USA",
+			"Andorran": "And",
+			"Angolan": "Ang",
+			"Antiguan": "AB",
+			"Argentinian": "Arg",
+			"Armenian": "Arm",
+			"Aruban": "Aru",
+			"Australian": "Aus",
+			"Austrian": "Aut",
+			"Azerbaijani": "Aze",
+			"Bahamian": "Bah",
+			"Bahraini": "Bah",
+			"Bangladeshi": "Bgd",
+			"Barbadian": "Bar",
+			"Belarusian": "Ber",
+			"Belgian": "Bel",
+			"Belizean": "Blz",
+			"Beninese": "Ben",
+			"Bermudian": "Bmd",
+			"Bhutanese": "Bhu",
+			"Bissau-Guinean": "GB",
+			"Bolivian": "Bol",
+			"Bosnian": "Bos",
+			"Brazilian": "Bra",
+			"British": "UK",
+			"Bruneian": "Bru",
+			"Bulgarian": "Bul",
+			"Burkinabé": "BF",
+			"Burmese": "Bur",
+			"Burundian": "Bnd",
+			"Cambodian": "Kam",
+			"Cameroonian": "Cam",
+			"Canadian": "Can",
+			"Cape Verdean": "CV",
+			"Catalan": "Cat",
+			"Central African": "CAR",
+			"Chadian": "Cha",
+			"Chilean": "Chl",
+			"Chinese": "Chi",
+			"Colombian": "Col",
+			"Comorian": "Com",
+			"Congolese": "RC",
+			"a Cook Islander": "CI",
+			"Costa Rican": "CR",
+			"Croatian": "Cro",
+			"Cuban": "Cub",
+			"Curaçaoan": "Cur",
+			"Cypriot": "Cyp",
+			"Czech": "Cze",
+			"Danish": "Den",
+			"Djiboutian": "Dji",
+			"Dominican": "DR",
+			"Dominiquais": "Dom",
+			"Dutch": "Nld",
+			"East Timorese": "ET",
+			"Ecuadorian": "Ecu",
+			"Egyptian": "Egy",
+			"Emirati": "UAE",
+			"Equatoguinean": "EG",
+			"Eritrean": "Eri",
+			"Estonian": "Est",
+			"Ethiopian": "Eth",
+			"Fijian": "Fij",
+			"Filipina": "Phl",
+			"Finnish": "Fin",
+			"French": "Fra",
+			"French Guianan": "FG",
+			"French Polynesian": "FP",
+			"Gabonese": "Gab",
+			"Gambian": "Gam",
+			"Georgian": "Geo",
+			"German": "Ger",
+			"Ghanan": "Gha",
+			"Greek": "Gre",
+			"Greenlandic": "Grn",
+			"Grenadian": "Gda",
+			"Guamanian": "Gua",
+			"Guatemalan": "Gtm",
+			"Guinean": "Gui",
+			"Guyanese": "Guy",
+			"Haitian": "Hai",
+			"Honduran": "Hon",
+			"Hungarian": "Hun",
+			"I-Kiribati": "Kir",
+			"Icelandic": "Ice",
+			"Indian": "Ind",
+			"Indonesian": "Idn",
+			"Iranian": "Irn",
+			"Iraqi": "Irq",
+			"Irish": "Irl",
+			"Israeli": "Isr",
+			"Italian": "Ita",
+			"Ivorian": "IC",
+			"Jamaican": "Jam",
+			"Japanese": "Jpn",
+			"Jordanian": "Jor",
+			"Kazakh": "Kaz",
+			"Kenyan": "Ken",
+			"Kittitian": "SKN",
+			"Korean": "Kor",
+			"Kosovan": "Kos",
+			"Kurdish": "Kur",
+			"Kuwaiti": "Kuw",
+			"Kyrgyz": "Kyr",
+			"Laotian": "Lao",
+			"Latvian": "Lat",
+			"Lebanese": "Lbn",
+			"Liberian": "Lib",
+			"Libyan": "Lby",
+			"a Liechtensteiner": "Lie",
+			"Lithuanian": "Lit",
+			"Luxembourgian": "Lux",
+			"Macedonian": "Mac",
+			"Malagasy": "Mad",
+			"Malawian": "Mwi",
+			"Malaysian": "Mys",
+			"Maldivian": "Mdv",
+			"Malian": "Mal",
+			"Maltese": "Mlt",
+			"Marshallese": "MI",
+			"Mauritanian": "Mta",
+			"Mauritian": "Mts",
+			"Mexican": "Mex",
+			"Micronesian": "FSM",
+			"Moldovan": "Mol",
+			"Monégasque": "Mnc",
+			"Mongolian": "Mon",
+			"Montenegrin": "Mng",
+			"Moroccan": "Mor",
+			"Mosotho": "Les",
+			"Motswana": "Bot",
+			"Mozambican": "Moz",
+			"Namibian": "Nam",
+			"Nauruan": "Nau",
+			"Nepalese": "Npl",
+			"New Caledonian": "NC",
+			"a New Zealander": "NZ",
+			"Ni-Vanuatu": "Van",
+			"Nicaraguan": "Nic",
+			"Nigerian": "Nga",
+			"Nigerien": "Ngr",
+			"Niuean": "Niu",
+			"Norwegian": "Nor",
+			"Omani": "Omn",
+			"Pakistani": "Pak",
+			"Palauan": "Plu",
+			"Palestinian": "Pal",
+			"Panamanian": "Pan",
+			"Papua New Guinean": "PNG",
+			"Paraguayan": "Par",
+			"Peruvian": "Per",
+			"Polish": "Pol",
+			"Portuguese": "Por",
+			"Puerto Rican": "PR",
+			"Qatari": "Qat",
+			"Romanian": "Rom",
+			"Russian": "Rus",
+			"Rwandan": "Rwa",
+			"Sahrawi": "Sah",
+			"Saint Lucian": "SL",
+			"Salvadoran": "ES",
+			"Sammarinese": "SM",
+			"Samoan": "Sam",
+			"São Toméan": "STP",
+			"Saudi": "Sau",
+			"Scottish": "Sco",
+			"Senegalese": "Sen",
+			"Serbian": "Srb",
+			"Seychellois": "Sey",
+			"Sierra Leonean": "Sie",
+			"Singaporean": "Sng",
+			"Slovak": "Svk",
+			"Slovene": "Svn",
+			"a Solomon Islander": "SI",
+			"Somali": "Som",
+			"South African": "RSA",
+			"South Sudanese": "SS",
+			"Spanish": "Spa",
+			"Sri Lankan": "Sri",
+			"Sudanese": "Sud",
+			"Surinamese": "Sur",
+			"Swazi": "Swa",
+			"Swedish": "Swe",
+			"Swiss": "Swi",
+			"Syrian": "Syr",
+			"Taiwanese": "Tai",
+			"Tajik": "Taj",
+			"Tanzanian": "Tza",
+			"Thai": "Tha",
+			"Tibetan": "Tib",
+			"Togolese": "Tog",
+			"Tongan": "Ton",
+			"Trinidadian": "TT",
+			"Tunisian": "Tun",
+			"Turkish": "Tur",
+			"Turkmen": "Tkm",
+			"Tuvaluan": "Tuv",
+			"Ugandan": "Uga",
+			"Ukrainian": "Ukr",
+			"Uruguayan": "Uru",
+			"Uzbek": "Uzb",
+			"Vatican": "VC",
+			"Venezuelan": "Ven",
+			"Vietnamese": "Vnm",
+			"Vincentian": "SVG",
+			"Yemeni": "Yem",
+			"Zairian": "DRC",
+			"Zambian": "Zam",
+			"Ancient Chinese Revivalist": "Chi Rev",
+			"Ancient Egyptian Revivalist": "Egy Rev",
+			"Arabian Revivalist": "Ara Rev",
+			"Aztec Revivalist": "Azt Rev",
+			"Edo Revivalist": "Edo Rev",
+			"Roman Revivalist": "Rom Rev",
+			"": "None",
+			"none": "None",
+			"slave": "None",
+			"Stateless": "None"
+		},
+		race: {
+			"white": "C",
+			"asian": "A",
+			"indo-aryan": "I",
+			"latina": "L",
+			"middle eastern": "ME",
+			"black": "B",
+			"pacific islander": "PI",
+			"malay": "M",
+			"amerindian": "AI",
+			"semitic": "S",
+			"southern european": "SE",
+			"mixed race": "MR",
+		},
+		skin: {
+			"pure white": "P. Whi",
+			"extremely fair": "E. Fai",
+			"very fair": "V. Fai",
+			"extremely pale": "E. Pal",
+			"very pale": "V. Pal",
+			"light brown": "L. Br",
+			"dark brown": "D. Br",
+			"light olive": "L. Oli",
+			"dark olive": "D. Oli",
+			"light beige": "L. Bei",
+			"dark beige": "D. Bei",
+			"tan": "Tan",
+			"bronze": "Bron",
+			"ebony": "Ebon",
+			"pure black": "P. Bla",
+			"dark": "Dark",
+			"fair": "Fair",
+			"pale": "Pale"
+		},
+		smartPiercing: {
+			setting: {
+				"off": "SP-",
+				"submissive": "SP:sub",
+				"lesbian": "SP:les",
+				"oral": "SP:oral",
+				"humiliation": "SP:humil",
+				"anal": "SP:anal",
+				"boobs": "SP:boobs",
+				"sadist": "SP:sade",
+				"masochist": "SP:pain",
+				"dom": "SP:dom",
+				"pregnancy": "SP:pregnancy",
+				"vanilla": "SP:vanilla",
+				"all": "SP:all",
+				"none": "SP:none",
+				"monitoring": "SP:monitoring",
+				"men": "SP:men",
+				"women": "SP:women",
+				"anti-men": "SP:anti-men",
+				"anti-women": "SP:anti-women",
+			}
+		},
+		skills: {
+			entertainment: {
+				10: null,
+				29: {desc: "E", style: "aquamarine"},
+				59: {desc: "E+", style: "aquamarine"},
+				99: {desc: "E++", style: "aquamarine"},
+				999: {desc: "E+++", style: "aquamarine"}
+			},
+			sex: { // indices: [sexSkills, hasVagina(0,1)]
+				30: {desc: "S-", style: "aquamarine"},
+				90: {desc: "S+", style: "aquamarine"},
+				120: {desc: "S++", style: "aquamarine"},
+				180: {
+					0: {desc: "Sh++", style: "aquamarine"},
+					1: {desc: "S++", style: "aquamarine"}
+				},
+				9999: {desc: "S++", style: "aquamarine"}
+			},
+			whoring: {
+				10: null,
+				29: {desc: "W", style: "aquamarine"},
+				59: {desc: "W+", style: "aquamarine"},
+				99: {desc: "W++", style: "aquamarine"},
+				999: {desc: "W+++", style: "aquamarine"}
+			},
+			mss: {desc: "MSS", style: "aquamarine"},
+			fighter: {desc: "C", style: "aquamarine"},
+		},
+		sexDrive: {
+			XX: {
+				5: {desc: "XY---", style: "red"},
+				15: {desc: "XY--", style: "red"},
+				35: {desc: "XY-", style: "red"},
+				65: {desc: "XY"},
+				85: {desc: "XY+", style: "green"},
+				95: {desc: "XY++", style: "green"},
+				999: {desc: "XY+++", style: "green"}
+			},
+			XY: {
+				5: {desc: "XX---", style: "red"},
+				15: {desc: "XX--", style: "red"},
+				35: {desc: "XX-", style: "red"},
+				65: {desc: "XX"},
+				85: {desc: "XX+", style: "green"},
+				95: {desc: "XX++", style: "green"},
+				999: {desc: "XX+++", style: "green"}
+			},
+			energy: {
+				20: {desc: "SD--", style: "red"},
+				40: {desc: "SD-", style: "red"},
+				60: {desc: "SD", style: "yellow"},
+				80: {desc: "SD+", style: "green"},
+				95: {desc: "SD++", style: "green"},
+				999: {desc: "Nympho!", style: "green"}
+			},
+			synergy: {
+				omni: {desc: "Omni!", style: "green"},
+				nymphomni: {desc: "Omni+Nympho!!", style: "green"}
+			}
+		}
+	}
+};
diff --git a/saveTools/README.md b/saveTools/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..79d1949239601ec70cb217d59bc89e28a63f5706
--- /dev/null
+++ b/saveTools/README.md
@@ -0,0 +1,69 @@
+# Tool(s) for hacking saved games
+
+The script `fc_edit_save.py` can be used to read or write FC games saved with the "Save to Disk..." option.
+It can convert the compressed files (usually named `free-cities-YYYYMMDD-HHMMSS.save`) to or from JSON format,
+view or modify some variables, search for slaves matching some criteria to view or modify them, etc.
+
+## Dependencies
+
+The script `fc_edit_save.py` requires Python version 3.6 or later.
+
+In addition, it uses a small module called `lzstring`, available via [PyPI](https://pypi.org/project/lzstring/).
+If you do not have it yet, you can install it with `pip3 install lzstring` or simply `pip install lzstring` if
+your default version of Python is already Python 3.x.  You can also install the module manually from PyPI
+without using `pip`, but then you are on your own for copying the files in the right locations.
+
+## Usage
+
+To view the help message summarizing the command-line arguments, try:
+```
+./fc_edit_save.py --help
+```
+
+To view a longer help message including details for each command and a list of examples, try:
+```
+./fc_edit_save.py --long-help
+```
+
+The basic usage consists of:
+
+ * reading an input file (compressed `*.save` file with the option `-i`, or uncompressed JSON file with the
+   option `-I`),
+
+ * optionally modifying it with various commands,
+
+ * then saving the result in some output file (compressed `*.save` file with the option `-o`, or uncompressed
+   JSON file with the option `-O`).
+
+If the options `-i`/`-I` or `-o`/`-O` are not used, then the standard input and standard output will be used, which
+allows this script to be used easily in a pipe including other commands.  If the input is a file, then the option
+`-A` can also be used instead of `-o`/`-O` to set the name of the output file automatically.  Try `--help` and
+`--long-help` for more details.
+
+## Examples
+
+The following examples assume that you already have a saved game in the file `free-cities-20200401-123456.save`.
+
+To convert a compressed FC game file to a human-readable JSON file:
+```
+./fc_edit_save.py -i free-cities-20200104-123456.save -p -O free-cities-20200104-123456.json
+```
+
+To convert a JSON file to a compressed FC game file:
+```
+./fc_edit_save.py -I free-cities-20200104-123456.json -o free-cities-20200104-123456.save
+```
+
+To display the names and current assignments of all smart slaves (with more than 50 in intelligence):
+```
+./fc_edit_save.py -i free-cities-20200104-123456.save -p --get-slaves 'intelligence>50' 'name,assignment'
+```
+
+To ensure that all your slaves have at least DD-cup boobs and change the hair of Miss Lily at random:
+```
+./fc_edit_save.py -i free-cities-20200104-123456.save -A --set-slaves all 'boobs=atleast:900' --set-slaves 'Miss Lily' randomhair -v
+```
+Note that the option `-A` in this example will save the results in the file `free-cities-20200104-123456-cheat.save`.
+The option `-v` will show the changes that are applied to all slaves.
+
+For more examples, try `--long-help`.
diff --git a/saveTools/fc_edit_save.py b/saveTools/fc_edit_save.py
new file mode 100755
index 0000000000000000000000000000000000000000..2fb96f25d87c0e490b3f0971d58347fa48733925
--- /dev/null
+++ b/saveTools/fc_edit_save.py
@@ -0,0 +1,2041 @@
+#!/usr/bin/python3
+"""
+View or modify Free Cities save files in native format or JSON format.
+
+Run this program with --help to see a summary of its options.
+Try also --long-help to see more details and some examples.
+
+This software is released into the public domain (CC0).
+"""
+
+# Written in 2020 by UnwrappedGodiva <UnwrappedGodiva+FC@gmail.com>
+#
+# To the extent possible under law, the author(s) have dedicated all copyright
+# and related and neighboring rights to this software to the public domain
+# worldwide.  This software is distributed without any warranty.  You should
+# have received a copy of the CC0 Public Domain Dedication along with this
+# software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+#
+# In other words, feel free to do whatever you want with this code: modify it,
+# borrow any part of it in your own code, rewrite it in a different language,
+# remove this note and put your name on it, sell it for indecent amounts of
+# money, or redistribute it under any software license.  But there is no
+# warranty.  If it creates a giant black hole that destroys the known universe,
+# do not blame me for it.
+
+# Sanity checks that should be performed on this code on a regular basis:
+#   pylint3 ./fc_edit_save.py
+#   yapf3 --style=Google -d ./fc_edit_save.py
+# See also: http://google.github.io/styleguide/pyguide.html
+# pylint: disable=too-many-lines,too-many-statements,too-many-branches
+
+__version__ = "0.3.0"
+
+import argparse
+import copy
+import fnmatch
+import inspect
+import random
+import json
+import operator
+import os
+import re
+import shutil
+import sys
+import textwrap
+# If you do not have the lzstring module yet, you can install it with:
+# pip3 install lzstring   (format used by Twine/SugarCube save files)
+import lzstring
+
+
+def version_check():
+    """If this code compiles successfully, then the version is good enough."""
+    version = ".".join(map(str, sys.version_info))
+    return f"Format strings require python 3.6 or later.  You have {version}."
+
+
+# The following fields exist only in the PC and can be ignored in slaves.
+# This list was generated from a diff between the PC and a slave around version
+# 1065 and will have to be adjusted as new features are added to the game.
+IGNORE_IN_SLAVES = [
+    "ballsImplant", "counter.birthArcOwner", "counter.birthCitizen",
+    "counter.birthClient", "counter.birthDegenerate", "counter.birthElite",
+    "counter.birthFutaSis", "counter.birthLab", "counter.birthMaster",
+    "counter.birthOther", "counter.birthSelf", "counter.storedCum",
+    "criticalDamage", "degeneracy", "fertDrugs", "forcedFertDrugs", "newVag",
+    "origEye", "physicalImpairment", "pregMood", "refreshment",
+    "refreshmentType", "relationships", "reservedChildren",
+    "reservedChildrenNursery", "rumor", "sexualEnergy", "skill.cumTap",
+    "skill.engineering", "skill.hacking", "skill.medicine", "skill.slaving",
+    "skill.trading", "skill.warfare", "staminaPills", "title"
+]
+
+# The following fields exist only in slaves and can be ignored in the PC.
+# This list was generated from a diff between the PC and a slave, with a few
+# additions for things that might break if they are modified.  Although allowed,
+# it is probably not a good idea to change PC.career and some other fields.
+IGNORE_IN_PC = [
+    "HGExclude", "NCSyouthening", "albinismOverride", "assignment", "attrKnown",
+    "canRecruit", "choosesOwnAssignment", "choosesOwnChastity",
+    "choosesOwnClothes", "clitSetting", "counter.PCChildrenFathered",
+    "counter.PCKnockedUp", "counter.births", "counter.pitKills",
+    "counter.publicUse", "currentRules", "custom.desc", "custom.hairVector",
+    "custom.image", "custom.label", "custom.title", "custom.titleLisp", "death",
+    "devotion", "dietCum", "dietMilk", "effectiveWhoreClass", "fetishKnown",
+    "fuckdoll", "haircuts", "indenture", "indentureRestrictions",
+    "lastWeeksCashIncome", "lastWeeksRepExpenses", "lastWeeksRepIncome",
+    "lifetimeCashExpenses", "lifetimeCashIncome", "lifetimeRepExpenses",
+    "lifetimeRepIncome", "newGamePlus", "oldDevotion", "oldTrust", "onDiet",
+    "origin", "override_Arm_H_Color", "override_Brow_H_Color",
+    "override_Eye_Color", "override_H_Color", "override_Pubic_H_Color",
+    "override_Race", "override_Skin", "porn", "pregControl", "readyProsthetics",
+    "recruiter", "relation", "relationTarget", "relationship",
+    "relationshipTarget", "rivalry", "rivalryTarget", "rudeTitle", "sentence",
+    "sexAmount", "sexQuality", "skill.DJ", "skill.anal", "skill.attendant",
+    "skill.bodyguard", "skill.combat", "skill.entertainer",
+    "skill.entertainment", "skill.farmer", "skill.headGirl", "skill.madam",
+    "skill.matron", "skill.milkmaid", "skill.nurse", "skill.oral",
+    "skill.recruiter", "skill.servant", "skill.stewardess", "skill.teacher",
+    "skill.vaginal", "skill.wardeness", "skill.whore", "skill.whoring",
+    "slaveCost", "subTarget", "toyHole", "training", "trust",
+    "useRulesAssistant", "weekAcquired", "whoreClass"
+]
+
+# Aliases for field names used in --get-slaves.
+FIELD_ALIASES = {
+    "name": ["birthName", "birthSurname", "slaveName", "slaveSurname"],
+    "names": ["birthName", "birthSurname", "slaveName", "slaveSurname"],
+    "parent": ["mother", "father"],
+    "parents": ["mother", "father"]
+}
+
+# Each action can be a list of assignments or other actions that will be
+# expanded recursively.  The documentation of modify_slave() and parse_value()
+# explains how the assignments work and how special values are parsed:
+# "A~B" for a random range, "A|B" for a choice, "atleast:", "atmost:", etc.
+#
+# Improvements to consider:
+# - Change the names of the actions to match the values or ranges described in
+#   the slave variables documentation or the conditions in src/js/assayJS.js.
+# - add/remove piercings, tattoos, brand, ...
+# - add or modify rules for the RA (Here be dragons)
+SLAVE_ACTIONS = {
+    # special roles for the slaves
+    "Attendant": [
+        "blind", "fetish=submissive", "mature", "genius", "femaleorgans",
+        "career=a masseuse|a yoga instructor", "skill.attendant=atleast:200",
+        "clothes=a kimono"
+    ],
+    "Agent": [
+        "mature", "nympho", "fetish=dom", "genius", "career=an arcology owner",
+        "clothes=nice business attire"
+    ],
+    "Bodyguard": [
+        "goodshape", "muscles=96", "verytall", "skill.combat=1",
+        "career=a bodyguard|a boxer|a soldier", "skill.bodyguard=atleast:200",
+        "clothes=a military uniform|a mounty outfit|a police uniform"
+    ],
+    "Concubine": [
+        "prestige=atleast:3", "experienced", "master", "beautiful", "devoted",
+        "genius", "clothes=a ball gown|a succubus outfit|clubslut netting"
+    ],
+    "DJ": [
+        "wife", "blind", "master", "goodshape", "beautiful", "genius",
+        "career=a house DJ|a musician", "skill.DJ=atleast:200",
+        "clothes=a t-shirt and panties|a t-shirt and thong"
+    ],
+    "Farmer": [
+        "wife", "goodshape", "master", "experienced", "behavioralQuirk=funny",
+        "sexualQuirk=caring", "maleorgans", "career=a farmer|a rancher",
+        "skill.farmer=atleast:200", "clothes=Western clothing"
+    ],
+    "HeadGirl": [
+        "rules.living=luxurious", "fetish=dom", "maleorgans", "femaleorgans",
+        "master", "genius", "career=a captain|a director|a Queen",
+        "skill.headGirl=atleast:200", "clothes=a ball gown"
+    ],
+    "Head Girl": ["HeadGirl"],
+    "Headgirl": ["HeadGirl"],
+    "HG": ["HeadGirl"],
+    "Recruiter": [
+        "devoted", "genius", "master", "healthy", "beautiful", "experienced",
+        "rules.living=luxurious", "career=a princess|a spy|a talent scout",
+        "skill.recruiter=atleast:200", "clothes=a fallen nuns habit"
+    ],
+    "Madam": [
+        "wife", "master", "genius", "mature", "maleorgans",
+        "career=a banker|a madam|a pimp", "skill.madam=atleast:200",
+        "clothes=leather pants and pasties"
+    ],
+    "Matron": [
+        "sexualQuirk=caring", "fetish=dom", "mature", "genius",
+        "career=a babysitter|a nanny|a practitioner",
+        "skill.matron=atleast:200", "clothes=a slutty maid outfit|an apron"
+    ],
+    "Milkmaid": [
+        "goodshape", "master", "sexualQuirk=caring", "behavioralQuirk=funny",
+        "maleorgans", "career=a milkmaid|a shepherd",
+        "skill.milkmaid=atleast:200", "clothes=Western clothing"
+    ],
+    "MilkMaid": ["Milkmaid"],
+    "Milk Maid": ["Milkmaid"],
+    "Nurse": [
+        "wife", "fetish=dom", "goodshape", "genius", "beautiful",
+        "career=a doctor|a medic|a nurse|a surgeon", "skill.nurse=atleast:200",
+        "clothes=a slutty nurse outfit"
+    ],
+    "Teacher": [
+        "mature", "genius", "beautiful", "career=a coach|a professor|a teacher",
+        "skill.teacher=atleast:200", "clothes=a schoolgirl outfit"
+    ],
+    "Schoolteacher": ["Teacher"],
+    "Stewardess": [
+        "healthy", "mature", "genius", "nympho", "fetish=dom",
+        "career=a housekeeper|a housesitter", "skill.stewardess=atleast:200",
+        "clothes=a slutty maid outfit"
+    ],
+    "Wardeness": [
+        "wife", "nympho", "fetish=sadist", "maleorgans", "goodshape", "devoted",
+        "career=a police officer|a prison guard", "skill.wardeness=atleast:200",
+        "clothes=a red army uniform|a schutzstaffel uniform"
+    ],
+    "Lurcher": ["maleorgans", "devoted", "goodshape"],
+    # fun combos
+    "perfect": [
+        "healthy", "genius", "devoted", "goodshape", "tall", "master",
+        "behaves", "young"
+    ],
+    "boobdoll": ["healthy", "goodshape", "hugeboobs"],
+    "slut": ["healthy", "goodshape", "tall", "bigboobs", "nympho"],
+    "amazon": ["healthy", "goodshape", "verytall", "muscular", "bigboobs"],
+    "bimbo": [
+        "healthy", "beautiful", "fakeboobs", "fakebutt", "lips=80", "dumb",
+        "clothes=a bimbo outfit"
+    ],
+    "cow": ["hugeboobs", "lactating", "counter.milk=atleast:2000~10000"],
+    "slug": ["nolegs", "vagina=atleast:4", "vaginaLube=atleast:2"],
+    "futa": ["maleorgans", "femaleorgans", "boobs=atleast:500"],
+    "sissy": ["dick=1", "balls=1", "scrotum=1", "trust=-50"],
+    "wellspring": [
+        "young", "healthy", "vagina=3", "anus=3", "ovaries=1", "dick=5",
+        "balls=5", "prostate=1", "lactating", "hugeboobs"
+    ],
+    "onahole": ["femaleorgans", "fetish=mindbroken", "mute", "deaf", "nolimbs"],
+    # health
+    "healthy": [
+        "health.condition=atleast:100", "health.health=atleast:100",
+        "health.illness=0", "health.longDamage=0", "health.shortDamage=0",
+        "health.tired=0", "minorInjury=0", "eye.left.type=1",
+        "eye.left.vision=2", "eye.right.type=1", "eye.right.vision=2",
+        "eyewear=none", "hears=0", "earImplant=0", "earwear=none", "smells=0",
+        "tastes=0", "heels=0", "voice=2", "voiceImplant=0", "electrolarynx=0",
+        "teeth=normal", "vaginaLube=1", "preg=atleast:-1", "chem=0", "addict=0",
+        "burst=0", "hasarms", "haslegs"
+    ],
+    "blind": [
+        "eye.left.type=1", "eye.left.vision=0", "eye.right.type=1",
+        "eye.right.vision=0", "eyewear=none"
+    ],
+    "mute": ["voice=0"],
+    "deaf": ["hears=0"],
+    "hasarms": ["arm.left.type=1", "arm.right.type=1"],
+    "haslegs": ["leg.left.type=1", "leg.right.type=1"],
+    "haslimbs": ["hasarms", "haslegs"],
+    "noarms": ["arm.left=null", "arm.right=null"],
+    "nolegs": ["leg.left=null", "leg.right=null"],
+    "nolimbs": ["noarms", "nolegs"],
+    # intelligence
+    "smart":
+    ["intelligence=atleast:50~100", "intelligenceImplant=30", "accent=0"],
+    "genius": ["intelligence=100", "intelligenceImplant=30", "accent=0"],
+    "dumb": ["intelligence=atmost:-50~-16", "intelligenceImplant=0"],
+    # trust and devotion
+    "devoted": ["trust=100", "oldTrust=100", "devotion=100", "oldDevotion=100"],
+    # body shape and beauty
+    "goodshape": [
+        "weight=0", "muscles=31~50", "waist=atmost:-50~-10", "shoulders=0",
+        "shouldersImplant=0", "hips=0~3", "hipsImplant=0", "boobsImplant=0",
+        "boobsImplantType=none", "butt=1~4", "buttImplant=0",
+        "buttImplantType=none", "face=atleast:50~100", "faceImplant=0",
+        "faceShape=normal|cute|sensual|exotic", "lips=21~70", "lipsImplant=0",
+        "bellySag=0", "belly=0", "bellyImplant=0"
+    ],
+    "beautiful": [
+        "waist=atmost:-95~-30", "lips=41~71", "anus=atmost:2",
+        "vagina=atmost:2", "face=100", "faceShape=exotic", "teeth=normal",
+        "minorInjury=0", "scar={}", "makeup=3", "nails=2", "health.tired=0",
+        "boobShape=perky", "nipples=huge", "muscles=-4",
+        "underArmHStyle=hairless|bald|waxed|shaved",
+        "pubicHStyle=hairless|bald|waxed", "prestige=atleast:3",
+        "porn.prestige=atleast:3"
+    ],
+    "petite": ["height=110~149", "heightImplant=0"],
+    "short": ["height=150~159", "heightImplant=0"],
+    "average": ["height=160~169", "heightImplant=0"],
+    "tall": ["height=170~185", "heightImplant=0"],
+    "verytall": ["height=186~210", "heightImplant=0"],
+    "giant": ["height=atleast:220", "heightImplant=0"],
+    "veryweak": ["muscles=-95~-31"],
+    "weak": ["muscles=-30~-6"],
+    "toned": ["muscles=6~30"],
+    "fit": ["muscles=31~50"],
+    "muscular": ["muscles=51~95"],
+    "maleorgans": [
+        "dick=inrange:2:3", "prostate=atleast:1", "balls=atleast:4",
+        "scrotum=atleast:4", "pubertyXY=1"
+    ],
+    "XYorgans": ["maleorgans"],
+    "femaleorgans": [
+        "vagina=inrange:0:2", "vaginaLube=atleast:1", "ovaries=1",
+        "preg=atleast:-1", "pubertyXX=1"
+    ],
+    "XXorgans": ["femaleorgans"],
+    # boobs
+    "bigboobs": [
+        "boobs=atleast:3000~5100", "boobsImplant=0", "boobsImplantType=none",
+        "boobShape=normal|perky|wide-set|torpedo-shaped", "nipples=cute|huge"
+    ],
+    "fakeboobs": [
+        "boobs=atleast:3000~5100", "boobsImplant=600~1000",
+        "boobsImplantType=fillable", "boobShape=perky", "nipples=huge|fuckable"
+    ],
+    "hugeboobs": [
+        "boobs=atleast:6000~10000", "boobsImplant=0", "boobsImplantType=none",
+        "boobShape=perky|wide-set|torpedo-shaped", "nipples=huge"
+    ],
+    "lactating": [
+        "lactation=1", "lactationDuration=2",
+        "lactationAdaptation=atleast:51~100", "rules.lactation=maintain"
+    ],
+    # butt
+    "fakebutt": ["butt=3", "buttImplant=2", "buttImplantType=fillable"],
+    # hair
+    "randomhair": [
+        "randomhaircolor", "randomhairlength", "randomhairstyle",
+        "randompubichairstyle"
+    ],
+    "copyhaircolor": [
+        "pubicHColor=ref:hColor", "eyebrowHColor=ref:hColor",
+        "underArmHColor=ref:hColor"
+    ],
+    "randomhaircolor": [
+        "hColor=blonde|golden|platinum blonde|strawberry-blonde|copper"
+        "|ginger|red|green|blue|pink|dark brown|brown|auburn|burgundy"
+        "|chocolate brown|chestnut|hazel|black|grey|silver|white",
+        "copyhaircolor"
+    ],
+    "lighthaircolor": [
+        "hColor=blonde|golden|platinum blonde|strawberry-blonde|copper"
+        "|ginger|red|silver|white", "copyhaircolor"
+    ],
+    "darkhaircolor": [
+        "hColor=dark brown|brown|auburn|burgundy|chocolate brown|chestnut"
+        "|hazel|black|grey", "copyhaircolor"
+    ],
+    "funhaircolor": ["hColor=red|green|blue|pink|white", "copyhaircolor"],
+    "randomhairlength": ["hLength=0~150"],
+    "verylonghair": ["hLength=100~149"],
+    "longhair": ["hLength=30~99"],
+    "mediumhair": ["hLength=10~19"],
+    "shoulderhair": ["mediumhair"],
+    "shorthair": ["hLength=0~9"],
+    "randomhairstyle": [
+        "hStyle=shaved|buzzcut|trimmed|afro|cornrows|bun|neat|strip|tails"
+        "|up|ponytail|braided|dreadlocks|permed|curled|luxurious|bald"
+        "|messy bun|messy"
+    ],
+    "randompubicstyle": [
+        "pubicHStyle=hairless|waxed|in a strip|neat|bushy|very bushy"
+        "|bushy in the front and neat in the rear|bald"
+    ],
+    "randompubichairstyle": ["randompubicstyle"],
+    # eyes
+    "randomeyecolor": [
+        "eye.left.iris=blue|black|brown|green|turquoise|sky-blue|hazel"
+        "|pale-grey|white|pink|amber|red", "eye.right.iris=ref:eye.left.iris"
+    ],
+    "lighteyecolor": [
+        "eye.left.iris=green|turquoise|sky-blue|pale-grey",
+        "eye.right.iris=ref:eye.left.iris"
+    ],
+    "darkeyecolor": [
+        "eye.left.iris=black|brown|hazel|amber",
+        "eye.right.iris=ref:eye.left.iris"
+    ],
+    "funeyecolor":
+    ["eye.left.iris=white|pink|red", "eye.right.iris=ref:eye.left.iris"],
+    # age
+    "teen": [
+        "actualAge=10~19", "physicalAge=ref:actualAge",
+        "visualAge=ref:actualAge", "ovaryAge=ref:actualAge", "ageImplant=0"
+    ],
+    "young": [
+        "actualAge=18~25", "physicalAge=ref:actualAge",
+        "visualAge=ref:actualAge", "ovaryAge=ref:actualAge", "ageImplant=0"
+    ],
+    "mature": [
+        "actualAge=36~40", "physicalAge=ref:actualAge",
+        "visualAge=ref:actualAge", "ovaryAge=30", "ageImplant=0"
+    ],
+    # skills and experience
+    "skilled": [
+        "skill.vaginal=atleast:51~100", "skill.oral=atleast:51~100",
+        "skill.anal=atleast:51~100", "skill.whoring=atleast:51~100",
+        "skill.entertainment=atleast:51~100", "skill.combat=1"
+    ],
+    "master": [
+        "skill.vaginal=100", "skill.oral=100", "skill.anal=100",
+        "skill.whoring=100", "skill.entertainment=100", "skill.combat=1"
+    ],
+    "allroles": [
+        "skill.headGirl=100~200", "skill.recruiter=100~200",
+        "skill.bodyguard=100~200", "skill.madam=100~200", "skill.DJ=100~200",
+        "skill.nurse=100~200", "skill.teacher=100~200",
+        "skill.attendant=100~200", "skill.matron=100~200",
+        "skill.stewardess=100~200", "skill.milkmaid=100~200",
+        "skill.farmer=100~200", "skill.wardeness=100~200",
+        "skill.servant=100~200", "skill.entertainer=100~200",
+        "skill.whore=100~200"
+    ],
+    "experienced": [
+        "counter.vaginal=atleast:1000~3000", "counter.anal=atleast:1000~3000",
+        "counter.penetrative=atleast:1500~5000",
+        "counter.oral=atleast:1000~3000", "counter.mammary=atleast:1000~3000"
+    ],
+    # behavior, fetishes, flaws and quirks
+    "behaves": [
+        "energy=atleast:50~100", "attrXX=atleast:50~100",
+        "attrXY=atleast:50~100", "attrKnown=1", "fetishKnown=1",
+        "behavioralFlaw=none", "sexualFlaw=none"
+    ],
+    "nympho": [
+        "energy=100", "attrXX=100", "attrXY=100", "attrKnown=1",
+        "fetishKnown=1", "fetish=none|submissive|boobs|dom|pregnancy",
+        "fetishStrength=100", "behavioralFlaw=none", "sexualFlaw=none",
+        "behavioralQuirk=none|none|none|none|confident|cutting|funny|fitness"
+        "|sinful|advocate",
+        "sexualQuirk=none|none|none|none|tease|romantic|perverted|caring"
+        "|unflinching|size queen"
+    ],
+    # relationships
+    "wife": ["relationship=-3", "relationshipTarget=-1"],
+}
+
+
+class FCBaseError(Exception):
+    """Base class for exceptions defined in this file."""
+
+
+class FCNotFoundError(FCBaseError):
+    """Exception raised when a dotted path leads nowhere."""
+
+
+class FCTypeError(FCBaseError):
+    """Exception raised when a dotted path leads to a wrong type."""
+
+
+class FCParamsError(FCBaseError):
+    """Exception raised when a command-line parameter is incorret."""
+
+
+class FCBadSelectorError(FCParamsError):
+    """Exception raised when a selector returns no results."""
+
+
+class AppendConstAction(argparse.Action):
+    """argparse.Action similar to "append" but using a tuple (`const`, values).
+    """
+
+    def __init__(self, option_strings, dest, nargs=None, const=None, **kwargs):
+        if nargs is None:
+            raise ValueError("nargs must be supplied")
+        if const is None:
+            raise ValueError("const must be supplied")
+        super(AppendConstAction, self).__init__(
+            option_strings, dest, nargs=nargs, const=const, **kwargs)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        items = getattr(namespace, self.dest, None)
+        if items is None:
+            items = []
+        else:
+            items = items[:]
+        items.append((self.const, values))
+        setattr(namespace, self.dest, items)
+
+
+class CommandGroupContainer:
+    """Wrapper for an ArgumentParser that adds actions in a list."""
+
+    def __init__(self, arg_parser=None, dest=None, action=AppendConstAction):
+        if arg_parser is None:
+            raise ValueError("arg_parser must be supplied")
+        if not callable(getattr(arg_parser, "add_argument", None)):
+            raise ValueError("arg_parser does not look like an ArgumentParser")
+        if dest is None:
+            raise ValueError("dest must be supplied")
+        self.arg_parser = arg_parser
+        self.dest = dest
+        self.action = action
+        self.help = []
+
+    def command_from_docstring(self, func):
+        """Registers a function in the ArgumentParser based on its docstring."""
+        if func is None:
+            raise ValueError("func must be supplied")
+        if not func.__doc__:
+            message = "cannot register function {} without docstring"
+            raise ValueError(message.format(func.__name__))
+        options = []
+        arguments = []
+        help_lines = []
+        long_help_lines = None
+        re_command = re.compile(r'^\s+Command:\s+(.+?)\s*$')
+        for line in func.__doc__.splitlines():
+            if not options:
+                re_match = re_command.match(line)
+                if re_match:
+                    for arg in re_match.group(1).split():
+                        if arg.startswith("-") and not arguments:
+                            options.append(arg)
+                        else:
+                            arguments.append(arg)
+            elif long_help_lines is None:
+                if line and not line.isspace():
+                    help_lines.append(line)
+                else:
+                    long_help_lines = []
+            else:
+                long_help_lines.append(line)
+        if not options:
+            message = "cannot find 'Command:' in docstring for function '{}'"
+            raise ValueError(message.format(func.__name__))
+        # Compare the function signature with its docstring here to catch
+        # errors early instead of waiting until the function is called.
+        expected_args = len(inspect.signature(func).parameters) - 2
+        if expected_args != len(arguments):
+            message = ("function '{}' expects {} arguments after save_obj but"
+                       " its docstring shows {}")
+            raise ValueError(
+                message.format(func.__name__, expected_args, len(arguments)))
+        # Now register this function, its help text, and number of arguments.
+        short_help = textwrap.dedent("\n".join(help_lines)).strip()
+        self.arg_parser.add_argument(
+            *options,
+            action=self.action,
+            const=func,
+            dest=self.dest,
+            nargs=len(arguments),
+            metavar=tuple(arguments),
+            help=short_help)
+        # Save the long help texts.  It would be nicer to store this in the
+        # ArgumentParser and retrieve it later, but I could not find an
+        # elegant way to do that without using its private classes.  The
+        # handling of long help texts and the method format_long_help()
+        # should probably be rewritten.
+        long_help = textwrap.dedent("\n".join(long_help_lines)).strip()
+        self.help.append([options, arguments, short_help, long_help])
+
+    def format_long_help(self):
+        """Returns a list of long help texts for all commands."""
+        help_sections = []
+        for help_list in self.help:
+            for opt in help_list[0]:
+                if opt.startswith("--"):
+                    break
+            else:
+                opt = help_list[0][0]
+            help_sections.append("\n".join([
+                " ".join([opt] + help_list[1]), help_list[2], "", help_list[3]
+            ]))
+        return help_sections
+
+
+class ParagraphWrapper:
+    """Wrapper for TextWrapper, formatting text and lists in paragraphs.
+
+    This class splits the text in paragraphs separated by blank lines.  Each
+    paragraph is wrapped separately.  If a paragraph starts with "* " or "- ",
+    it is considered as a list: the lines following the first one will be
+    indented by two spaces.
+
+    If the text is already indented (e.g., in a docstring), it will first be
+    de-indented (using textwrap.dedent()) before the line wrapping is done.
+    Additional blank lines before or after the text will be stripped.
+    """
+
+    def __init__(self, width=70, indent=None):
+        if indent is None:
+            indent = ""
+        elif isinstance(indent, int):
+            indent = " " * indent
+        self.wrapper = textwrap.TextWrapper(
+            width=width,
+            initial_indent=indent,
+            subsequent_indent=indent,
+            fix_sentence_endings=True,
+            break_long_words=False,
+            break_on_hyphens=False)
+        self.wrapper_list = textwrap.TextWrapper(
+            width=width,
+            initial_indent=indent,
+            subsequent_indent=indent + "  ",
+            fix_sentence_endings=True,
+            break_long_words=False,
+            break_on_hyphens=False)
+
+    def format_paragraphs(self, text):
+        """Wraps `text` to the specified width, preserving paragraph breaks."""
+        return "\n\n".join([
+            self.wrapper_list.fill(para) if para.startswith("* ") or
+            para.startswith("- ") else self.wrapper.fill(para)
+            for para in textwrap.dedent(text).strip().split("\n\n")
+        ])
+
+
+def deepmerge(dst, src, conflict_fail=False, list_skip_none=True):
+    """Recursively merges the content of src into dst, modifying it as needed.
+
+    This is function is designed to merge objects converted to and from JSON
+    so it handles dictionaries, lists and scalar objects but not sets nor
+    other classes.
+
+    FIXME: obsolete description
+    If both objects are dictionaries, then their keys are merged recursively.
+    If both objects are lists, then the elements of src replace those of dst,
+    unless an element of src is None and list_skip_none is True.  In all other
+    cases src replaces dst, except if conflict_fail is True in which case a
+    TypeError exception is thrown.
+
+    After writing this code, I found a more complete (and more complex)
+    implementation of JSON deep merge: https://github.com/avian2/jsonmerge
+    but I think that the short code included here is good enough for what we
+    need to do with the subset of JSON used in Twine/SugarCube save files.
+
+    Args:
+        dst: The object to be modified.
+        src: The object from which the data will be copied.
+        conflict_fail: If True, an exception is thrown if src and dest are not
+            both dictionaries or lists.
+        list_skip_none: If True and both src and dst are lists, then the
+            elements of src that are None will be skipped.  If False and both
+            src and dst are lists, then all elements of src will replace those
+            of dst even if they are None.
+
+    Returns:
+        The results of the merge, which may be dst (modified) or src.
+
+    Raises:
+        FCTypeError: If src and dst are not both dictionaries or lists and if
+            conflict_fail is True.
+    """
+    if isinstance(dst, dict) and isinstance(src, dict):
+        for key in src:
+            if key in dst:
+                dst[key] = deepmerge(
+                    dst[key],
+                    src[key],
+                    conflict_fail=conflict_fail,
+                    list_skip_none=list_skip_none)
+            else:
+                dst[key] = src[key]
+        return dst
+    if isinstance(dst, list) and isinstance(src, list):
+        for i in range(min(len(src), len(dst))):
+            if not list_skip_none or src[i] is not None:
+                dst[i] = deepmerge(
+                    dst[i],
+                    src[i],
+                    conflict_fail=conflict_fail,
+                    list_skip_none=list_skip_none)
+        if len(src) > len(dst):
+            dst.extend(src[len(dst):])
+        return dst
+    if ((dst is None or isinstance(dst, (str, int, float, bool))) and
+            (src is None or isinstance(src, (str, int, float, bool)))):
+        return src
+    if conflict_fail:
+        raise FCTypeError(f"Cannot merge {src.__class__.__name__} into "
+                          "{dst.__class__.__name__}")
+    return src
+
+
+def deepfind(src,
+             regex_key=None,
+             regex_val=None,
+             path=None,
+             callback=None,
+             context=None):
+    """Searches recursively in `src` for a matching key name or value."""
+    found = 0
+    if path is None:
+        path = []
+    if isinstance(src, str):
+        if regex_val is not None and regex_val.search(src):
+            if callback:
+                callback(path, src, context)
+            found += 1
+    elif isinstance(src, dict):
+        for key in src:
+            path.append(key)
+            if regex_key is not None and regex_key.search(key):
+                if callback:
+                    callback(path, src[key], context)
+                found += 1
+            found += deepfind(
+                src[key],
+                regex_key=regex_key,
+                regex_val=regex_val,
+                path=path,
+                callback=callback,
+                context=context)
+            path.pop()
+    elif isinstance(src, list):
+        # pylint: disable=consider-using-enumerate
+        for idx in range(len(src)):
+            path.append(idx)
+            found += deepfind(
+                src[idx],
+                regex_key=regex_key,
+                regex_val=regex_val,
+                path=path,
+                callback=callback,
+                context=context)
+            path.pop()
+    return found
+
+
+def dotted_to_str(path):
+    """Converts a list of strings and ints into a Javascript-like dotted path.
+
+    This is the opposite of parse_dotted().
+    """
+    return ".".join([
+        f"[{part}]" if isinstance(part, int) else part for part in path
+    ]).replace(".[", "[")
+
+
+def parse_dotted(dotted_path):
+    """Converts a Javascript-like dotted path to a list of strings and ints
+
+    For example, the path "foo.bar[42][0].baz" will be decoded as a list
+    ["foo", "bar", 42, 0, "baz"]
+    """
+    if dotted_path == "":
+        return []
+    re_idx = re.compile(r'^(.+)\[(\d+)\]$')
+    plist = []
+    for part in dotted_path.split("."):
+        rlist = []
+        re_idx_match = re_idx.match(part)
+        while re_idx_match:
+            part = re_idx_match.group(1)
+            rlist.append(int(re_idx_match.group(2)))
+            re_idx_match = re_idx.match(part)
+        plist.append(part)
+        plist.extend(reversed(rlist))
+    return plist
+
+
+def get_dotted(src, dotted_path, null_parent_ok=False):
+    """Returns the object found at `dotted_path` in `src`."""
+    # pylint: disable=too-many-branches
+    plist = parse_dotted(dotted_path)
+    obj = src
+    done = "."
+    for part in plist:
+        if obj is None:
+            if null_parent_ok:
+                return None
+            raise FCNotFoundError(f"cannot get field '{part}' from a null "
+                                  f"object: '{done}'")
+        if isinstance(part, int):
+            if not isinstance(obj, list):
+                raise FCTypeError(f"cannot get index {part} from object that "
+                                  f"is not a list: '{done}'")
+            elif part >= len(obj):
+                raise FCNotFoundError(f"cannot get index {part} from list of "
+                                      f"length {len(obj)}: '{done}'")
+            else:
+                obj = obj[part]
+                done = done + "[" + str(part) + "]"
+        else:
+            if not isinstance(obj, dict):
+                raise FCTypeError(f"cannot get field '{part}' from a list: "
+                                  f"'{done}'")
+            elif not part in obj:
+                raise FCNotFoundError(f"field '{part}' not found in '{done}'")
+            else:
+                obj = obj[part]
+                if done == ".":
+                    done = part
+                else:
+                    done = done + "." + part
+    return obj
+
+
+def create_dotted(dotted_path, value):
+    """Creates an object that contains `value` at `dotted_path`."""
+    plist = parse_dotted(dotted_path)
+    obj = value
+    for part in reversed(plist):
+        if isinstance(part, int):
+            new_list = []
+            for _ in range(part):
+                new_list.append(None)
+            new_list.append(obj)
+            obj = new_list
+        else:
+            new_dict = {}
+            new_dict[part] = obj
+            obj = new_dict
+    #print(f"create_dotted({dotted_path}, {value}): {obj}")
+    return obj
+
+
+def set_dotted(src, dotted_path, value, verbosity=None, conflict_fail=True):
+    """Changes the value of `dotted_path` in `src` to `value`."""
+    if verbosity:
+        old_value = get_dotted(src, dotted_path, null_parent_ok=True)
+        if value == old_value:
+            if verbosity >= 2:
+                print(" - {} = {}".format(dotted_path, value))
+        else:
+            if verbosity >= 1:
+                print(" - {} changed from {} to {}".format(
+                    dotted_path, old_value, value))
+    return deepmerge(
+        src, create_dotted(dotted_path, value), conflict_fail=conflict_fail)
+
+
+def top_path_aliases(dotted_path):
+    """Substitutes the aliases $V.*, $PC.*, slaves[*] in a top-level path."""
+    rematch = re.match(r'^\$?V\.(\w.*)$', dotted_path)
+    if rematch:
+        dotted_path = "state.delta[0].variables." + rematch.group(1)
+    else:
+        rematch = re.match(r'^slaves?(\[\d.*)$', dotted_path)
+        if rematch:
+            dotted_path = "state.delta[0].variables.slaves" + rematch.group(1)
+        else:
+            rematch = re.match(r'^\$?PC\.(\w.*)$', dotted_path)
+            if rematch:
+                dotted_path = "state.delta[0].variables.PC." + rematch.group(1)
+    return dotted_path
+
+
+def parse_value(value, context_obj, path, ref_aliases=False):
+    """Parses the string `value`, which may refer to `path` in `context_obj`.
+
+    See also the documentation for command_set(), which gives more details.
+    """
+    # pylint: disable=no-member,too-many-branches
+    # if value starts with "string:", return it as is without processing
+    if value.startswith("string:"):
+        return value[7:]
+    # pick a random sub-expression if any "|" separators are present
+    if "|" in value:
+        value = random.choice(value.split("|"))
+    # check for prefixes requiring a comparison with the old value
+    compare_old = None
+    if value.startswith("atleast:"):
+        value = value[8:]
+        compare_old = "max"
+    elif value.startswith("atmost:"):
+        value = value[7:]
+        compare_old = "min"
+    elif value.startswith("inrange:"):
+        value = value[8:]
+        compare_old = "range"
+    # special values: A~B (random range), JSON values, or path to other value
+    if "~" in value:
+        vmin, vmax = value.split("~", 1)
+        value = random.randint(int(vmin), int(vmax))
+    elif re.match(r'^-?\d+$', value):
+        value = int(value)
+    elif re.match(r'^-?\d?\.\d+$', value):
+        value = float(value)
+    elif value == "null":
+        value = None
+    elif value == "true":
+        value = True
+    elif value == "false":
+        value = False
+    elif ((value.startswith("{") and value.endswith("}")) or
+          (value.startswith("[") and value.endswith("]"))):
+        value = json.loads(value)
+    elif value.startswith("ref:"):
+        if ref_aliases:
+            value = get_dotted(
+                context_obj, top_path_aliases(value[4:]), null_parent_ok=True)
+        else:
+            value = get_dotted(context_obj, value[4:], null_parent_ok=True)
+    # now that we have the value, compare it with the old value if required
+    if compare_old is not None:
+        old_value = get_dotted(context_obj, path)
+        if compare_old == "max":
+            value = max(int(value), int(old_value))
+        elif compare_old == "min":
+            value = min(int(value), int(old_value))
+        elif compare_old == "range":
+            vmin, vmax = value.split(":", 1)
+            value = min(int(vmax), max(int(vmin), int(old_value)))
+        else:
+            raise RuntimeError("invalid compare_old")
+    return value
+
+
+def fnmatch_slave_name(slave, name):
+    """Returns true if the `slave` matches the `name` (with shell wildcards)."""
+    field_names = ["slaveName", "slaveSurname", "birthName", "birthSurname"]
+    regex = re.compile(fnmatch.translate(name))
+    for field in field_names:
+        if field in slave and slave[field] and regex.match(slave[field]):
+            return True
+    return False
+
+
+def select_slaves(slaves, selector, show_fields=None):
+    """Returns a list of slaves matching `selector`.
+
+    The `selector` is usually a comma-separated list of conditions but it also
+    accepts as special cases "all" or "*" to select all slaves, or square
+    brackets including a number or a list of numbers representing a list of
+    indexes in the `slaves` array.
+
+    The standard conditions can be just a number matching a slave ID, or a
+    slave name (with shell wildcards), or a comparison between a variable
+    given by its dotted path and a scalar value (integer or string).
+
+    Note that `show_fields` is modified if present.  Tt should be a set that
+    will get additional elements for each dotted path found in the `selector`.
+    """
+    if show_fields is None:
+        show_fields = set()
+    selected = []
+    # "all" or "All" or "*" => select all slaves
+    if selector in {"all", "All", "*"}:
+        return slaves
+    # number or list of numbers in square brackets => array indexes
+    rematch = re.match(r'^\[(\d+[,\d\s]*)\]$', selector)
+    if rematch:
+        for idx in re.split(r',\s*', rematch.group(1)):
+            selected.append(slaves[int(idx)])
+        return selected
+    # list of comparisons (field=value, field<value, ...) or slave IDs or names
+    for slave in slaves:
+        for expr in re.split(r',\s*', selector):
+            # if the condition is just a number, try to match a slave ID
+            nummatch = re.match(r'^(\d+)$', expr)
+            if nummatch and slave["ID"] == int(nummatch.group(1)):
+                selected.append(slave)
+                show_fields.add("ID")
+                break
+            # try different comparison operators: <=, <, >=, >, !=, ==, =
+            for op_str, op_class, op_func in [
+                    ("<=", int, operator.le),
+                    ("<", int, operator.lt),
+                    (">=", int, operator.ge),
+                    (">", int, operator.gt),
+                    ("!=", str, operator.ne),
+                    ("==", str, operator.eq),
+                    ("=", str, operator.eq),
+            ]:
+                rematch = re.match(rf'^(.+?)\s*{op_str}\s*(.+)$', expr)
+                if rematch:
+                    break
+            else:
+                op_func = None
+            if op_func:
+                if op_func(
+                        op_class(get_dotted(slave, rematch.group(1))),
+                        op_class(rematch.group(2))):
+                    selected.append(slave)
+                    show_fields.add(rematch.group(1))
+                    break
+                else:
+                    continue
+            # else try to match a slave name (shell wildcards allowed)
+            if fnmatch_slave_name(slave, expr):
+                selected.append(slave)
+                show_fields.add("name")
+                break
+    # return what was found (maybe nothing)
+    return selected
+
+
+def expand_action(actions_dict, action):
+    """Returns the expanded `action` from `actions_dict`, recursively."""
+    actions_list = []
+    for subaction in actions_dict[action]:
+        if subaction in actions_dict:
+            actions_list += expand_action(actions_dict, subaction)
+        elif "=" in subaction:
+            actions_list.append(subaction)
+        else:
+            raise FCParamsError("unknown action: '{}' used in "
+                                "'{}'".format(subaction, action))
+    return actions_list
+
+
+def modify_slave(slave, actions_list, verbosity=None, ignore=None):
+    """Modifies a slave or PC according to a list of actions.
+
+    Each action can be a list of assignments or other actions that will be
+    expanded recursively.  Assignments must contain an equal sign ("=") and
+    will assign a new value to a field in the current slave or PC.  Special
+    values are recognized by parse_value().
+
+    Args:
+        slave: The slave or PC to be modified.
+        actions_list: The list of modifications to be applied.
+        verbose: If true, print the list of changes.
+        ignore: List of fields that should be ignored if found in an assignment.
+    """
+    if verbosity and verbosity >= 1:
+        if slave["slaveName"]:
+            print(f'Actions for slave {slave["ID"]} "{slave["slaveName"]}":'
+                  f' {actions_list}')
+        else:
+            print(f'Actions for slave {slave["ID"]}: {actions_list}')
+    if ignore is None:
+        ignore = []
+    assignments = []
+    for action in actions_list:
+        if action in SLAVE_ACTIONS:
+            assignments += expand_action(SLAVE_ACTIONS, action)
+        elif "=" in action:
+            assignments.append(action)
+        elif action == "":
+            pass
+        else:
+            raise FCParamsError("unknown action: '{}'".format(action))
+    for assignment in assignments:
+        re_eq = re.match(r'^(.+?)\s*=\s*(.+)$', assignment)
+        if re_eq:
+            path = re_eq.group(1)
+            if path in ignore:
+                continue  # skip to the next assignment
+            value = parse_value(re_eq.group(2), slave, path)
+            set_dotted(
+                slave, path, value, verbosity=verbosity, conflict_fail=False)
+        else:
+            raise FCParamsError("invalid assignment: '{}'".format(assignment))
+
+
+def generate_slave_id(game_vars):
+    """Returns the next available slave ID."""
+    # for compatibility, use the same technique as the Javascript code
+    all_slave_ids = []
+    for slist in ["slaves", "tanks", "cribs"]:
+        all_slave_ids += [slave["ID"] for slave in game_vars[slist]]
+    while game_vars["IDNumber"] in all_slave_ids:
+        game_vars["IDNumber"] += 1
+    # ugly way to do the same as "return V.IDNumber++;" in src/js/utilsFC.js
+    game_vars["IDNumber"] += 1
+    return game_vars["IDNumber"] - 1
+
+
+def generate_missing_parent_id(game_vars):
+    """Returns the next available negative slave ID (starting at -10000)."""
+    if game_vars["missingParentID"]:
+        game_vars["missingParentID"] -= 1
+    else:
+        game_vars["missingParentID"] = -10001
+    return game_vars["missingParentID"] + 1
+
+
+def clone_slave(game_vars, orig_slave, same_parents=False):
+    """Clone a slave `orig_slave` and give it a new ID."""
+    new_slave = copy.deepcopy(orig_slave)
+    new_slave["ID"] = generate_slave_id(game_vars)
+    for zero_key in [
+            "bodyswap", "clone", "cloneID", "cumSource", "daughters",
+            "milkSource", "origBodyOwnerID", "preg", "pregType", "pregSource",
+            "pregWeek", "readyOva", "recruiter", "relation", "relationTarget",
+            "relationship", "relationshipTarget", "rivalry", "rivalryTarget",
+            "sisters", "subTarget"
+    ]:
+        if zero_key in new_slave:
+            new_slave[zero_key] = 0
+    for counter in [
+            "PCChildrenFathered", "PCKnockedUp", "births", "birthsTotal",
+            "laborCount", "slavesFathered", "slavesKnockedUp"
+    ]:
+        if counter in new_slave["counter"]:
+            new_slave["counter"][counter] = 0
+    if "origBodyOwner" in new_slave:
+        new_slave["origBodyOwner"] = ""
+    if "womb" in new_slave:
+        new_slave["womb"] = []
+    if same_parents:
+        # Add a sister (mother and father already copied but may need changes).
+        if orig_slave["mother"] == -1:
+            game_vars["PC"]["daughters"] += 1
+        elif orig_slave["mother"] == 0 or orig_slave["mother"] == -2:
+            orig_slave["mother"] = generate_missing_parent_id(game_vars)
+            new_slave["mother"] = orig_slave["mother"]
+        if orig_slave["father"] == -1:
+            game_vars["PC"]["daughters"] += 1
+        elif orig_slave["father"] == 0 or orig_slave["father"] == -2:
+            orig_slave["father"] = generate_missing_parent_id(game_vars)
+            new_slave["father"] = orig_slave["father"]
+        # Adjust the counters of daughters and sisters.
+        for slave in game_vars["slaves"]:
+            if (slave["ID"] == new_slave["mother"] or
+                    slave["ID"] == new_slave["father"]):
+                slave["daughters"] += 1
+            if (slave["mother"] == new_slave["mother"] or
+                    slave["father"] == new_slave["father"] or
+                    slave["mother"] == new_slave["father"] or
+                    slave["father"] == new_slave["mother"]):
+                slave["sisters"] += 1
+                new_slave["sisters"] += 1
+    else:
+        # Clear the family info.
+        new_slave["sisters"] = 0
+        new_slave["mother"] = 0
+        new_slave["father"] = 0
+        new_slave["slaveSurname"] = 0
+        new_slave["birthSurname"] = 0
+    new_slave["slaveName"] = "Clone #{}".format(new_slave["ID"])
+    if new_slave["birthName"]:
+        # Append a number to the birth name, but check if it is not used yet.
+        base_name = new_slave["birthName"]
+        clone_num = 1
+        end_num = re.match(r'^(.+) (\d+)$', base_name)
+        if end_num:
+            base_name = end_num.group(1)
+            clone_num = int(end_num.group(2))
+        for name in [slave["birthName"] for slave in game_vars["slaves"]]:
+            end_num = re.match(rf'^{base_name} (\d+)$', name)
+            if end_num and int(end_num.group(1)) > clone_num:
+                clone_num = int(end_num.group(1))
+        clone_num += 1
+        new_slave["birthName"] = f"{base_name} {clone_num}"
+    new_slave["assignment"] = "rest"
+    if game_vars["JobIDArray"]:
+        if "rest" in game_vars["JobIDArray"]:
+            game_vars["JobIDArray"]["rest"].append(new_slave["ID"])
+        else:
+            game_vars["JobIDArray"]["rest"] = [new_slave["ID"]]
+    if game_vars["slaveIndices"]:
+        max_val = max(game_vars["slaveIndices"].values())
+        game_vars["slaveIndices"][str(new_slave["ID"])] = max_val + 1
+    game_vars["slaves"].append(new_slave)
+    return new_slave
+
+
+# Silence pylint here because many of the commands do not use their `options`.
+# pylint: disable=unused-argument
+
+
+def command_get(save_obj, target, options=None):
+    """Returns the part of `save_obj` that matches the path `target`
+
+    Command: -g --get VAR
+        Get JSON object VAR (shortcuts: V.*, slaves[N].*, PC.*).
+
+        The JSON object matching the dotted path `VAR` will be output.  The
+        following shortcuts are availble:
+
+        - V.* or $V.* replaces state.delta[0].variables.*
+
+        - slaves[N].* replaces state.delta[0].variables.slaves[N].*, where N
+        is the index number of the slave in the slaves array, starting at 0.
+
+        - PC.* or $PC.* replaces state.delta[0].variables.PC.*
+
+        This command should appear last on the command line, otherwise the
+        selected object(s) will be replaced by the next command.
+    """
+    path = top_path_aliases(target)
+    obj = get_dotted(save_obj, path)
+    return obj
+
+
+def command_set(save_obj, changes, options=None):
+    """Applies the assignment(s) in `changes` to `save_obj`.
+
+    Command: -s --set VAR=VAL[,VAR=VAL...]
+        Set JSON object VAR to value VAL.
+
+        Multiple assignments can be separated by commas, as long as they are
+        passed as a single parameter on the command line (using quotes as
+        necessary).
+
+        Note that each dotted path `VAR` is relative to the top level of the
+        game state.  The same shortcuts as for --get are available (V.*,
+        slaves[N].*, PC.*).
+
+        The value `VAR` can be any string or number, with special meanings
+        interpreted in the following order:
+
+        - If the value starts with "string:" then the following string will be
+        used as is, disabling the interpretation of special characters except
+        for the comma "," that always separates the assignments.
+
+        - One or more "|" characters can be used to split the value in a list
+        of sub-strings from which one will be picked at random.
+
+        - If the value starts with "atleast:", "atmost:", or "inrange:", then
+        the following string defines respectively the minimum, maximum, or
+        both (two integers separated by ":") for `VAR`.  The old value of
+        `VAR` will be compared with the limit(s) and will be changed only if
+        necessary.
+
+        - If the value contains two numbers (postive or negative) separated by
+        "~" (tilde), then the result will be picked at random between these
+        two numbers (inclusive).
+
+        - If the value looks like an integer or a floating-point number, then
+        the result will be converted to a number.  You can force a number to
+        be interpreted as a string by prefixing it with "string:".
+
+        - If the value is "null", "true" or "false", then it will be converted
+        to JSON null, true or false.  You can force a special value to be
+        interpreted as a string by prefixing it with "string:".
+
+        - If the value is surrounded by "{" and "}" or by "[" and "]", then it
+        will be interpreted as a JSON object or JSON list.
+        Note: due to a limitation of the implementation, the assignments are
+        split along the commas before the values are parsed, which means that
+        it is currently not possible to supply a JSON object or list
+        containing more than one element.  This can be considered as a bug.  A
+        workaround is to use --json instead of --set.
+
+        - If the value starts with "ref:", then it is interpreted as a
+        reference to another variable.  The shortcuts V.*, PC.* or
+        slave[N].* are allowed in the reference for the --set command but not
+        for --set-slaves.
+
+        The special meanings are interpreted in the order given above as the
+        value is read, which allows some complex combinations.  For example,
+        the value "10~20|ref:some.var|atleast:-5" means that the result will
+        be either an integer between 10 and 20, or a copy of the other
+        variable `some.var`, or will keep the current value as long as it is
+        greater or equal to the minimum -5.  In this example, the choice
+        delimiter "|" is interpreted first and one of the sub-strings is
+        selected at random, then the value of that sub-string is parsed
+        according to the next rules.
+    """
+    if options is not None and options.verbose:
+        verbosity = options.verbose
+    else:
+        verbosity = None
+    for assignment in re.split(r',\s*', changes):
+        try:
+            path, valexpr = assignment.split("=", 1)
+        except ValueError:
+            raise FCParamsError(
+                "argument to --set should be of the form dotted.name=value: " +
+                assignment)
+        path = top_path_aliases(path)
+        value = parse_value(valexpr, save_obj, path, ref_aliases=True)
+        save_obj = set_dotted(save_obj, path, value, verbosity=verbosity)
+    if changes:
+        save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1)
+    return save_obj
+
+
+def command_json(save_obj, jstring, options=None):
+    """Merges the JSON strong `jstring` into `save_obj`.
+
+    Command: -j --json JSON
+        Merge JSON string into JSON output.
+
+        A JSON object (as complex as necessary) will be merged with the
+        current state of the game.  As the merge is done from the top level,
+        the object should probably follow the structure
+        `{"state":{"delta":[{"variables":{...}}]}}` where "..." represents
+        the variable(s) to be modified.
+    """
+    return deepmerge(save_obj, json.loads(jstring))
+
+
+def _print_dotted(path, value, options):
+    """Callback for find key/value: prints the path, not the value."""
+    print(dotted_to_str(path))
+
+
+def _print_dotted_val(path, value, options):
+    """Callback for find key/value: prints the path and abbreviated value."""
+    if options.width:
+        width = options.width
+    else:
+        width = shutil.get_terminal_size().columns
+    path_str = dotted_to_str(path)
+    val_abbr = textwrap.shorten(
+        json.dumps(value, sort_keys=options.sort),
+        width=max(3, width - len(path_str)),
+        placeholder="...")
+    print(f"{path_str} = {val_abbr}")
+
+
+def _print_dotted_val_full(path, value, options):
+    """Callback for find key/value: prints the path and whole value."""
+    val_str = json.dumps(value, sort_keys=options.sort, indent=options.indent)
+    print(f"{dotted_to_str(path)} = {val_str}")
+
+
+def command_find_key(save_obj, key_name, options=None):
+    """Tries to find a key matching `key_name` in `save_obj`.
+
+    Command: --find-key NAME
+        Tries to find a VAR (key) that matches NAME.
+
+        The NAME to search for can be specified as a string with optional
+        shell wildcards ("*", "?") or as a regular expression surrounded by
+        "/.../".
+
+        For each key that matches NAME, the path to that key will be printed.
+        If the verbosity level is at least one (-v), then the value will also
+        be printed but limited to its first 30 characters.  If the verbosity
+        level is at least 2 (-vv), then the whole value will be printed
+        regardless of its length.
+
+        If this option appears last on the command line, the total number of
+        matches will be printed at the end.
+    """
+    re_match = re.match(r"^/(.+)/$", key_name)
+    if re_match:
+        regex_key = re.compile(re_match.group(1))
+    else:
+        regex_key = re.compile("^" + fnmatch.translate(key_name) + "$")
+    if options is None or options.verbose <= 0:
+        callback = _print_dotted
+    elif options.verbose <= 1:
+        callback = _print_dotted_val
+    else:
+        callback = _print_dotted_val_full
+    return deepfind(
+        save_obj, regex_key=regex_key, callback=callback, context=options)
+
+
+def command_find_value(save_obj, value_str, options=None):
+    """Tries to find a string containing `value_str` in `save_obj`.
+
+    Command: --find-value NAME
+        Tries to find a string value that matches NAME.
+
+        The NAME to search for can be specified as a string with optional
+        shell wildcards ("*", "?") or as a regular expression surrounded by
+        "/.../".
+
+        For each key that matches NAME, the path to that key will be printed.
+        If the verbosity level is at least one (-v), then the value will also
+        be printed but limited to its first 30 characters.  If the verbosity
+        level is at least 2 (-vv), then the whole value will be printed
+        regardless of its length.
+
+        If this option appears last on the command line, the total number of
+        matches will be printed at the end.
+    """
+    re_match = re.match(r"^/(.+)/$", value_str)
+    if re_match:
+        regex_val = re.compile(re_match.group(1))
+    else:
+        regex_val = re.compile("^" + fnmatch.translate(value_str) + "$")
+    if options is None or options.verbose <= 0:
+        callback = _print_dotted
+    elif options.verbose <= 1:
+        callback = _print_dotted_val
+    else:
+        callback = _print_dotted_val_full
+    return deepfind(
+        save_obj, regex_val=regex_val, callback=callback, context=options)
+
+
+def command_get_slaves(save_obj, selector, show_vars, options=None):
+    """Returns specific fields from the slaves matching `selector`.
+
+    Command: --get-slaves SELECTOR VAR[,VAR...]
+        Select the slaves matching SELECTOR and output the fields
+        named VAR plus those used for the selection, or "*" to output
+        all fields.
+
+        SELECTOR can be one of the following:
+
+        - "*", "all", or "All" to select all slaves.
+
+        - A condition or comma-separated list of conditions, using one of
+        the numerical comparison operators "<", "<=", ">", ">=", or a string
+        comparison "!=" or "==", or just a number matching a slave ID, or a
+        name matching a slave name, surname, birth name or birth surname
+        (shell wildcards are allowed).  Any slave that matches at least one
+        of the conditions will be selected.  For example,
+        "Miss A*, skill.oral<30" would select Miss Anne and any slave who has
+        less than 30 in the oral skill.
+
+        - In square brackets "[...]", a number or a comma-separated list of
+        numbers.  The numbers will be used as indexes in the slaves array,
+        starting at 0.  As the ordering of the slaves array is difficult to
+        predict, it is usually better to refer to the slaves by their ID
+        or by their name as described in the previous paragraph instead of
+        using their index in square brackets.
+
+        This command should appear last on the command line, otherwise the
+        selected object(s) will be replaced by the next command.
+
+        VAR,[,VAR...] can be one of the following:
+
+        - "" (empty), "*", "all", or "All" to select all fields for each slave
+
+        - A dotted path to a variable such as "eye.left" or "intelligence",
+        or a comma-separated list of them.
+
+        - One of the special aliases "name", "names", "parent", or "parents".
+        "name" or "names" will be replaced by "birthName", "birthSurname",
+        "slaveName", "slaveSurname".  "parent" or "parents" will be replaced
+        by "mother" and "father".
+    """
+    if selector is None:
+        raise RuntimeError("missing selector for --get-slaves command")
+    slaves = get_dotted(save_obj, "state.delta[0].variables.slaves")
+    show_fields = {"ID"}
+    selected = select_slaves(slaves, selector, show_fields=show_fields)
+    if show_vars and show_vars not in {"all", "All", "*"}:
+        for field in re.split(r',\s*', show_vars):
+            show_fields.add(field)
+        for field in FIELD_ALIASES:
+            if field in show_fields:
+                for new_field in FIELD_ALIASES[field]:
+                    show_fields.add(new_field)
+                show_fields.remove(field)
+        filtered = []
+        for slave in selected:
+            out = {}
+            for field in show_fields:
+                set_dotted(out, field, get_dotted(slave, field))
+            filtered.append(out)
+        return filtered
+    return selected
+
+
+def command_set_slaves(save_obj, selector, changes, options=None):
+    """Modifies the slaves matching `selector` according to `changes`.
+
+    Command: --set-slaves SELECTOR VAR=VAL[,VAR=VAL...]
+        Modify all slaves matching SELECTOR and apply the changes VAR=VAL.
+
+        If this command appears last on the command line and the output is
+        uncompressed JSON, then only the selected slaves will be output.
+        Using --set-slaves with an empty list of assignments ("") produces
+        the same results as --get-slaves with the same selector and an empty
+        list of selected fields or "*".
+
+        If the verbosity level is at least 1, then the list of changes will
+        be printed for each slave.  If the verbosity level is at least 2,
+        then the list of variables that are checked but not changed (because
+        their value is already acceptable) will also be printed.
+    """
+    if selector is None:
+        raise RuntimeError("missing selector for set_slaves command")
+    if changes is None:
+        raise RuntimeError("missing assignments for set_slaves command")
+    if options is not None and options.verbose:
+        verbosity = options.verbose
+    else:
+        verbosity = None
+    slaves = get_dotted(save_obj, "state.delta[0].variables.slaves")
+    selected_slaves = select_slaves(slaves, selector)
+    assignments = re.split(r',\s*', changes)
+    for slave in selected_slaves:
+        modify_slave(
+            slave, assignments, verbosity=verbosity, ignore=IGNORE_IN_SLAVES)
+    if changes:
+        save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1)
+    return selected_slaves
+
+
+def _command_copy_or_clone(save_obj,
+                           selector,
+                           changes,
+                           options=None,
+                           same_parents=False):
+    """Implements command_copy_slave or command_clone_slave."""
+    if selector is None:
+        raise RuntimeError("missing selector for copy_slaves command")
+    if changes is None:
+        raise RuntimeError("missing assignments for copy_slaves command")
+    if options is not None and options.verbose:
+        verbosity = options.verbose
+    else:
+        verbosity = None
+    game_vars = get_dotted(save_obj, "state.delta[0].variables")
+    selected_slaves = select_slaves(game_vars["slaves"], selector)
+    if not selected_slaves:
+        raise FCBadSelectorError("no slaves match the selector "
+                                 "'{}'".format(selector))
+    new_slave = clone_slave(
+        game_vars, selected_slaves[-1], same_parents=same_parents)
+    assignments = re.split(r',\s*', changes)
+    modify_slave(
+        new_slave, assignments, verbosity=verbosity, ignore=IGNORE_IN_SLAVES)
+    save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1)
+    return new_slave
+
+
+def command_copy_slave(save_obj, selector, changes, options=None):
+    """Returns a copy of the last slave matching `selector`.
+
+    Command: --copy-slave SELECTOR VAR=VAL[,VAR=VAL...]
+        Copy the last slave matching SELECTOR and apply the changes VAR=VAL.
+
+        A copy of the slave matching SELECTOR (or of the last match if
+        the selector matches multiple slaves) is created and then modified
+        according to the assignments VAR=VAL.  The new clone has no family.
+
+        The new clone is named "Clone #ID" where ID is its ID number.  You
+        can rename the new clone by including "slaveName=NNN" in the list
+        of assignments, where NNN is the new name of the clone.
+
+        The commands --copy-slave and --clone-slave are similar, but
+        --copy-slave erases the family relationships and family name of the
+        clone, so it forgets about its origins.
+    """
+    return _command_copy_or_clone(
+        save_obj, selector, changes, options=options, same_parents=False)
+
+
+def command_clone_slave(save_obj, selector, changes, options=None):
+    """Returns a clone of the last slave matching `selector`.
+
+    Command: --clone-slave SELECTOR VAR=VAL[,VAR=VAL...]
+        Clone the last slave matching SELECTOR and apply the changes VAR=VAL.
+
+        A clone of the slave matching SELECTOR (or of the last match if
+        the selector matches multiple slaves) is created and then modified
+        according to the assignments VAR=VAL.  The new clone is a twin sister
+        of the original slave.
+
+        The new clone is named "Clone #ID" where ID is its ID number.  You
+        can rename the new clone by including "slaveName=NNN" in the list
+        of assignments, where NNN is the new name of the clone.
+
+        The commands --copy-slave and --clone-slave are similar, but
+        --clone-slave creates the clone as a sister of the original slave,
+        keeping the same family name.  The number of daughters and sisters
+        of the other family members are updated accordingly.
+
+        The clone will have the same age as the original slave so it will be
+        her twin sister, but you can change that by assigining a different
+        age to the clone or using one of the shortcut actions "teen", "young",
+        or "mature".
+    """
+    return _command_copy_or_clone(
+        save_obj, selector, changes, options=options, same_parents=True)
+
+
+def command_get_pc(save_obj, show_vars, options=None):
+    """Returns specific fields from the player character.
+
+    Command: --get-pc VAR[,VAR...]
+        Output the fields of the PC named VAR, or "*" to output all fields.
+
+        This command should appear last on the command line, otherwise the
+        selected object(s) will be replaced by the next command.
+    """
+    player = get_dotted(save_obj, "state.delta[0].variables.PC")
+    show_fields = set()
+    if show_vars and show_vars not in {"all", "All", "*"}:
+        for field in re.split(r',\s*', show_vars):
+            show_fields.add(field)
+        for field in FIELD_ALIASES:
+            if field in show_fields:
+                for new_field in FIELD_ALIASES[field]:
+                    show_fields.add(new_field)
+                show_fields.remove(field)
+        out = {}
+        for field in show_fields:
+            set_dotted(out, field, get_dotted(player, field))
+        return out
+    return player
+
+
+def command_set_pc(save_obj, changes, options=None):
+    """Applies some `changes` to the player character.
+
+    Command: --set-pc VAR=VAL[,VAR=VAL...]
+        Modify the player character according to the changes VAR=VAL.
+
+        This is similar to --set-slaves, but for the player character.  The
+        same shortcut actions (see --list-actions) can be used for the PC, but
+        the ones modifying fields that do not exist in the PC will be silently
+        ignored.
+
+        If this command appears last on the command line and the output is
+        uncompressed JSON, then only the PC object will be output.
+    """
+    if changes is None:
+        raise RuntimeError("missing assignments for set_pc command")
+    if options is not None and options.verbose:
+        verbosity = options.verbose
+    else:
+        verbosity = None
+    player = get_dotted(save_obj, "state.delta[0].variables.PC")
+    assignments = re.split(r',\s*', changes)
+    modify_slave(player, assignments, verbosity=verbosity, ignore=IGNORE_IN_PC)
+    if changes:
+        save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1)
+    return player
+
+
+def command_top_up(save_obj, options=None):
+    """Tops up the cach and reputation.
+
+    Command: --top-up --topup
+        Maximize cash (100M) and reputation (20k).
+
+        This is a quick way to get a ridiculously high amount of money and
+        unlock all features of the arcology without having to click 1000 times
+        on "Add 100000 money" in cheat mode.  Of course you will be flagged as
+        a cheater if you use this command or any other command that modifies
+        the state of the game.
+    """
+    return command_set(
+        save_obj, "V.cash=100000000, V.rep=20000", options=options)
+
+
+# pylint: enable=unused-argument
+
+
+def list_actions(width=None, verbosity=0):
+    """Prints the list of shortcuts ("actions") for the assignments."""
+    maxkeylen = max([len(key) for key in SLAVE_ACTIONS])
+    if width:
+        if width < max(40, maxkeylen + 20):
+            raise FCParamsError("width {} must be at least "
+                                "{}".format(width, max(40, maxkeylen + 20)))
+    else:
+        width = shutil.get_terminal_size().columns - 2
+    wrapper = textwrap.TextWrapper(
+        width=width,
+        subsequent_indent=(" " * (maxkeylen + 2)),
+        break_long_words=False,
+        break_on_hyphens=False)
+    print("The following actions can replace an assignment VAR=VAL:")
+    if verbosity < 3:
+        print("(You can increase the verbosity level to see more details)")
+    if verbosity >= 1:
+        if verbosity >= 2:
+            actions = {}
+            for key in SLAVE_ACTIONS:
+                actions[key] = expand_action(SLAVE_ACTIONS, key)
+        else:
+            actions = SLAVE_ACTIONS
+        for key in sorted(actions):
+            print(
+                wrapper.fill("{:<{}} {}".format(key, maxkeylen, actions[key])))
+        if verbosity >= 3:
+            all_assignments = []
+            for assignments in actions.values():
+                all_assignments += assignments
+            print("\nThese actions include the following assignments:")
+            for assignment in sorted(set(all_assignments)):
+                print(wrapper.fill(assignment))
+    else:
+        for key in sorted(SLAVE_ACTIONS):
+            print(key)
+
+
+def read_game_file(infile, compressed=True):
+    """Reads and returns the optionally compressed JSON data from infile."""
+    if compressed:
+        b64_data = infile.read()
+        json_string = lzstring.LZString().decompressFromBase64(b64_data)
+    else:
+        json_string = infile.read()
+    parsed_json = json.loads(json_string)
+    return parsed_json
+
+
+def write_game_file(outfile, obj, compressed=True, indent=None, sort_keys=True):
+    """Writes obj as JSON string in outfile, with optional compression."""
+    if indent is not None and indent > 0:
+        separators = (',', ': ')
+    else:
+        separators = (',', ':')
+    json_string = json.dumps(
+        obj, indent=indent, sort_keys=sort_keys, separators=separators)
+    if compressed:
+        b64_data = lzstring.LZString().compressToBase64(json_string)
+        outfile.write(b64_data)
+    else:
+        outfile.write(json_string)
+        outfile.write("\n")
+
+
+def modify_file_name(orig_name, suffix="-cheat"):
+    """Modifies a file name to insert a suffix before the file extension."""
+    if orig_name is None or orig_name == "-" or orig_name == "<stdin>":
+        raise FCParamsError("the input is not a named file.  Cannot generate "
+                            "output file name")
+    # If the suffix is already present before the file extension, add a number.
+    re_fname = re.match(rf"^(.+{re.escape(suffix)})(\d+)(\..+?)$", orig_name)
+    if re_fname:
+        return "".join([
+            re_fname.group(1),
+            str(int(re_fname.group(2)) + 1),
+            re_fname.group(3)
+        ])
+    re_fname = re.match(rf"^(.+{re.escape(suffix)})(\..+?)$", orig_name)
+    if re_fname:
+        return "".join([re_fname.group(1), "2", re_fname.group(2)])
+    # If there is no suffix but there is a file extension, insert it before.
+    re_fname = re.match(r"^(.+)(\..+?)$", orig_name)
+    if re_fname:
+        return "".join([re_fname.group(1), suffix, re_fname.group(2)])
+    # If the suffix is already present without file exension, add a number.
+    re_fname = re.match(rf"^(.+{re.escape(suffix)})(\d+)$", orig_name)
+    if re_fname:
+        return "".join([re_fname.group(1), str(int(re_fname.group(2)) + 1)])
+    re_fname = re.match(rf"^(.+{re.escape(suffix)})$", orig_name)
+    if re_fname:
+        return "".join([re_fname.group(1), "2"])
+    # Else just append the suffix.
+    return orig_name + suffix
+
+
+def print_long_help(help_sections, width=None, verbosity=0):
+    """Displays the detailed help messages when --long-help is used."""
+
+    def print_title(line):
+        """Prints a line followed by a line of dashes of the same length."""
+        print(line)
+        print("-" * len(line))
+
+    long_help_intro_text = """
+    The commands can appear multiple times on the command line.  They
+    modify the game variables loaded from the input file.
+
+    VAR, VAL, VAR=VAL, and SELECTOR have the following meanings:
+
+    - VAR is a dotted path to an object, such as "arm.left.type" for a
+    slave or "state.delta[0].variables.slaves[0]" from the top level.
+
+    - VAL can be a string, a number, true, false, null, a set of random
+    choices separated by "|", a random number from a range A~B (using "~"),
+    a JSON object or array if it is surrounded by "{}" or "[]", a reference
+    to another variable prefixed with "ref:", or a string without any of
+    these special meanings if prefixed with "string:".  See --set for
+    details.
+
+    - SELECTOR selects one or more slaves by id (number or comma-separated
+    list of numbers), by condition ("intelligence>50" or "genes=XY"), or by
+    name or comma-separated list of names with wildcards (matching name,
+    surname, birth name, or birth surname).  See --get-slaves for details.
+    "*" or "all" selects all slaves.
+
+    - Assignments VAR=VAL can be repeated, separated by commas.  They can
+    also be replaced by actions that are shortcuts to a list of assignments.
+    Use --list-actions to see the list of shortcuts and their expansion.
+
+    The commands --get, --get-slaves and --get-pc are only useful if they
+    appear last on the command line and if the output is not compressed.
+    In that case, only the selected objects will be output in JSON format.
+    Otherwise the output will include the whole game state.
+    """
+    long_examples = [
+        """{prog} -i free-cities-123456.save -p -O free-cities-123456.json
+
+        Loads the file "free-cities-123456.save" (-i ...) and saves its
+        contents as a JSON file (-O ...) with nice indentation (-p).  You can
+        then use any JSON processing tool to analyze the contents of this new
+        file or modify it.  Without the arguments
+        "-O free-cities-123456.json", this command would send the formatted
+        JSON to standard output where it could be directly piped to other
+        commands such as "grep".
+        """,
+        """{prog} -I free-cities-123456.json -o free-cities-123456.save
+
+        Loads the JSON file "free-cities-123456.json" (-I ...) and saves it as
+        a compressed file "free-cities-123456.save" (-o ...).
+        """,
+        """{prog} -i fc-123456.save -p --get-slaves all name,career,assignment
+
+        Selects all slaves from the file "fc-123456.save" and prints their
+        names and surnames, as well as their past career and current
+        assignment.
+        """,
+        """{prog} -i fc-123456.save -A --clone-slave "Miss Lily" "Nurse,slaveName=Doctor Diana,randomhair"
+
+        Creates a twin sister of the slave called "Miss Lily" and modifies the
+        new clone to be an ideal nurse (which makes her your genius, beautiful
+        wife with a dom fetish and career experience as a nurse).  Sets her
+        slave name to be "Doctor Diana" and randomizes her hairstyle and
+        color.  Saves the results in a file called "fc-123456-cheat.save"
+        (option -A with the default suffix "-cheat").
+        """,
+        """{prog} -i fc-123456.save -A --set-slaves "intelligence>50" nympho,bimbo
+
+        Selects all your smart slaves (intelligence>50) and turns them into
+        dumb bimbos with fake boobs and fake butt who are addicted to sex.
+        Saves the results in a file called fc-123456-cheat.save.
+        """,
+        """{prog} -i fc-123456.save --set-slaves all healthy,boobs=atleast:900 --no-output -v
+
+        Selects all slaves and displays what changes would be applied to make
+        them healthy with boobs that are at least DD-cup.  A summary of the
+        changes is displayed (-v) but they are not saved to a file nor shown
+        on standard output (--no-output).  With an additional verbosity level
+        (-vv), this command would also display a detailed list of variables
+        that are checked but not changed if their value is already in the
+        acceptable range.
+        """,
+        """{prog} -i fc-123456.save --suffix=-godmode -A --top-up --set-slaves all perfect,experienced
+
+        Maximizes your money and reputation (--top-up), then modifies all
+        slaves to be healthy geniuses with goddess bodies, perfect skills and
+        experience.  Saves the results in a new file called
+        "fc-123456-godmode.save" (option -A with a custom --suffix).
+        Playing the game would be rather pointless with that extreme cheating
+        but this can be useful for testing some new features.
+        """,
+        """{prog} --list-actions -v
+
+        Lists the shortcuts (actions) for the assignments that can be used
+        with the commands --set-slaves, --copy-slave, --clone-slave, or
+        --set-pc.  With verbosity level 1 (-v), this command shows the
+        lists of assignments or other actions associated with each action.
+        With verbosity level 2 (-vv), these lists would be expanded
+        recursively.  With the default verbosity (no -v), only the names of
+        the actions would be listed.
+        """
+    ]
+    if width:
+        minwidth = 30
+        if width < minwidth:
+            raise FCParamsError(f"width {width} must be at least {minwidth}")
+    else:
+        width = shutil.get_terminal_size().columns - 2
+    print_title("Specifying commands")
+    help_wrapper = ParagraphWrapper(width=width, indent=0)
+    print(help_wrapper.format_paragraphs(long_help_intro_text))
+    print("\n")
+    print_title("List of available commands")
+    indent_wrapper = ParagraphWrapper(width=width, indent=8)
+    for help_section in help_sections:
+        (first_line, _, other_lines) = help_section.partition("\n")
+        print(help_wrapper.format_paragraphs(first_line))
+        print(indent_wrapper.format_paragraphs(other_lines))
+        print("")
+    print_title("Examples")
+    for example in long_examples:
+        (first_line, _,
+         other_lines) = example.replace("{prog}", sys.argv[0]).partition("\n")
+        print(help_wrapper.format_paragraphs(first_line))
+        print(indent_wrapper.format_paragraphs(other_lines))
+        print("")
+
+
+def main():
+    """If you run this program from the command line, you will end up here..."""
+
+    parser = argparse.ArgumentParser(
+        description="View or modify Free Cities save files, compressed or not.",
+        fromfile_prefix_chars="@",
+        epilog="These commands modify the game variables and can appear"
+        " multiple times on the command line.  The commands --get,"
+        " --get-slaves and --get-pc are only useful if they appear last on the"
+        " command line and if the output is not compressed.  See --long-help"
+        " for details.")
+    parser.add_argument(
+        "-H",
+        "--long-help",
+        action="store_true",
+        help="More detailed help and examples.")
+    parser.add_argument(
+        "-V",
+        "--version",
+        action="version",
+        version="%(prog)s " + __version__,
+        help="Show this program's version number and exit.")
+    infile_group = parser.add_mutually_exclusive_group()
+    infile_group.add_argument(
+        "-i",
+        "--infile",
+        "--input",
+        type=argparse.FileType("r"),
+        help="Name of the compressed input file.")
+    infile_group.add_argument(
+        "-I",
+        "--injson",
+        "--input-json",
+        type=argparse.FileType("r"),
+        default=sys.stdin,
+        help="Name of the JSON input file (default: stdin).")
+    outfile_group = parser.add_mutually_exclusive_group()
+    outfile_group.add_argument(
+        "-o",
+        "--outfile",
+        "--output",
+        type=argparse.FileType("w"),
+        help="Name of the compressed output file.")
+    outfile_group.add_argument(
+        "-O",
+        "--outjson",
+        "--output-json",
+        type=argparse.FileType("w"),
+        default=sys.stdout,
+        help="Name of the JSON output file (default: stdout).")
+    outfile_group.add_argument(
+        "-A",
+        "--output-auto",
+        action="store_true",
+        help="Generate the output file name from the input file name by adding"
+        " a suffix to it (see --suffix).  The output will be compressed or"
+        " not, depending on the type of the input.")
+    outfile_group.add_argument(
+        "-n",
+        "--no-output",
+        action="store_true",
+        help="Do not output the JSON data.  Can be used with an increased"
+        " verbosity level to see what changes are applied without saving them.")
+    parser.add_argument(
+        "-a",
+        "--output-all",
+        action="store_true",
+        help="When the output is uncompressed JSON, output the whole game state"
+        " even if only a part of it is selected by the last command.")
+    parser.add_argument(
+        "-v",
+        "--verbose",
+        action="count",
+        default=0,
+        help="Verbosity level when modifying data or for --list-actions.  Can"
+        " be repeated to be more verbose (default: 0).")
+    parser.add_argument(
+        "--width",
+        action="store",
+        type=int,
+        help="Width of the output for --list-actions (default is term width).")
+    parser.add_argument(
+        "--suffix",
+        action="store",
+        type=str,
+        default="-cheat",
+        help="Suffix added to the file name by --output-auto (default:"
+        " '-cheat').  Can be set to an empty string if you want to overwrite"
+        " the input file.  Note: if the suffix starts with a dash, write this"
+        " argument as '--suffix=-mysuffix' and not '--suffix -mysuffix'.")
+    parser.add_argument(
+        "--indent",
+        action="store",
+        type=int,
+        help="Indent the JSON output by that many spaces per level.")
+    parser.add_argument(
+        "--sort", action="store_true", help="Sort the JSON keys.")
+    parser.add_argument(
+        "-p",
+        "--pretty-print",
+        action="store_true",
+        help="Same as --indent 2 --sort.")
+    parser.add_argument(
+        "--list-actions",
+        action="store_true",
+        help="Print the list of known actions (shortcuts) and exit.  With"
+        " increased verbosity levels you can see the list of actions, their"
+        " definitions, the fully expanded definitions, and the list of"
+        " assignments.")
+    parser.add_argument(
+        "--no-taint", action="store_true", help=argparse.SUPPRESS)
+    cmd_arg_group = parser.add_argument_group("optional, repeatable commands")
+    cmd_group = CommandGroupContainer(cmd_arg_group, dest="commands")
+    # Register all functions that declare arguments in their docstring
+    cmd_group.command_from_docstring(command_get)
+    cmd_group.command_from_docstring(command_set)
+    cmd_group.command_from_docstring(command_json)
+    cmd_group.command_from_docstring(command_find_key)
+    cmd_group.command_from_docstring(command_find_value)
+    cmd_group.command_from_docstring(command_get_slaves)
+    cmd_group.command_from_docstring(command_set_slaves)
+    cmd_group.command_from_docstring(command_copy_slave)
+    cmd_group.command_from_docstring(command_clone_slave)
+    cmd_group.command_from_docstring(command_get_pc)
+    cmd_group.command_from_docstring(command_set_pc)
+    cmd_group.command_from_docstring(command_top_up)
+    # And now try to make sense of all this
+    try:
+        args = parser.parse_args()
+
+        if args.long_help:
+            print_long_help(
+                cmd_group.format_long_help(),
+                width=args.width,
+                verbosity=args.verbose)
+            exit(0)
+        if args.list_actions:
+            list_actions(width=args.width, verbosity=args.verbose)
+            exit(0)
+        if args.pretty_print:
+            args.indent = 2
+            args.sort = True
+        if args.output_auto:
+            if args.infile is not None:
+                outname = modify_file_name(args.infile.name, suffix=args.suffix)
+                args.outfile = open(outname, "w")
+            else:
+                outname = modify_file_name(args.injson.name, suffix=args.suffix)
+                args.outjson = open(outname, "w")
+    except FCBaseError as error:
+        print(f"Error in command-line arguments: {error}.")
+        exit(2)
+
+    if args.infile is not None:
+        parsed_json = read_game_file(args.infile, compressed=True)
+    else:
+        parsed_json = read_game_file(args.injson, compressed=False)
+
+    # Execute all commands that were passed on the command line
+    if args.commands:
+        for func, fargs in args.commands:
+            try:
+                result_obj = func(parsed_json, *fargs, options=args)
+            except FCBaseError as error:
+                fname = func.__name__.replace("command_", "").replace("_", "-")
+                if isinstance(error, FCParamsError) and fargs:
+                    print("Error in {}: {} in arguments "
+                          "{}".format(fname, str(error), fargs))
+                else:
+                    print("Error in {}: {}".format(fname, str(error)))
+                exit(2)
+    else:
+        result_obj = parsed_json
+
+    # Unless the --no-taint option has been used, add a variable that marks
+    # the save file as tainted.  This should help when triaging bug reports:
+    # if a tainted save file appears in a bug report, then the user could be
+    # asked to try and reproduce the bug without hacking the state of the
+    # game.
+    if not args.no_taint:
+        set_dotted(parsed_json, "state.delta[0].variables.taintedSaveFile",
+                   "{} v{}".format(os.path.basename(sys.argv[0]), __version__))
+
+    if args.no_output:
+        pass
+    elif args.outfile is not None:
+        write_game_file(
+            args.outfile,
+            parsed_json,
+            compressed=True,
+            indent=args.indent,
+            sort_keys=args.sort)
+    elif args.output_all:
+        write_game_file(
+            args.outjson,
+            parsed_json,
+            compressed=False,
+            indent=args.indent,
+            sort_keys=args.sort)
+    else:
+        write_game_file(
+            args.outjson,
+            result_obj,
+            compressed=False,
+            indent=args.indent,
+            sort_keys=args.sort)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/002-config/fc-version.js b/src/002-config/fc-version.js
index 9be650848c218e931834205c1b94a8380a525fb8..dda732eac5bddf28028fe2ede56ffabe69dd2a0a 100644
--- a/src/002-config/fc-version.js
+++ b/src/002-config/fc-version.js
@@ -1,7 +1,7 @@
 App.Version = {
 	base: "0.10.7.1", // The vanilla version the mod is based off of, this should never be changed.
 	pmod: "3.4.0",
-	release: 1065,
+	release: 1066,
 };
 
 /* Use release as save version */
diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js
index 930e64d4bce2e80a5c1e00c310d766475e390af3..3eb0251be825d75d3e08964f423acf58b7abff69 100644
--- a/src/data/backwardsCompatibility/backwardsCompatibility.js
+++ b/src/data/backwardsCompatibility/backwardsCompatibility.js
@@ -105,6 +105,7 @@ App.Update.backwardsCompatibility = function() {
 		div.append(`Cleaning up... `);
 		jQuery('#backwardsCompatibility').append(div);
 		App.Update.cleanUp(div);
+		App.UI.SlaveSummary.settingsChanged();
 	} catch (error) {
 		div = document.createElement('p');
 		div.className = "red";
@@ -113,7 +114,6 @@ App.Update.backwardsCompatibility = function() {
 		State.restore();
 		throw (error); // rethrow the exception to Sugarcube so we get a fancy stack trace
 	}
-	return;
 };
 
 App.Update.globalVariables = function(node) {
@@ -199,6 +199,13 @@ App.Update.globalVariables = function(node) {
 		if (V.sortSlavesBy === "income" || V.sortSlavesBy === "lastWeeksCashIncome") {
 			V.sortSlavesBy = "weeklyIncome";
 		}
+
+		if (typeof V.abbreviateClothes === "number") {
+			for (const key of ["clothes", "devotion", "diet", "drugs", "genitalia", "health", "hormoneBalance",
+				"mental", "nationality", "origins", "physicals", "race", "rules", "rulesets", "skills"]) {
+				V.UI.slaveSummary.abbreviation[key] = V["abbreviate" + capFirstChar(key)];
+			}
+		}
 	}
 
 	if (typeof V.taitorWeeks !== "undefined") {
diff --git a/src/data/onLoad.js b/src/data/onLoad.js
deleted file mode 100644
index 349c62cad99c864af3de06bcd6c5b18927c90eb3..0000000000000000000000000000000000000000
--- a/src/data/onLoad.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- *
- * @param {object} save
- */
-Config.saves.onLoad = function(save) {
-	// get the old State.variables for easier access.
-	const V = save.state.history[0].variables;
-
-	App.UI.Theme.onLoad(V);
-};
diff --git a/src/debugging/debugJS.js b/src/debugging/debugJS.js
index d2c4dabc29c39f9086ba2f7c25c9ad1f57d7eda9..92f29adeb43750a93d37d3053a083816f3b243e3 100644
--- a/src/debugging/debugJS.js
+++ b/src/debugging/debugJS.js
@@ -121,6 +121,6 @@ App.Debug.dumpGameState = function() {
 
 App.Debug.slaveSummaryText = function(idx) {
 	let span = document.createElement("span");
-	span.appendChild(SlaveSummary(State.variables.slaves[idx]));
+	span.appendChild(App.UI.SlaveSummary.render(State.variables.slaves[idx]));
 	return span.outerHTML;
 };
diff --git a/src/events/intro/introSummary.tw b/src/events/intro/introSummary.tw
index f4663b62525eb58ea43159b44ba9be197235dae6..25f164f96dbd605afa5e7a460a21a47cf22a77d6 100644
--- a/src/events/intro/introSummary.tw
+++ b/src/events/intro/introSummary.tw
@@ -78,7 +78,7 @@ You may review your settings before clicking "Continue" to begin.<br>
 <<if ($economy != 100) || ($seeDicks != 25) || ($continent != "North America") || ($internationalTrade != 1) || ($internationalVariety != 1) || ($seeRace != 1) || ($seeNationality != 1) || ($seeExtreme != 0) || ($seeCircumcision != 1) || ($seeAge != 1) || ($plot != 1)>>
 	| [[restore defaults|Intro Summary][$seeDicks = 25, $economy = 100, $continent = "North America", $internationalTrade = 1, $internationalVariety = 1, $seeRace = 1, $seeNationality = 1, $seeExtreme = 0, $seeCircumcision = 1, $seeAge = 1, $plot = 1]]
 <</if>>
-	| [[Cheat Start|init Nationalities][cashX(1000000, "cheating"), $PC.rules.living = "luxurious",repX(20000, "cheating"), $dojo += 1, $cheatMode = 1, $seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion", $sortSlavesOrder = "descending", $sortSlavesMain = 0, $rulesAssistantMain = 1, $abbreviateDevotion = 1, $abbreviateRules = 1, $abbreviateClothes = 2, $abbreviateHealth = 1, $abbreviateDiet = 1, $abbreviateDrugs = 1, $abbreviateRace = 1, $abbreviateNationality = 1, $abbreviateGenitalia = 1, $abbreviatePhysicals = 1, $abbreviateSkills = 1, $abbreviateMental = 2, $PC.skill.trading = 100, $PC.skill.warfare = 100, $PC.skill.slaving = 100, $PC.skill.engineering = 100, $PC.skill.medicine = 100, $PC.skill.hacking = 100, resetEyeColor($PC)]] //Intended for debugging: may have unexpected effects//
+	| [[Cheat Start|init Nationalities][cashX(1000000, "cheating"), $PC.rules.living = "luxurious",repX(20000, "cheating"), $dojo += 1, $cheatMode = 1, $seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion", $sortSlavesOrder = "descending", $sortSlavesMain = 0, $rulesAssistantMain = 1, $UI.slaveSummary.abbreviation = {devotion: 1, rules: 1, clothes: 2, health: 1, diet: 1, drugs: 1, race: 1, nationality: 1, genitalia: 1, physicals: 1, skills: 1, mental: 2}, $PC.skill.trading = 100, $PC.skill.warfare = 100, $PC.skill.slaving = 100, $PC.skill.engineering = 100, $PC.skill.medicine = 100, $PC.skill.hacking = 100, resetEyeColor($PC)]] //Intended for debugging: may have unexpected effects//
 
 <<set $minimumSlaveAge = variableAsNumber($minimumSlaveAge, 3, 18, 18)>>
 <<set $retirementAge = variableAsNumber($retirementAge, 25, 120, 45)>>
@@ -1362,4 +1362,4 @@ You may review your settings before clicking "Continue" to begin.<br>
 	<</if>>
 
 	</div>
-</div>
\ No newline at end of file
+</div>
diff --git a/src/facilities/farmyard/farmyardReport.tw b/src/facilities/farmyard/farmyardReport.tw
index feb3c95fb00da62e733da16154ee7468a32cbe51..e07eb815b2498f52d88e795fec7775ec6378da9c 100644
--- a/src/facilities/farmyard/farmyardReport.tw
+++ b/src/facilities/farmyard/farmyardReport.tw
@@ -209,13 +209,13 @@
 		<</switch>>
 		/* TODO: should FS with "spare" living rules cause some minor health damage and devotion / trust loss? */
 		<<if ($slaves[$i].health.condition < -80)>>
-			<<run improveCondition($slaves[_FLs], 20)>>
+			<<run improveCondition($slaves[$i], 20)>>
 		<<elseif $slaves[$i].health.condition < -40>>
-			<<run improveCondition($slaves[_FLs], 15)>>
+			<<run improveCondition($slaves[$i], 15)>>
 		<<elseif $slaves[$i].health.condition < 0>>
-			<<run improveCondition($slaves[_FLs], 10)>>
+			<<run improveCondition($slaves[$i], 10)>>
 		<<elseif $slaves[$i].health.condition < 90>>
-			<<run improveCondition($slaves[_FLs], 7)>>
+			<<run improveCondition($slaves[$i], 7)>>
 		<</if>>
 		<<if ($slaves[$i].devotion <= 20) && ($slaves[$i].trust >= -20)>>
 			<<set $slaves[$i].devotion -= 5, $slaves[$i].trust -= 5>>
diff --git a/src/facilities/nursery/nurseryWidgets.js b/src/facilities/nursery/nurseryWidgets.js
index 764f268c1f28f4cbcb89ad0d97ec9624d93e4b71..ce37ddade40cd968014524a9cf417ef04844854f 100644
--- a/src/facilities/nursery/nurseryWidgets.js
+++ b/src/facilities/nursery/nurseryWidgets.js
@@ -14,12 +14,14 @@ App.Facilities.Nursery.InfantSummary = function(child) {
 		r = ``;
 
 	function InfantSummaryUncached(child) {
-		if (V.abbreviateHealth === 1) {
+		/** @type {App.UI.SlaveSummary.AbbreviationState} */
+		const abbreviate = V.UI.slaveSummary.abbreviation;
+		if (abbreviate.health === 1) {
 			// shortHealth(child);
-		} else if (V.abbreviateHealth === 2) {
+		} else if (abbreviate.health === 2) {
 			// longHealth(child);
 		}
-		if (V.abbreviateNationality + V.abbreviateGenitalia + V.abbreviatePhysicals + V.abbreviateSkills + V.abbreviateMental !== 0) {
+		if (abbreviate.nationality + abbreviate.genitalia + abbreviate.physicals + abbreviate.skills + abbreviate.mental !== 0) {
 			r += `<br> `;
 			if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) {
 				r += "&nbsp;&nbsp;&nbsp;&nbsp;";
@@ -40,32 +42,32 @@ App.Facilities.Nursery.InfantSummary = function(child) {
 		}
 		const firstLetter = V.desc.substring(0, 1).toUpperCase();
 		V.desc = firstLetter + V.desc.substring(1);
-		r += `<strong><span class="coral">${V.desc}${V.abbreviatePhysicals === 2? '.' : ''}</span></strong> `;
+		r += `<strong><span class="coral">${V.desc}${abbreviate.physicals === 2? '.' : ''}</span></strong> `;
 		if (V.seeRace) {
 			r += `<span class="tan">`;
-			if (V.abbreviateRace === 1) {
+			if (abbreviate.race === 1) {
 				shortRace(child);
-			} else if (V.abbreviateRace === 2) {
+			} else if (abbreviate.race === 2) {
 				longRace(child);
 			}
 			r += `</span> `;
 		}
-		if (V.abbreviateNationality === 1) {
+		if (abbreviate.nationality === 1) {
 			shortNationality(child);
-		} else if (V.abbreviateNationality === 2) {
+		} else if (abbreviate.nationality === 2) {
 			longNationality(child);
 		}
-		if (V.abbreviatePhysicals === 1) {
+		if (abbreviate.physicals === 1) {
 			shortSkin(child);
 		} else {
 			r += `<span class="pink">${child.skin.charAt(0).toUpperCase() + child.skin.slice(1)} skin.</span> `;
 		}
-		if (V.abbreviatePhysicals === 1) {
+		if (abbreviate.physicals === 1) {
 			shortAge(child);
 			shortFace(child);
 			shortEyes(child);
 			r += `</span> `;
-		} else if (V.abbreviatePhysicals === 2) {
+		} else if (abbreviate.physicals === 2) {
 			longAge(child);
 			longFace(child);
 			longEyes(child);
@@ -75,39 +77,39 @@ App.Facilities.Nursery.InfantSummary = function(child) {
 		if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) {
 			r += "&nbsp;&nbsp;&nbsp;&nbsp;";
 		}
-		if (V.abbreviateSkills === 1) {
+		if (abbreviate.skills === 1) {
 			shortIntelligence(child);
 			shortPrestige(child);
 			shortPornPrestige(child);
-		} else if (V.abbreviateSkills === 2) {
+		} else if (abbreviate.skills === 2) {
 			longIntelligence(child);
 			longPrestige(child);
 			longPornPrestige(child);
 		}
-		if (V.abbreviateMental === 1) {
+		if (abbreviate.mental === 1) {
 			shortBehaviorFlaw(child);
 			shortSexFlaw(child);
-		} else if (V.abbreviateMental === 2) {
+		} else if (abbreviate.mental === 2) {
 			longBehaviorFlaw(child);
 			longSexFlaw(child);
 		}
-		if ((child.relationship !== 0) || (V.abbreviateClothes === 2) || (V.abbreviateRulesets === 2)) {
+		if ((child.relationship !== 0) || (abbreviate.clothes === 2) || (abbreviate.rulesets === 2)) {
 			r += `<br> `;
 			if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) {
 				r += `&nbsp;&nbsp;&nbsp;&nbsp;`;
 			}
 		}
-		if (V.abbreviateMental === 1) {
+		if (abbreviate.mental === 1) {
 			r += `<span class="lightgreen">`;
 			shortExtendedFamily(child);
 			r += `</span> `;
 			shortRival(child);
-		} else if (V.abbreviateMental === 2) {
+		} else if (abbreviate.mental === 2) {
 			longExtendedFamily(child);
 			longRival(child);
 		}
 		r += `&nbsp;&nbsp;&nbsp;&nbsp;`;
-		if (V.abbreviateOrigins === 2 && child.origin !== 0) {
+		if (abbreviate.origins === 2 && child.origin !== 0) {
 			origins(child);
 		}
 		return r;
@@ -2065,6 +2067,8 @@ App.Facilities.Nursery.LongInfantDescription = function(child) {
  */
 App.Facilities.Nursery.ChildSummary = function(child) {
 	"use strict";
+	/** @type {App.UI.SlaveSummary.AbbreviationState} */
+	const abbreviate = V.UI.slaveSummary.abbreviation;
 
 	let
 		r = ``;
@@ -2074,39 +2078,39 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	 * @returns {string}
 	 */
 	function ChildSummaryUncached(child) {
-		if (V.abbreviateDevotion === 1) {
+		if (abbreviate.devotion === 1) {
 			shortDevotion(child);
-		} else if (V.abbreviateDevotion === 2) {
+		} else if (abbreviate.devotion === 2) {
 			longDevotion(child);
 		}
 		if (child.fuckdoll === 0) {
-			if (V.abbreviateRules === 1) {
+			if (abbreviate.rules === 1) {
 				shortRules(child);
-			} else if (V.abbreviateRules === 2) {
+			} else if (abbreviate.rules === 2) {
 				longRules(child);
 			}
 		}
-		if (V.abbreviateDiet === 1) {
+		if (abbreviate.diet === 1) {
 			shortWeight(child);
-		} else if (V.abbreviateDiet === 2) {
+		} else if (abbreviate.diet === 2) {
 			longWeight(child);
 		}
-		if (V.abbreviateDiet === 1) {
+		if (abbreviate.diet === 1) {
 			shortDiet(child);
-		} else if (V.abbreviateDiet === 2) {
+		} else if (abbreviate.diet === 2) {
 			longDiet(child);
 		}
-		if (V.abbreviateHealth === 1) {
+		if (abbreviate.health === 1) {
 			shortHealth(child);
-		} else if (V.abbreviateHealth === 2) {
+		} else if (abbreviate.health === 2) {
 			longHealth(child);
 		}
-		if (V.abbreviateDrugs === 1) {
+		if (abbreviate.drugs === 1) {
 			shortDrugs(child);
-		} else if (V.abbreviateDrugs === 2) {
+		} else if (abbreviate.drugs === 2) {
 			longDrugs(child);
 		}
-		if (V.abbreviateNationality + V.abbreviateGenitalia + V.abbreviatePhysicals + V.abbreviateSkills + V.abbreviateMental !== 0) {
+		if (abbreviate.nationality + abbreviate.genitalia + abbreviate.physicals + abbreviate.skills + abbreviate.mental !== 0) {
 			r += `<br> `;
 			if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) {
 				r += "&nbsp;&nbsp;&nbsp;&nbsp;";
@@ -2115,32 +2119,32 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 		V.desc = SlaveTitle(child);
 		const firstLetter = V.desc.substring(0, 1).toUpperCase();
 		V.desc = firstLetter + V.desc.substring(1);
-		r += `<strong><span class="coral">${V.desc}${V.abbreviatePhysicals === 2? '.' : ''}</span></strong> `;
+		r += `<strong><span class="coral">${V.desc}${abbreviate.physicals === 2? '.' : ''}</span></strong> `;
 		if (V.seeRace) {
 			r += `<span class="tan">`;
-			if (V.abbreviateRace === 1) {
+			if (abbreviate.race === 1) {
 				shortRace(child);
-			} else if (V.abbreviateRace === 2) {
+			} else if (abbreviate.race === 2) {
 				longRace(child);
 			}
 			r += `</span> `;
 		}
-		if (V.abbreviateNationality === 1) {
+		if (abbreviate.nationality === 1) {
 			shortNationality(child);
-		} else if (V.abbreviateNationality === 2) {
+		} else if (abbreviate.nationality === 2) {
 			longNationality(child);
 		}
-		if (V.abbreviatePhysicals === 1) {
+		if (abbreviate.physicals === 1) {
 			shortSkin(child);
 		} else {
 			r += `<span class="pink">${child.skin.charAt(0).toUpperCase() + child.skin.slice(1)} skin.</span> `;
 		}
-		if (V.abbreviateGenitalia === 1) {
+		if (abbreviate.genitalia === 1) {
 			shortGenitals(child);
-		} else if (V.abbreviateGenitalia === 2) {
+		} else if (abbreviate.genitalia === 2) {
 			longGenitals(child);
 		}
-		if (V.abbreviatePhysicals === 1) {
+		if (abbreviate.physicals === 1) {
 			shortAge(child);
 			shortFace(child);
 			shortEyes(child);
@@ -2160,7 +2164,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 			shortLactation(child);
 			// shortMods(child);
 			r += `</span> `;
-		} else if (V.abbreviatePhysicals === 2) {
+		} else if (abbreviate.physicals === 2) {
 			longAge(child);
 			longFace(child);
 			longEyes(child);
@@ -2181,7 +2185,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 			}
 			r += `</span> `;
 		}
-		if (V.abbreviateHormoneBalance === 1) {
+		if (abbreviate.hormoneBalance === 1) {
 			if (child.hormoneBalance <= -21) {
 				r += `<span class="deepskyblue">`;
 				r += ` <strong>HB:M</strong> `;
@@ -2193,7 +2197,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 				r += ` <strong>HB:F</strong> `;
 			}
 			r += `</span> `;
-		} else if (V.abbreviateHormoneBalance === 2) {
+		} else if (abbreviate.hormoneBalance === 2) {
 			r += `<span class=`;
 			if (child.hormoneBalance <= -21) {
 				r += `"deepskyblue"`;
@@ -2230,7 +2234,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 		if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) {
 			r += "&nbsp;&nbsp;&nbsp;&nbsp;";
 		}
-		if (V.abbreviateSkills === 1) {
+		if (abbreviate.skills === 1) {
 			shortIntelligence(child);
 			shortSexSkills(child);
 			if (child.skill.combat > 0) {
@@ -2239,7 +2243,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 			r += "</span> ";
 			shortPrestige(child);
 			shortPornPrestige(child);
-		} else if (V.abbreviateSkills === 2) {
+		} else if (abbreviate.skills === 2) {
 			longIntelligence(child);
 			longSexSkills(child);
 			if (child.skill.combat > 0) {
@@ -2249,7 +2253,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 			longPrestige(child);
 			longPornPrestige(child);
 		}
-		if (V.abbreviateMental === 1) {
+		if (abbreviate.mental === 1) {
 			if (child.fetish !== "mindbroken") {
 				if (child.fetishKnown === 1) {
 					shortFetish(child);
@@ -2266,7 +2270,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 			shortSexFlaw(child);
 			shortBehaviorQuirk(child);
 			shortSexQuirk(child);
-		} else if (V.abbreviateMental === 2) {
+		} else if (abbreviate.mental === 2) {
 			if (child.fetish !== "mindbroken") {
 				if (child.fetishKnown === 1) {
 					longFetish(child);
@@ -2287,23 +2291,23 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 		if (child.custom.label) {
 			r += `<strong><span class="yellow">${capFirstChar(child.custom.label)}</span></strong> `;
 		}
-		if ((child.relationship !== 0) || (V.abbreviateClothes === 2) || (V.abbreviateRulesets === 2)) {
+		if ((child.relationship !== 0) || (abbreviate.clothes === 2) || (abbreviate.rulesets === 2)) {
 			r += `<br> `;
 			if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) {
 				r += `&nbsp;&nbsp;&nbsp;&nbsp;`;
 			}
 		}
-		if (V.abbreviateMental === 1) {
+		if (abbreviate.mental === 1) {
 			r += `<span class="lightgreen">`;
 			shortExtendedFamily(child);
 			r += `</span> `;
 			shortClone(child);
 			shortRival(child);
-		} else if (V.abbreviateMental === 2) {
+		} else if (abbreviate.mental === 2) {
 			longExtendedFamily(child);
 		}
 		if (child.fuckdoll === 0) {
-			if (V.abbreviateClothes === 2) {
+			if (abbreviate.clothes === 2) {
 				r += `&nbsp;&nbsp;&nbsp;&nbsp;`;
 				if (child.choosesOwnClothes === 1) {
 					r += "Dressing herself. ";
@@ -2325,7 +2329,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 		}
 		r += `&nbsp;&nbsp;&nbsp;&nbsp;`;
 		rulesAssistant(child);
-		if (V.abbreviateOrigins === 2 && child.origin !== 0) {
+		if (abbreviate.origins === 2 && child.origin !== 0) {
 			origins(child);
 		}
 		return r;
@@ -6448,7 +6452,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	function rulesAssistant(child) {
 		if (child.useRulesAssistant === 0) {
 			r += `<span class="lightgreen">RA-Exempt</span> `;
-		} else if (V.abbreviateRulesets === 2 && (child.currentRules !== undefined) && (child.currentRules.length > 0)) {
+		} else if (abbreviate.rulesets === 2 && (child.currentRules !== undefined) && (child.currentRules.length > 0)) {
 			r += `Rules: ${V.defaultRules.filter(x => ruleApplied(child, x)).map(x => x.name).join(", ")}`;
 		}
 	}
diff --git a/src/interaction/slaveInteract.js b/src/interaction/slaveInteract.js
index d8a3f70062e55d3aa90e6690a4d87ef547c39b22..b187865f2dd471fc3c70e8c0586a6377d9d96cb0 100644
--- a/src/interaction/slaveInteract.js
+++ b/src/interaction/slaveInteract.js
@@ -306,211 +306,226 @@ App.UI.SlaveInteract.drugs = function(slave) {
 		He,
 		His
 	} = getPronouns(slave);
-	const drugOptions = [];
 	const drugLevelOptions = [];
+	const lips = [];
+	const breasts = [];
+	const nipples = [];
+	const butt = [];
+	const dick = [];
+	const balls = [];
+	const fertility = [];
+	const hormones = [];
+	const psych = [];
+	const misc = [];
 
 	if (slave.drugs !== "no drugs") {
 		drugLevelOptions.push({text: `None`, updateSlave: {drugs: `no drugs`}});
 	}
 	if (slave.indentureRestrictions < 2) {
-		if (
-			slave.drugs === "intensive breast injections" ||
-			slave.drugs === "intensive butt injections" ||
-			slave.drugs === "intensive penis enhancement" ||
-			slave.drugs === "intensive testicle enhancement"
-		) {
-			switch (slave.drugs) {
-				case "intensive breast injections":
-					drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `breast injections`}});
-					break;
-				case "intensive butt injections":
-					drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `butt injections`}});
-					break;
-				case "intensive penis enhancement":
-					drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `penis enhancement`}});
-					break;
-				case "intensive testicle enhancement":
-					drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `testicle enhancement`}});
-					break;
-			}
-		} else if (slave.drugs === "breast injections" || slave.drugs === "butt injections" || slave.drugs === "penis enhancement" || slave.drugs === "testicle enhancement") {
-			drugLevelOptions.push({text: `Intensify`, updateSlave: {drugs: "intensive " + slave.drugs}});
-		}
+
+		// Psych
 		if (slave.intelligence > -100 && slave.indentureRestrictions < 1) {
-			drugOptions.push({text: `Psychosuppressants`, updateSlave: {drugs: `psychosuppressants`}});
+			psych.push({text: `Psychosuppressants`, updateSlave: {drugs: `psychosuppressants`}});
 		} else if (slave.intelligence > -100) {
-			drugOptions.push({text: `Psychosuppressants`, disabled: `Cannot suppress indentured slave`});
+			psych.push({text: `Psychosuppressants`, disabled: `Cannot suppress indentured slave`});
 		} else if (slave.indentureRestrictions < 1) {
-			drugOptions.push({text: `Psychosuppressants`, disabled: `Too stupid to suppress`});
+			psych.push({text: `Psychosuppressants`, disabled: `Too stupid to suppress`});
 		} else {
-			drugOptions.push({text: `Psychosuppressants`, disabled: `Too stupid and indentured to suppress`});
+			psych.push({text: `Psychosuppressants`, disabled: `Too stupid and indentured to suppress`});
 		}
 		if (V.arcologies[0].FSSlaveProfessionalismResearch === 1) {
 			if (canImproveIntelligence(slave)) {
-				drugOptions.push({text: `Psychostimulants`, updateSlave: {drugs: `psychostimulants`}});
+				psych.push({text: `Psychostimulants`, updateSlave: {drugs: `psychostimulants`}});
+			} else {
+				psych.push({text: `Psychostimulants`, disabled: `Cannot improve intelligence`});
+			}
+		}
+
+		// Breasts
+		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
+			if ((slave.boobs - slave.boobsImplant - slave.boobsMilk) > 100) {
+				breasts.push({text: `Reducers`, updateSlave: {drugs: `breast redistributors`}});
 			} else {
-				drugOptions.push({text: `Psychostimulants`, disabled: `Cannot improve intelligence`});
+				breasts.push({text: `Reducers`, disabled: `Boobs are too small`});
 			}
 		}
 		if (slave.boobs < 48000) {
-			drugOptions.push({text: `Breasts`, updateSlave: {drugs: `breast injections`}});
+			breasts.push({text: `Enhancement`, updateSlave: {drugs: `breast injections`}});
+			breasts.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive breast injections`}});
 		} else {
-			drugOptions.push({text: `Breasts`, disabled: `Boobs are too large`});
+			breasts.push({text: `Enhancement`, disabled: `Boobs are too large`});
 		}
 		if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
 			if (slave.boobs < 25000) {
-				drugOptions.push({text: `Hyper-Breasts`, updateSlave: {drugs: `hyper breast injections`}});
+				breasts.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper breast injections`}});
 			} else {
-				drugOptions.push({text: `Hyper Breasts`, disabled: `Boobs are too large`});
+				breasts.push({text: `Hyper enhancement`, disabled: `Boobs are too large`});
+			}
+		}
+
+		// Nipples
+		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
+			if (slave.nipples === "huge" || slave.nipples === "puffy" || slave.nipples === "cute") {
+				nipples.push({text: `Reducers`, updateSlave: {drugs: `nipple atrophiers`}});
+			} else {
+				nipples.push({text: `Reducers`, disabled: `Nipples are ${slave.nipples}`});
 			}
 		}
 		if(V.dispensary) {
 			if ((["inverted", "partially inverted", "cute", "tiny", "puffy"].includes(slave.nipples))) {
-				drugOptions.push({text: `Nipple enhancers`, updateSlave: {drugs: `nipple enhancers`}});
+				nipples.push({text: `Enhancement`, updateSlave: {drugs: `nipple enhancers`}});
 			} else if(slave.nipples === "huge") {
-				drugOptions.push({text: `Nipple enhancers`, disabled: `Nipples are already huge`});
+				nipples.push({text: `Enhancement`, disabled: `Nipples are already huge`});
 			} else {
-				drugOptions.push({text: `Nipple enhancers`, disabled: `Has no effect on ${slave.nipples} nipples`});
+				nipples.push({text: `Enhancement`, disabled: `Has no effect on ${slave.nipples} nipples`});
 			}
 		}
+
+		// Butt
 		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
-			if ((slave.boobs - slave.boobsImplant - slave.boobsMilk) > 100) {
-				drugOptions.push({text: `Breast reducers`, updateSlave: {drugs: `breast redistributors`}});
-			} else {
-				drugOptions.push({text: `Breast reducers`, disabled: `Boobs are too small`});
-			}
-			if (slave.nipples === "huge" || slave.nipples === "puffy" || slave.nipples === "cute") {
-				drugOptions.push({text: `Nipple reducers`, updateSlave: {drugs: `nipple atrophiers`}});
+			if (slave.butt - slave.buttImplant > 0) {
+				butt.push({text: `Reducers`, updateSlave: {drugs: `butt redistributors`}});
 			} else {
-				drugOptions.push({text: `Nipple reducers`, disabled: `Nipples are ${slave.nipples}`});
+				butt.push({text: `Reducers`, disabled: `Butt is too small`});
 			}
 		}
 		if (slave.butt < 9) {
-			drugOptions.push({text: `Buttocks`, updateSlave: {drugs: `butt injections`}});
+			butt.push({text: `Enhancement`, updateSlave: {drugs: `butt injections`}});
+			butt.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive butt injections`}});
 		} else {
-			drugOptions.push({text: `Buttocks`, disabled: `Butt is too large`});
+			butt.push({text: `Enhancement`, disabled: `Butt is too large`});
 		}
 		if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
 			if (slave.butt < 20) {
-				drugOptions.push({text: `Hyper-Buttocks`, updateSlave: {drugs: `hyper butt injections`}});
+				butt.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper butt injections`}});
 			} else {
-				drugOptions.push({text: `Hyper Buttocks`, disabled: `Butt is too large`});
+				butt.push({text: `Hyper enhancement`, disabled: `Butt is too large`});
 			}
 		}
+
+		// Lips
 		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
-			if (slave.butt - slave.buttImplant > 0) {
-				drugOptions.push({text: `Butt reducers`, updateSlave: {drugs: `butt redistributors`}});
+			if (slave.lips - slave.lipsImplant > 0) {
+				lips.push({text: `Reducers`, updateSlave: {drugs: `lip atrophiers`}});
 			} else {
-				drugOptions.push({text: `Butt reducers`, disabled: `Butt is too small`});
+				lips.push({text: `Reducers`, disabled: `Lips are too small`});
 			}
 		}
 		if (slave.lips <= 95 || (slave.lips <= 85 && V.seeExtreme !== 1)) {
-			drugOptions.push({text: `Lips`, updateSlave: {drugs: `lip injections`}});
+			lips.push({text: `Enhancement`, updateSlave: {drugs: `lip injections`}});
 		} else {
-			drugOptions.push({text: `Lips`, disabled: `Lips are too large`});
+			lips.push({text: `Enhancement`, disabled: `Lips are too large`});
 		}
+
+		// Fertility
+		fertility.push({text: `Fertility`, updateSlave: {drugs: `fertility drugs`}});
+		if (V.seeHyperPreg === 1 && slave.indentureRestrictions < 1 && V.superFertilityDrugs === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
+			fertility.push({text: `Fertility+`, updateSlave: {drugs: `super fertility drugs`}});
+		}
+
+		// Dick/clit
 		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
-			if (slave.lips - slave.lipsImplant > 0) {
-				drugOptions.push({text: `Lip reducers`, updateSlave: {drugs: `lip atrophiers`}});
-			} else {
-				drugOptions.push({text: `Lip reducers`, disabled: `Lips are too small`});
+			if (slave.dick > 1) {
+				dick.push({text: `Reducers`, updateSlave: {drugs: `penis atrophiers`}});
+			} else if (slave.dick === 1) {
+				dick.push({text: `Reducers`, disabled: `Dick is already at minimum size`});
 			}
-		}
-		if (V.growthStim === 1) {
-			if (canImproveHeight(slave)) {
-				drugOptions.push({text: `Growth Stimulants`, updateSlave: {drugs: `growth stimulants`}});
-			} else {
-				drugOptions.push({text: `Growth Stimulants`, disabled: `Cannot increase height further`});
+			if (slave.clit > 0) {
+				dick.push({text: `Reducers`, updateSlave: {drugs: `clitoris atrophiers`}});
 			}
 		}
-		drugOptions.push({text: `Fertility`, updateSlave: {drugs: `fertility drugs`}});
-		if (V.seeHyperPreg === 1 && slave.indentureRestrictions < 1 && V.superFertilityDrugs === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
-			drugOptions.push({text: `Fertility+`, updateSlave: {drugs: `super fertility drugs`}});
-		}
 		if (slave.dick > 0) {
 			if (slave.dick < 10) {
-				drugOptions.push({text: `Penis enhancement`, updateSlave: {drugs: `penis enhancement`}});
+				dick.push({text: `Enhancement`, updateSlave: {drugs: `penis enhancement`}});
+				dick.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive penis enhancement`}});
 			} else {
-				drugOptions.push({text: `Penis enhancement`, disabled: `Dick is too large`});
+				dick.push({text: `Enhancement`, disabled: `Dick is too large`});
 			}
 		} else {
 			if (slave.clit < 5) {
-				drugOptions.push({text: `Clitoris enhancement`, updateSlave: {drugs: `penis enhancement`}});
+				dick.push({text: `Enhancement`, updateSlave: {drugs: `penis enhancement`}});
+				dick.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive penis enhancement`}});
 			} else {
-				drugOptions.push({text: `Clitoris enhancement`, disabled: `Clit is too large`});
+				dick.push({text: `Enhancement`, disabled: `Clit is too large`});
 			}
 		}
 		if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
 			if (slave.dick > 0) {
 				if (slave.dick < 31) {
-					drugOptions.push({text: `Hyper penis enhancement`, updateSlave: {drugs: `hyper penis enhancement`}});
+					dick.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper penis enhancement`}});
 				} else {
-					drugOptions.push({text: `Hyper penis enhancement`, disabled: `Dick is too large`});
+					dick.push({text: `Hyper enhancement`, disabled: `Dick is too large`});
 				}
 			} else {
 				if (slave.clit < 5) {
-					drugOptions.push({text: `Hyper clitoris enhancement`, updateSlave: {drugs: `penis enhancement`}});
+					dick.push({text: `Hyper enhancement`, updateSlave: {drugs: `penis enhancement`}});
 				} else {
-					drugOptions.push({text: `Hyper clitoris enhancement`, disabled: `Clit is too large`});
+					dick.push({text: `Hyper enhancement`, disabled: `Clit is too large`});
 				}
 			}
 		}
+		if (slave.dick > 0 && slave.dick < 11 && !canAchieveErection(slave) && slave.chastityPenis !== 1) {
+			dick.push({text: `Erectile dysfunction circumvention`, updateSlave: {drugs: `priapism agents`}});
+		}
+
+		// Balls
 		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
-			if (slave.dick > 1) {
-				drugOptions.push({text: `Penis reducers`, updateSlave: {drugs: `penis atrophiers`}});
-			} else if (slave.dick === 1) {
-				drugOptions.push({text: `Penis reducers`, disabled: `Dick is already at minimum size`});
+			if (slave.balls > 1) {
+				balls.push({text: `Reducers`, updateSlave: {drugs: `testicle atrophiers`}});
+			} else if (slave.balls === 1) {
+				balls.push({text: `Reducers`, disabled: `Balls are already at minimum size`});
 			}
 		}
-		if (slave.dick > 0 && slave.dick < 11 && !canAchieveErection(slave) && slave.chastityPenis !== 1) {
-			drugOptions.push({text: `Erectile dysfunction circumvention`, updateSlave: {drugs: `priapism agents`}});
-		}
 		if (slave.balls > 0) {
-			drugOptions.push({text: `Testicle enhancement`, updateSlave: {drugs: `testicle enhancement`}});
+			balls.push({text: `Enhancement`, updateSlave: {drugs: `testicle enhancement`}});
+			balls.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive testicle enhancement`}});
 			if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
-				drugOptions.push({text: `Hyper testicle enhancement`, updateSlave: {drugs: `hyper testicle enhancement`}});
+				balls.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper testicle enhancement`}});
 			}
 		}
-		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
-			if (slave.balls > 1) {
-				drugOptions.push({text: `Testicle reducers`, updateSlave: {drugs: `testicle atrophiers`}});
-			} else if (slave.balls === 1) {
-				drugOptions.push({text: `Testicle reducers`, disabled: `Balls are already at minimum size`});
+
+		// Hormones
+		if (V.precociousPuberty === 1 && V.pubertyHormones === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
+			if ((slave.ovaries === 1 || slave.mpreg === 1) && slave.pubertyXX === 0) {
+				hormones.push({text: `Female injections`, updateSlave: {drugs: `female hormone injections`}});
 			}
-			if (slave.clit > 0) {
-				drugOptions.push({text: `Clitoris reducers`, updateSlave: {drugs: `clitoris atrophiers`}});
+			if (slave.balls > 0 && slave.pubertyXY === 0) {
+				hormones.push({text: `Male injections`, updateSlave: {drugs: `male hormone injections`}});
 			}
+		}
+		hormones.push({text: `Blockers`, updateSlave: {drugs: `hormone blockers`}});
+		hormones.push({text: `Enhancement`, updateSlave: {drugs: `hormone enhancers`}});
+
+		// Misc
+		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
 			if (slave.labia > 0) {
-				drugOptions.push({text: `Labia reducers`, updateSlave: {drugs: `labia atrophiers`}});
+				misc.push({text: `Labia reducers`, updateSlave: {drugs: `labia atrophiers`}});
 			}
 		}
-		if (V.arcologies[0].FSYouthPreferentialistResearch === 1) {
-			if (slave.visualAge > 18) {
-				drugOptions.push({text: `Anti-aging cream`, updateSlave: {drugs: `anti-aging cream`}});
+		if (V.growthStim === 1) {
+			if (canImproveHeight(slave)) {
+				misc.push({text: `Growth Stimulants`, updateSlave: {drugs: `growth stimulants`}});
 			} else {
-				drugOptions.push({text: `Anti-aging cream`, disabled: `Slave already looks young enough`});
+				misc.push({text: `Growth Stimulants`, disabled: `Cannot increase height further`});
 			}
 		}
-		drugOptions.push({text: `Steroids`, updateSlave: {drugs: `steroids`}});
 		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
 			if (slave.weight > -95) {
-				drugOptions.push({text: `Weight loss pills`, updateSlave: {drugs: `appetite suppressors`}});
+				misc.push({text: `Weight loss pills`, updateSlave: {drugs: `appetite suppressors`}});
 			} else {
-				drugOptions.push({text: `Weight loss pills`, disabled: `Slave is already at low weight`});
-			}
-		}
-		if (V.precociousPuberty === 1 && V.pubertyHormones === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
-			if ((slave.ovaries === 1 || slave.mpreg === 1) && slave.pubertyXX === 0) {
-				drugOptions.push({text: `Female hormone injections`, updateSlave: {drugs: `female hormone injections`}});
-			}
-			if (slave.balls > 0 && slave.pubertyXY === 0) {
-				drugOptions.push({text: `Male hormone injections`, updateSlave: {drugs: `male hormone injections`}});
+				misc.push({text: `Weight loss pills`, disabled: `Slave is already at low weight`});
 			}
 		}
-		drugOptions.push({text: `Hormone enhancers`, updateSlave: {drugs: `hormone enhancers`}});
-		drugOptions.push({text: `Hormone blockers`, updateSlave: {drugs: `hormone blockers`}});
+		misc.push({text: `Steroids`, updateSlave: {drugs: `steroids`}});
 		if (slave.boobs > 250 && slave.boobShape !== "saggy" && V.purchasedSagBGone === 1) {
-			drugOptions.push({text: `Sag-B-Gone breast lifting cream`, updateSlave: {drugs: `sag-B-gone`}});
+			misc.push({text: `Sag-B-Gone breast lifting cream`, updateSlave: {drugs: `sag-B-gone`}});
+		}
+		if (V.arcologies[0].FSYouthPreferentialistResearch === 1) {
+			if (slave.visualAge > 18) {
+				misc.push({text: `Anti-aging cream`, updateSlave: {drugs: `anti-aging cream`}});
+			} else {
+				misc.push({text: `Anti-aging cream`, disabled: `Slave already looks young enough`});
+			}
 		}
 	}
 
@@ -523,10 +538,87 @@ App.UI.SlaveInteract.drugs = function(slave) {
 	title.appendChild(App.UI.SlaveInteract.generateRows(drugLevelOptions, slave));
 	el.append(title);
 
-	let drugLinks = document.createElement('div');
-	drugLinks.appendChild(App.UI.SlaveInteract.generateRows(drugOptions, slave));
-	drugLinks.className = "choices";
-	el.append(drugLinks);
+	let links;
+	if (lips.length) {
+		links = document.createElement('div');
+		links.append(`Lips: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(lips, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (breasts.length) {
+		links = document.createElement('div');
+		links.append(`Breasts: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(breasts, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (nipples.length) {
+		links = document.createElement('div');
+		links.append(`Nipples: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(nipples, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (butt.length) {
+		links = document.createElement('div');
+		links.append(`Butt: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(butt, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (dick.length) {
+		links = document.createElement('div');
+		links.append(`Dick: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(dick, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (balls.length) {
+		links = document.createElement('div');
+		links.append(`Balls: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(balls, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (fertility.length) {
+		links = document.createElement('div');
+		links.append(`Fertility: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(fertility, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (hormones.length) {
+		links = document.createElement('div');
+		links.append(`Hormones: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(hormones, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (psych.length) {
+		links = document.createElement('div');
+		links.append(`Psych: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(psych, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (misc.length) {
+		links = document.createElement('div');
+		links.append(`Misc: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(misc, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
 
 	return jQuery('#drugs').empty().append(el);
 };
@@ -2006,6 +2098,223 @@ App.UI.SlaveInteract.nursery = function(slave) {
 	return jQuery('#nursery').empty().append(el);
 };
 
+App.UI.SlaveInteract.smartSettings = function(slave) {
+	let el = document.createElement('div');
+
+	const {
+		// eslint-disable-next-line no-unused-vars
+		he,
+		him,
+		his,
+		hers,
+		himself,
+		boy,
+		He,
+		His
+	} = getPronouns(slave);
+	const bodyPart = [];
+	const BDSM = [];
+	const gender = [];
+	const level = [];
+
+	if (slave.clitPiercing === 3 || slave.vaginalAccessory === "smart bullet vibrator") {
+		// Level
+		level.push({text: `No sex`, updateSlave: {clitSetting: `none`}});
+		level.push({text: `All sex`, updateSlave: {clitSetting: `all`}});
+
+		// Body part
+		bodyPart.push({text: `Vanilla`, updateSlave: {clitSetting: `vanilla`}});
+		bodyPart.push({text: `Oral`, updateSlave: {clitSetting: `oral`}});
+		bodyPart.push({text: `Anal`, updateSlave: {clitSetting: `anal`}});
+		bodyPart.push({text: `Boobs`, updateSlave: {clitSetting: `boobs`}});
+		if (V.seePreg !== 0) {
+			bodyPart.push({text: `Preg`, updateSlave: {clitSetting: `pregnancy`}});
+		}
+		// BDSM
+		BDSM.push({text: `Sub`, updateSlave: {clitSetting: `submissive`}});
+		BDSM.push({text: `Dom`, updateSlave: {clitSetting: `dom`}});
+		BDSM.push({text: `Masochism`, updateSlave: {clitSetting: `masochist`}});
+		BDSM.push({text: `Sadism`, updateSlave: {clitSetting: `sadist`}});
+		BDSM.push({text: `Humiliation`, updateSlave: {clitSetting: `humiliation`}});
+
+		// Gender
+		gender.push({text: `Men`, updateSlave: {clitSetting: `men`}});
+		gender.push({text: `Women`, updateSlave: {clitSetting: `women`}});
+		gender.push({text: `Anti-men`, updateSlave: {clitSetting: `anti-men`}});
+		gender.push({text: `Anti-women`, updateSlave: {clitSetting: `anti-women`}});
+	}
+
+	let title = document.createElement('div');
+	title.textContent = ``;
+	if (slave.clitPiercing === 3) {
+		if (slave.dick < 1) {
+			title.textContent = `${His} smart clit piercing `;
+			if (slave.vaginalAccessory === "smart bullet vibrator") {
+				title.textContent += `and smart bullet vibrator are `;
+			} else {
+				title.textContent += `is `;
+			 }
+			 title.textContent += `set to: `;
+		} else{
+			title.textContent = `${His} smart frenulum piercing `;
+			if (slave.vaginalAccessory === "smart bullet vibrator") {
+				title.textContent += `and smart bullet vibrator are `;
+			} else {
+				title.textContent += `is `;
+			}
+			title.textContent += `set to: `;
+		}
+	} else {
+		title.textContent = `${His} smart bullet vibe is set to: `;
+	}
+	let selected = document.createElement('span');
+	selected.style.fontWeight = "bold";
+	selected.textContent = `${slave.clitSetting}. `;
+	title.append(selected);
+	el.append(title);
+
+	let links;
+	if (level.length) {
+		links = document.createElement('div');
+		links.append(`Level: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(level, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (bodyPart.length) {
+		links = document.createElement('div');
+		links.append(`Body part: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(bodyPart, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (BDSM.length) {
+		links = document.createElement('div');
+		links.append(`BDSM: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(BDSM, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (gender.length) {
+		links = document.createElement('div');
+		links.append(`Gender: `);
+		links.appendChild(App.UI.SlaveInteract.generateRows(gender, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	return jQuery('#smartSettings').empty().append(el);
+};
+
+App.UI.SlaveInteract.orgasm = function(slave) {
+	let el = document.createElement('div');
+
+	const {
+		// eslint-disable-next-line no-unused-vars
+		he,
+		him,
+		his,
+		hers,
+		himself,
+		boy,
+		He,
+		His
+	} = getPronouns(slave);
+	const masturbation = [];
+	const partner = [];
+	const family = [];
+	const slaves = [];
+	const master = [];
+
+	// Masturbation
+	masturbation.push({text: `Allow`, updateSlave: {"rules.release.masturbation": 1}});
+	masturbation.push({text: `Forbid`, updateSlave: {"rules.release.masturbation": 0}});
+
+	// Partner
+	partner.push({text: `Allow`, updateSlave: {"rules.release.partner": 1}});
+	partner.push({text: `Forbid`, updateSlave: {"rules.release.partner": 0}});
+
+	// Family
+	family.push({text: `Allow`, updateSlave: {"rules.release.family": 1}});
+	family.push({text: `Forbid`, updateSlave: {"rules.release.family": 0}});
+
+	// Slaves
+	slaves.push({text: `Allow`, updateSlave: {"rules.release.slaves": 1}});
+	slaves.push({text: `Forbid`, updateSlave: {"rules.release.slaves": 0}});
+
+	// Master
+	master.push({text: `Grant`, updateSlave: {"rules.release.master": 1}});
+	master.push({text: `Deny`, updateSlave: {"rules.release.master": 0}});
+
+	let title = document.createElement('div');
+	title.textContent = `Non-assignment orgasm rules: `;
+	el.append(title);
+
+	let links;
+	if (masturbation.length) {
+		links = document.createElement('div');
+		links.append(`Masturbation: `);
+		status(slave.rules.release.masturbation);
+		links.appendChild(App.UI.SlaveInteract.generateRows(masturbation, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (partner.length) {
+		links = document.createElement('div');
+		links.append(`Partner: `);
+		status(slave.rules.release.partner);
+		links.appendChild(App.UI.SlaveInteract.generateRows(partner, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (family.length) {
+		links = document.createElement('div');
+		links.append(`Family: `);
+		status(slave.rules.release.family);
+		links.appendChild(App.UI.SlaveInteract.generateRows(family, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (slaves.length) {
+		links = document.createElement('div');
+		links.append(`Other slaves: `);
+		status(slave.rules.release.slaves);
+		links.appendChild(App.UI.SlaveInteract.generateRows(slaves, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	if (master.length) {
+		links = document.createElement('div');
+		links.append(`Master: `);
+		status(slave.rules.release.master, true);
+		links.appendChild(App.UI.SlaveInteract.generateRows(master, slave));
+		links.className = "choices";
+		el.append(links);
+	}
+
+	function status(setting, master) {
+		let selected = document.createElement('span');
+		selected.style.fontWeight = "bold";
+		let text;
+		if (master) {
+			text = setting ? "granted" : "denied";
+		} else {
+			text = setting ? "allowed" : "denied";
+		}
+		selected.textContent = `${text}. `;
+		links.append(selected);
+	}
+
+	return jQuery('#orgasm').empty().append(el);
+};
+
 App.UI.SlaveInteract.custom = (function() {
 	let el;
 	let label;
@@ -2914,7 +3223,9 @@ App.UI.SlaveInteract.generateRows = function(array, slave, category, accessCheck
 
 	function click(arrayOption) {
 		if (arrayOption.updateSlave) {
-			Object.assign(slave, arrayOption.updateSlave);
+			for (const slaveProperty in arrayOption.updateSlave) {
+				setValue(slave, slaveProperty, arrayOption.updateSlave[slaveProperty]);
+			}
 		}
 		if (arrayOption.update) {
 			Object.assign(V, arrayOption.update);
@@ -2923,6 +3234,20 @@ App.UI.SlaveInteract.generateRows = function(array, slave, category, accessCheck
 		App.UI.SlaveInteract.refreshAll(slave);
 		return;
 	}
+
+	function setValue(obj, path, val) {
+		const ref = resolve(obj, path);
+		ref.obj[ref.prop] = val;
+	}
+
+	function resolve(obj, path, separator = '.') {
+		const pathArray = Array.isArray(path) ? path : path.split(separator);
+		if (pathArray.length === 1) {
+			return {obj: obj, prop: pathArray[0]};
+		}
+		const lastObj = pathArray.slice(0, pathArray.length - 1).reduce((prev, prop) => prev[prop], obj);
+		return {obj: lastObj, prop: pathArray.slice(pathArray.length - 1)};
+	}
 };
 
 App.UI.SlaveInteract.refreshAll = function(slave) {
@@ -2941,4 +3266,6 @@ App.UI.SlaveInteract.refreshAll = function(slave) {
 	App.UI.SlaveInteract.diet(slave);
 	App.UI.SlaveInteract.dietBase(slave);
 	App.UI.SlaveInteract.snacks(slave);
+	App.UI.SlaveInteract.smartSettings(slave);
+	App.UI.SlaveInteract.orgasm(slave);
 };
diff --git a/src/js/eventHandlers.js b/src/js/eventHandlers.js
index 4099ad52566ebc2354161abf905e45bb84717516..4e3a7fc73e4e128f3d3566970828a0492aa020ef 100644
--- a/src/js/eventHandlers.js
+++ b/src/js/eventHandlers.js
@@ -3,6 +3,18 @@ App.EventHandlers = function() {
 	 * @param {SugarCubeLib.SaveObject} save
 	 */
 	function onLoad(save) {
+		const v = save.state.history[0].variables;
+		if (v.releaseID > App.Version.release) {
+			console.error("Save game version problem. Loaded : " + v.releaseID + ", above expected:" + App.Version.release); // eslint-disable-line no-console
+			throw new Error("The save you're attempting to load was created with the game version newer than one you are running. Please download the latest game version.");
+		}
+		// updating settings only for the same releaseID, otherwise user will run
+		// backwards compatibility and we update settings from there
+		if (v.releaseID === App.Version.release) {
+			App.UI.SlaveSummary.settingsChanged(v);
+		}
+
+		App.UI.Theme.onLoad(v);
 	}
 
 	/**
@@ -15,6 +27,7 @@ App.EventHandlers = function() {
 	}
 
 	function optionsChanged() {
+		App.UI.SlaveSummary.settingsChanged();
 	}
 
 	return {
diff --git a/src/js/slaveListing.js b/src/js/slaveListing.js
index cc59d9a7aa1cc816a4f8890d42fe1b199dad2b90..86eb4a8010566738bd4aaf05a2b61444c40da656 100644
--- a/src/js/slaveListing.js
+++ b/src/js/slaveListing.js
@@ -328,7 +328,7 @@ App.UI.SlaveList.render = function() {
 			res.appendChild(App.UI.jobLinks.transfersFragment(index, (slave, assignment) => { App.UI.SlaveList.ScrollPosition.record(); assignJob(slave, assignment); }));
 		}
 
-		res.appendChild(SlaveSummary(slave));
+		res.appendChild(App.UI.SlaveSummary.render(slave));
 
 		if (postNote !== undefined) {
 			const pn = postNote(slave, index);
diff --git a/src/js/slaveSummaryHelpers.js b/src/js/slaveSummaryHelpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..4340f9ada967832ee42e4b6139743c831a4e8921
--- /dev/null
+++ b/src/js/slaveSummaryHelpers.js
@@ -0,0 +1,1738 @@
+// 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");
+			r.classList.add("ssi");
+			_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);
+		}
+
+		function syncFSData(arcology) {
+			arcology = arcology || V.arcologies[0];
+			for (const fsp of App.Data.misc.FutureSocieties) {
+				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 === "get treatment in the 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 === "get treatment in the 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) {
+			let r = makeSpan(c, "", "pink");
+			if (V.showAgeDetail === 1) {
+				r.textContent += `Age ` + `${slave.actualAge}` + `.`;
+			} else {
+				r.textContent +=  helpers.getNumericRating(data.long.body.age, slave.actualAge);
+			}
+			/*
+			 ** 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) {
+					r.textContent += ` ${slave.physicalAge}` + ` year old body.`;
+				}
+				if (slave.visualAge !== slave.physicalAge) {
+					r.textContent += ` Looks ` + `${slave.visualAge}` + `.`;
+				}
+			} 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.'
+				 */
+				r.textContent += ` Appears to have a ` + `${slave.visualAge}` + ` year old body.`;
+			}
+			if (slave.geneMods.NCS === 1) {
+				makeSpan(r, "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]));
+		}
+
+		/**
+		 * @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]));
+		}
+
+		/**
+		 * @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_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);
+			}
+			makeSpan(c, fetishStr(slave), "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);
+			}
+			makeSpan(c, fetishStr(slave), "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,
+				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
+	};
+}();
diff --git a/src/js/slaveSummaryWidgets.js b/src/js/slaveSummaryWidgets.js
index a5b28aa5561a5c738c92c4416a49d313a728868f..ad5e8a32fa9b911f97961fee7568951fa964107f 100644
--- a/src/js/slaveSummaryWidgets.js
+++ b/src/js/slaveSummaryWidgets.js
@@ -1,4514 +1,1008 @@
-/* eslint-disable camelcase */
+App.UI.SlaveSummaryRenderers = function() {
 
-window.SlaveSummary = (function() {
-	/** @type {DocumentFragment} */
-	let res;
+	const bits = App.UI.SlaveSummaryImpl.bits;
+	const helpers = App.UI.SlaveSummaryImpl.helpers;
+	const data = App.Data.SlaveSummary;
 
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @returns {DocumentFragment} */
-	function SlaveSummary(slave) {
-		res = document.createDocumentFragment();
-		let para = makeParagraph(res);
-
-		if (V.abbreviateDevotion === 1) {
-			short_devotion(slave, para);
-		} else if (V.abbreviateDevotion === 2) {
-			long_devotion(slave, para);
-		}
-		if (slave.fuckdoll === 0) {
-			if (V.abbreviateRules === 1) {
-				short_rules(slave, para);
-			} else if (V.abbreviateRules === 2) {
-				long_rules(slave, para);
+	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);
 			}
-		}
-		if (V.abbreviateDiet === 1) {
-			short_weight(slave, para);
-		} else if (V.abbreviateDiet === 2) {
-			long_weight(slave, para);
-		}
-		if (V.abbreviateDiet === 1) {
-			short_diet(slave, para);
-		} else if (V.abbreviateDiet === 2) {
-			long_diet(slave, para);
-		}
-		if (V.abbreviateHealth === 1) {
-			short_health(slave, para);
-			short_illness(slave, para);
-			short_tired(slave, para);
-		} else if (V.abbreviateHealth === 2) {
-			long_health(slave, para);
-			long_illness(slave, para);
-			long_tired(slave, para);
-		}
-		if (V.abbreviateDrugs === 1) {
-			short_drugs(slave, para);
-		} else if (V.abbreviateDrugs === 2) {
-			long_drugs(slave, para);
-		}
-
-		para = makeParagraph(res);
-		para.classList.add("pink");
+		},
 
-		makeSpan(para, `${capFirstChar(SlaveTitle(slave))}${V.abbreviatePhysicals === 2 ? '.' : ''}`, ["coral", "strong"]);
-		if (V.seeRace === 1 && V.abbreviateRace !== 0) {
-			makeSpan(para, V.abbreviateRace === 1 ? short_race(slave) : long_race(slave), "tan");
-		}
-		if (V.abbreviateNationality !== 0) {
-			makeSpan(para, V.abbreviateNationality === 1 ? short_nationality(slave) : long_nationality(slave), "tan");
-		}
-		makeSpan(para, V.abbreviatePhysicals === 1 ? short_skin(slave) : `${capFirstChar(slave.skin)} skin.`);
-		if (V.abbreviateGenitalia === 1) {
-			short_genitals(slave, para);
-		} else if (V.abbreviateGenitalia === 2) {
-			long_genitals(slave, para);
-		}
-		if (V.abbreviatePhysicals === 1) {
-			short_age(slave, para);
-			short_face(slave, para);
-			short_eyes(slave, para);
-			short_ears(slave, para);
-			if (slave.markings !== "none") {
-				makeSpan(para, "Markings");
+		/**
+		 * @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;
 			}
-			short_lips(slave, para);
-			short_teeth(slave, para);
-			short_muscles(slave, para);
-			addText(para, App.Desc.shortLimbs(slave));
-			short_voice(slave, para);
-			short_tits_ass(slave, para);
-			short_hips(slave, para);
-			short_waist(slave, para);
-			short_implants(slave, para);
-			short_lactation(slave, para);
-			short_mods(slave, para);
-		} else if (V.abbreviatePhysicals === 2) {
-			long_age(slave, para);
-			long_face(slave, para);
-			long_eyes(slave, para);
-			long_ears(slave, para);
-			long_lips(slave, para);
-			long_teeth(slave, para);
-			long_muscles(slave, para);
-			makeSpan(para, App.Desc.longLimbs(slave));
-			long_voice(slave, para);
-			long_tits_ass(slave, para);
-			long_hips(slave, para);
-			long_waist(slave, para);
-			long_implants(slave, para);
-			long_lactation(slave, para);
-			long_mods(slave, para);
-			if (!jQuery.isEmptyObject(slave.brand)) {
-				makeSpan(para, `Branded.`);
+			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;
 			}
-		}
-		if (V.abbreviateHormoneBalance === 1) {
-			if (slave.hormoneBalance <= -21) {
-				makeSpan(para, "HB:M", ["deepskyblue", "strong"]);
-			} else if (slave.hormoneBalance <= 20) {
-				makeSpan(para, "HB:N", ["pink", "strong"]);
-			} else if (slave.hormoneBalance <= 500) {
-				makeSpan(para, "HB:F", ["pink", "strong"]);
+			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;
 			}
-		} else if (V.abbreviateHormoneBalance === 2) {
-			const colorClass = slave.hormoneBalance <= -21 ? "deepskyblue" : "pink";
-			let desc = "";
-			if (slave.hormoneBalance < -400) {
-				desc = `Overwhelmingly masculine`;
-			} else if (slave.hormoneBalance <= -300) {
-				desc = `Extremely masculine`;
-			} else if (slave.hormoneBalance <= -200) {
-				desc = `Heavily masculine`;
-			} else if (slave.hormoneBalance <= -100) {
-				desc = `Very masculine`;
-			} else if (slave.hormoneBalance <= -21) {
-				desc = `Masculine`;
-			} else if (slave.hormoneBalance <= 20) {
-				desc = `Neutral`;
-			} else if (slave.hormoneBalance <= 99) {
-				desc = `Feminine`;
-			} else if (slave.hormoneBalance <= 199) {
-				desc = `Very feminine`;
-			} else if (slave.hormoneBalance <= 299) {
-				desc = `Heavily feminine`;
-			} else if (slave.hormoneBalance <= 399) {
-				desc = `Extremely feminine`;
-			} else if (slave.hormoneBalance <= 500) {
-				desc = `Overwhelmingly feminine`;
+			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(para, desc + " hormone balance.", colorClass);
-		}
+			makeSpan(c, "MaR:" + App.Utils.releaseSummaryShort(slave), styles);
+		},
 
-		para = makeParagraph(res);
 
-		if (V.abbreviateSkills === 1) {
-			short_intelligence(slave, para);
-			short_skills(slave, para);
-			short_prestige(slave, para);
-			short_porn_prestige(slave, para);
-		} else if (V.abbreviateSkills === 2) {
-			long_intelligence(slave, para);
-			long_skills(slave, para);
-			long_prestige(slave, para);
-			long_porn_prestige(slave, para);
-		}
-		if (V.abbreviateMental === 1) {
-			if (slave.fetish !== "mindbroken") {
-				if (slave.fetishKnown === 1) {
-					short_fetish(slave, para);
+		/**
+		 * @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 (slave.attrKnown === 1) {
-					short_attraction(slave, para);
+				if (dickDesc) {
+					helpers.makeSpan(c, dickDesc, "pink");
 				}
 			}
-			if (slave.clitPiercing === 3) {
-				short_smart_fetish(slave, para);
-				short_smart_attraction(slave, para);
+			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"]);
 			}
-			short_behavior_flaw(slave, para);
-			short_sex_flaw(slave, para);
-			short_behavior_quirk(slave, para);
-			short_sex_quirk(slave, para);
-		} else if (V.abbreviateMental === 2) {
+		},
+
+		/**
+		 * @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) {
-					long_fetish(slave, para);
+					b.fetish(slave, c);
 				}
 				if (slave.attrKnown === 1) {
-					long_attraction(slave, para);
+					b.attraction(slave, c);
 				}
 			}
 			if (slave.clitPiercing === 3) {
-				long_smart_fetish(slave, para);
-				long_smart_attraction(slave, para);
-			}
-			long_behavior_flaw(slave, para);
-			long_sex_flaw(slave, para);
-			long_behavior_quirk(slave, para);
-			long_sex_quirk(slave, para);
-		}
-		if (slave.custom.label) {
-			makeSpan(res, `${capFirstChar(slave.custom.label)}.`, ["yellow", "strong"]);
-		}
-		if ((slave.relationship !== 0) || (V.abbreviateClothes === 2) || (V.abbreviateRulesets === 2)) {
-			para = makeParagraph(res);
-		}
-		if (V.abbreviateMental === 1) {
-			makeSpan(para, short_extended_family(slave), "lightgreen");
-			short_clone(slave, para);
-			short_rival(slave, para);
-		} else if (V.abbreviateMental === 2) {
-			long_extended_family(slave, para);
-			long_clone(slave, para);
-			long_rival(slave, para);
-		}
-		if (slave.fuckdoll === 0) {
-			if (V.abbreviateClothes === 2) {
-				const dressingBlock = makeBlock(para);
-				if (slave.choosesOwnClothes === 1) {
-					makeSpan(dressingBlock, `Dressing ${getPronouns(slave).himself}.`);
-				}
-				long_clothes(slave, dressingBlock);
-				long_collar(slave, dressingBlock);
-				long_belly(slave, dressingBlock);
-				if (hasAnyArms(slave)) {
-					long_arms(slave, dressingBlock);
-				}
-				if (hasAnyLegs(slave)) {
-					long_legs(slave, dressingBlock);
-					long_shoes(slave, dressingBlock);
+				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}.`;
 				}
-				long_chastity(slave, dressingBlock);
-				long_vaginal_acc(slave, dressingBlock);
-				long_dick_acc(slave, dressingBlock);
-				long_buttplug(slave, dressingBlock);
 			}
-		}
-		const RABlock = makeBlock(para);
-		rules_assistant(slave, RABlock);
-		if (V.abbreviateOrigins === 2 && slave.origin !== 0) {
-			origins(slave, res);
-		}
-		return res;
-	}
+			helpers.makeSpan(c, tmp ? tmp : slave.nationality.substr(0, 3), "tan");
+		},
 
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 */
-	function short_devotion(slave, c) {
-		if (slave.fetish === "mindbroken") {
-			makeSpan(c, "MB", "mindbroken");
-		} else if (slave.devotion < -95) {
-			makeSpan(c, "Hate", ["devotion", "hateful"], true, slave.devotion);
-		} else if (slave.devotion < -50) {
-			makeSpan(c, "Hate", ["devotion", "hateful"], true, slave.devotion);
-		} else if (slave.devotion < -20) {
-			makeSpan(c, "Res", ["devotion", "resistant"], true, slave.devotion);
-		} else if (slave.devotion <= 20) {
-			makeSpan(c, "Ambiv", ["devotion", "ambivalent"], true, slave.devotion);
-		} else if (slave.devotion <= 50) {
-			makeSpan(c, "Accept", ["devotion", "accept"], true, slave.devotion);
-		} else if (slave.devotion <= 95) {
-			makeSpan(c, "Devo", ["devotion", "devoted"], true, slave.devotion);
-		} else {
-			makeSpan(c, "Wor", ["devotion", "worship"], true, slave.devotion);
-		}
-		if (slave.fetish === "mindbroken") {
-			return;
-		} else if (slave.trust < -95) {
-			makeSpan(c, "ETerr", ["trust", "extremely-terrified"], true, slave.trust);
-		} else if (slave.trust < -50) {
-			makeSpan(c, "Terr", ["trust", "terrified"], true, slave.trust);
-		} else if (slave.trust < -20) {
-			makeSpan(c, "Fright", ["trust", "frightened"], true, slave.trust);
-		} else if (slave.trust <= 20) {
-			makeSpan(c, "Fear", ["trust", "fearful"], true, slave.trust);
-		} else if (slave.trust <= 50) {
-			if (slave.devotion < -20) {
-				makeSpan(c, "Caref", ["defiant", "careful"], true, slave.trust);
-			} else {
-				makeSpan(c, "Caref", ["trust", "careful"], true, slave.trust);
-			}
-		} else if (slave.trust < 95) {
-			if (slave.devotion < -20) {
-				makeSpan(c, "Bold", ["defiant", "bold"], true, slave.trust);
-			} else {
-				makeSpan(c, "Trust", ["trust", "trusting"], true, slave.trust);
-			}
-		} else {
-			if (slave.devotion < -20) {
-				makeSpan(c, "Defiant", ["defiant", "full"], true, slave.trust);
-			} else {
-				makeSpan(c, "VTrust", ["trust", "prof-trusting"], true, slave.trust);
-			}
-		}
-	}
+		/**
+		 * @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));
+		},
 
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_devotion(slave, c) {
-		if (slave.fetish === "mindbroken") {
-			makeSpan(c, "Mindbroken.", "mindbroken");
-		} else if (slave.devotion < -95) {
-			makeSpan(c, "Very hateful", ["devotion", "hateful"], true, slave.devotion);
-		} else if (slave.devotion < -50) {
-			makeSpan(c, "Hateful", ["devotion", "hateful"], true, slave.devotion);
-		} else if (slave.devotion < -20) {
-			makeSpan(c, "Resistant", ["devotion", "resistant"], true, slave.devotion);
-		} else if (slave.devotion <= 20) {
-			makeSpan(c, "Ambivalent", ["devotion", "ambivalent"], true, slave.devotion);
-		} else if (slave.devotion <= 50) {
-			makeSpan(c, "Accepting", ["devotion", "accept"], true, slave.devotion);
-		} else if (slave.devotion <= 95) {
-			makeSpan(c, "Devoted", ["devotion", "devoted"], true, slave.devotion);
-		} else {
-			makeSpan(c, "Worshipful", ["devotion", "worship"], true, slave.devotion);
-		}
-		if (slave.fetish === "mindbroken") {
-			return;
-		} else if (slave.trust < -95) {
-			makeSpan(c, "Extremely terrified", ["trust", "extremely-terrified"], true, slave.trust);
-		} else if (slave.trust < -50) {
-			makeSpan(c, "Terrified", ["trust", "terrified"], true, slave.trust);
-		} else if (slave.trust < -20) {
-			makeSpan(c, "Frightened", ["trust", "frightened"], true, slave.trust);
-		} else if (slave.trust <= 20) {
-			makeSpan(c, "Fearful", ["trust", "fearful"], true, slave.trust);
-		} else if (slave.trust <= 50) {
-			if (slave.devotion < -20) {
-				makeSpan(c, "Careful", ["defiant", "careful"], true, slave.trust);
-			} else {
-				makeSpan(c, "Careful", ["trust", "careful"], true, slave.trust);
-			}
-		} else if (slave.trust <= 95) {
-			if (slave.devotion < -20) {
-				makeSpan(c, "Bold", ["defiant", "bold"], true, slave.trust);
-			} else {
-				makeSpan(c, "Trusting", ["trust", "trusting"], true, slave.trust);
-			}
-		} else {
-			if (slave.devotion < -20) {
-				makeSpan(c, "Defiant", ["defiant", "full"], true, slave.trust);
-			} else {
-				makeSpan(c, "Profoundly trusting", ["trust", "prof-trusting"], true, slave.trust);
-			}
-		}
-	}
+		clothes: function() { },
 
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_rules(slave, c) {
-		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;
+		/**
+		 * @param {App.Entity.SlaveState} slave
+		 * @param {Node} c
+		 * @returns {void}
+		 */
+		rulesets: function(slave, c) {
+			if (slave.useRulesAssistant === 0) {
+				helpers.makeSpan(c, "RA-Exmt", "lightgreen");
 			}
-		}
-		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}
-	 */
-	function long_rules(slave, c) {
-		addText(c, `Living standard: ${slave.rules.living}. `);
-		if (canTalk(slave, false)) {
-			addText(c, `Speech rules: ${slave.rules.speech}. `);
+		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);
 		}
-		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}
-	 */
-	function short_weight(slave, c) {
-		if (slave.weight < -95) {
-			makeSpan(c, "W---", ["red", "strong"], true, slave.weight);
-		} else if (slave.weight < -30) {
-			if (slave.hips < -1) {
-				makeSpan(c, "W--", "strong", true, slave.weight);
-			} else {
-				makeSpan(c, "W--", ["red", "strong>"], true, slave.weight);
-			}
-		} else if (slave.weight < -10) {
-			makeSpan(c, "W-", "strong", true, slave.weight);
-		} else if (slave.weight <= 10) {
-			makeSpan(c, "W", "strong", true, slave.weight);
-		} else if (slave.weight <= 30) {
-			makeSpan(c, "W+", "strong", true, slave.weight);
-		} else if (slave.weight <= 95) {
-			if (slave.hips > 1 || V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "W++", "strong", true, slave.weight);
-			} else {
-				makeSpan(c, "W++", ["red", "strong"], true, slave.weight);
-			}
-		} else if (slave.weight <= 130) {
-			if (slave.hips > 2 || V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "W+++", "strong", true, slave.weight);
-			} else {
-				makeSpan(c, "W+++", ["red", "strong"], true, slave.weight);
+	};
+
+	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");
+				}
 			}
-		} else if (slave.weight <= 160) {
-			if (V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "W++++", "strong", true, slave.weight);
-			} else {
-				makeSpan(c, "W++++", ["red", "strong"], true, slave.weight);
+			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");
 			}
-		} else if (slave.weight <= 190) {
-			if (V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "W+++++", "strong", true, slave.weight);
-			} else {
-				makeSpan(c, "W+++++", ["red", "strong"], true, slave.weight);
+			if (slave.anus === 0) {
+				helpers.makeSpan(c, "Anal virgin.", "lime");
 			}
-		} else {
-			if (V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "W++++++", "strong", true, slave.weight);
-			} else {
-				makeSpan(c, "W++++++", ["red", "strong"], true, slave.weight);
+			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}
-	 */
-	function long_weight(slave, c) {
-		if (slave.weight < -95) {
-			makeSpan(c, "Emaciated", "red", true, slave.weight);
-		} else if (slave.weight < -30) {
-			if (slave.hips < -1) {
-				makeSpan(c, "Model-thin", null, true, slave.weight);
-			} else {
-				makeSpan(c, "Very thin", "red", true, slave.weight);
-			}
-		} else if (slave.weight < -10) {
-			makeSpan(c, "Thin", null, true, slave.weight);
-		} else if (slave.weight <= 10) {
-			makeSpan(c, "Trim", null, true, slave.weight);
-		} else if (slave.weight <= 30) {
-			makeSpan(c, "Plush", null, true, slave.weight);
-		} else if (slave.weight <= 95) {
-			if (slave.hips > 1 || V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "Nicely chubby", null, true, slave.weight);
-			} else {
-				makeSpan(c, "Overweight", "red", true, slave.weight);
+		/**
+		 * @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.)");
+				}
 			}
-		} else if (slave.weight <= 130) {
-			if (slave.hips > 2 || V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "Pleasantly soft and shapely", null, true, slave.weight);
-			} else {
-				makeSpan(c, "Fat", "red", true, slave.weight);
+			if (slave.induce === 1) {
+				makeSpan(c, "Showing signs of early labor.", "orange");
 			}
-		} else if (slave.weight <= 160) {
-			if (V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "Amazingly voluptuous", null, true, slave.weight);
-			} else {
-				makeSpan(c, "Obese", "red", true, slave.weight);
+			if (slave.pubertyXY === 0 && slave.balls > 0) {
+				makeSpan(c, "Has not had first ejaculation.");
 			}
-		} else if (slave.weight <= 190) {
-			if (V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "SSBBW", null, true, slave.weight);
-			} else {
-				makeSpan(c, "Super Obese", "red", true, slave.weight);
+			if (slave.balls > 0 && slave.vasectomy === 1) {
+				makeSpan(c, "Vasectomy.");
 			}
-		} else {
-			if (V.arcologies[0].FSHedonisticDecadence !== "unset") {
-				makeSpan(c, "Perfectly massive", null, true, slave.weight);
-			} else {
-				makeSpan(c, "Dangerously Obese", "red", true, slave.weight);
+			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}
-	 */
-	function short_diet(slave, c) {
-		let diet = makeSpan(null, "", ["teal", "strong"]);
-		switch (slave.diet) {
-			case "restricted":
-				diet.textContent = "Di:W-";
-				break;
-			case "fattening":
-				diet.textContent = "Di:W+";
-				break;
-			case "corrective":
-				diet.textContent = "Di:W=";
-				break;
-			case "XX":
-				diet.textContent = "Di:XX+";
-				break;
-			case "XY":
-				diet.textContent = "Di:XY+";
-				break;
-			case "XXY":
-				diet.textContent = "Di:XXY+";
-				break;
-			case "muscle building":
-				diet.textContent = "Di:M+";
-				break;
-			case "slimming":
-				diet.textContent = "Di:M-";
-				break;
-			case "cum production":
-				diet.textContent = "Di:C+";
-				break;
-			case "cleansing":
-				diet.textContent = "Di:H+";
-				break;
-			case "fertility":
-				diet.textContent = "Di:F+";
-				break;
-		}
-		if (diet.textContent.length > 0) {
-			c.appendChild(diet);
-		}
-		let specialDiet = makeSpan(null, "", ["cyan", "strong"]);
-		if (slave.dietCum === 2) {
-			specialDiet.textContent = "Cum++";
-		} else if (((slave.dietCum === 1) && (slave.dietMilk === 0))) {
-			specialDiet.textContent = "Cum+";
-		} else if (((slave.dietCum === 1) && (slave.dietMilk === 1))) {
-			specialDiet.textContent = "Cum+ Milk+";
-		} else if (((slave.dietCum === 0) && (slave.dietMilk === 1))) {
-			specialDiet.textContent = "Milk+";
-		} else if ((slave.dietMilk === 2)) {
-			specialDiet.textContent = ">Milk++";
-		}
-		if (specialDiet.textContent.length > 0) {
-			c.appendChild(specialDiet);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_diet(slave, c) {
-		let dietDesc = "";
-		switch (slave.diet) {
-			case "restricted":
-				dietDesc = `Dieting.`;
-				break;
-			case "fattening":
-				dietDesc = `Gaining weight.`;
-				break;
-			case "corrective":
-				dietDesc = `Corrective.`;
-				break;
-			case "XX":
-				dietDesc = `Estrogen rich.`;
-				break;
-			case "XY":
-				dietDesc = `Testosterone rich.`;
-				break;
-			case "XXY":
-				dietDesc = `Futanari mix.`;
-				break;
-			case "muscle building":
-				dietDesc = `Pumping iron.`;
-				break;
-			case "slimming":
-				dietDesc = `Slimming down.`;
-				break;
-			case "cum production":
-				dietDesc = `Cum production.`;
-				break;
-			case "cleansing":
-				dietDesc = `Cleansing.`;
-				break;
-			case "fertility":
-				dietDesc = `Fertility.`;
-				break;
-		}
-
-		if (dietDesc) {
-			makeSpan(c, dietDesc, "teal");
-		}
-
-		function specialDiet(text) {
-			addText(c, "Diet base: ");
-			makeSpan(c, text, "cyan");
-		}
-
-		if (slave.dietCum === 2) {
-			specialDiet("Cum Based.");
-		} else if (((slave.dietCum === 1) && (slave.dietMilk === 0))) {
-			specialDiet("Cum Added.");
-		} else if (((slave.dietCum === 1) && (slave.dietMilk === 1))) {
-			specialDiet("Milk & Cum Added.");
-		} else if (((slave.dietCum === 0) && (slave.dietMilk === 1))) {
-			specialDiet("Milk Added.");
-		} else if ((slave.dietMilk === 2)) {
-			specialDiet("Milk Based.");
-		}
-	}
+		},
 
-	/**
-	 * @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 === "get treatment in the clinic") {
-				makeSpan(c, `CSafe`, ["green", "strong"]);
+		/**
+		 * @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}
-	 */
-	function long_health(slave, c) {
-		if (slave.health.health < -90) {
-			makeSpan(c, "On the edge of death", ["red", "strong"], true, slave.health.health);
-		} else if (slave.health.health < -50) {
-			makeSpan(c, "Extremely unhealthy", ["red", "strong"], true, slave.health.health);
-		} else if (slave.health.health < -20) {
-			makeSpan(c, "Unhealthy", ["red", "strong"], true, slave.health.health);
-		} else if (slave.health.health <= 20) {
-			makeSpan(c, "healthy", "yellow", true, slave.health.health);
-		} else if (slave.health.health <= 50) {
-			makeSpan(c, "Very healthy", "green", true, slave.health.health);
-		} else if (slave.health.health <= 90) {
-			makeSpan(c, "Extremely healthy", "green", true, slave.health.health);
-		} else {
-			makeSpan(c, "Unnaturally healthy", "green", true, slave.health.health);
-		}
-		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 === "get treatment in the clinic") {
-				makeSpan(c, `Safe chem levels.`, "green");
+		/**
+		 * @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.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}.`;
+				}
 			}
-		}
-	}
-
-	function short_illness(slave, c) {
-		if (slave.health.illness > 4) {
-			makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness);
-		} else if (slave.health.illness > 3) {
-			makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness);
-		} else 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);
-		}
-	}
-
-	function long_illness(slave, c) {
-		if (slave.health.illness > 4) {
-			makeSpan(c, "Terribly ill", ["red", "strong"], true, slave.health.illness);
-		} else if (slave.health.illness > 3) {
-			makeSpan(c, "Very ill", ["red", "strong"], true, slave.health.illness);
-		} else if (slave.health.illness > 2) {
-			makeSpan(c, "Ill", ["red", "strong"], true, slave.health.illness);
-		} else if (slave.health.illness > 0) {
-			makeSpan(c, "Sick", "yellow", true, slave.health.illness);
-		}
-	}
 
-	function short_tired(slave, c) {
-		if (slave.health.tired > 90) {
-			makeSpan(c, "Exh", ["red", "strong"], true, slave.health.tired);
-		} else if (slave.health.tired > 60) {
-			makeSpan(c, "Tir+", "orange", true, slave.health.tired);
-		} else if (slave.health.tired > 30) {
-			makeSpan(c, "Tir", "yellow", true, slave.health.tired);
-		} else if (slave.health.tired < 0) {
-			makeSpan(c, "Ene", "green", true, slave.health.tired);
-		}
-	}
+			helpers.makeSpan(c, descStr(slave), "tan");
+		},
 
-	function long_tired(slave, c) {
-		if (slave.health.tired > 90) {
-			makeSpan(c, "Exhausted", ["red", "strong"], true, slave.health.tired);
-		} else if (slave.health.tired > 60) {
-			makeSpan(c, "Fatigued", "orange", true, slave.health.tired);
-		} else if (slave.health.tired > 30) {
-			makeSpan(c, "Tired", "yellow", true, slave.health.tired);
-		} else if (slave.health.tired < 0) {
-			makeSpan(c, "Energetic", "green", true, slave.health.tired);
-		}
-	}
+		/**
+		 * @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,
+				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.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);
 
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_drugs(slave, c) {
-		let drugDesc = "";
-		switch (slave.drugs) {
-			case "breast injections":
-				drugDesc = "Boobs+";
-				break;
-			case "intensive breast injections":
-				drugDesc = "Boobs++";
-				break;
-			case "hyper breast injections":
-				drugDesc = "Boobs+++";
-				break;
-			case "nipple enhancers":
-				drugDesc = "Nipple+";
-				break;
-			case "butt injections":
-				drugDesc = "Butt+";
-				break;
-			case "intensive butt injections":
-				drugDesc = "Butt++";
-				break;
-			case "hyper butt injections":
-				drugDesc = "Butt+++";
-				break;
-			case "lip injections":
-				drugDesc = "Lip+";
-				break;
-			case "fertility drugs":
-				drugDesc = "Fert+";
-				break;
-			case "super fertility drugs":
-				drugDesc = "Fert++";
-				break;
-			case "penis enhancement":
-				drugDesc = "Dick+";
-				break;
-			case "intensive penis enhancement":
-				drugDesc = "Dick++";
-				break;
-			case "hyper penis enhancement":
-				drugDesc = "Dick+++";
-				break;
-			case "testicle enhancement":
-				drugDesc = "Balls+";
-				break;
-			case "intensive testicle enhancement":
-				drugDesc = "Balls++";
-				break;
-			case "hyper testicle enhancement":
-				drugDesc = "Balls+++";
-				break;
-			case "psychosuppressants":
-				drugDesc = "Psych-";
-				break;
-			case "psychostimulants":
-				drugDesc = "Psych+";
-				break;
-			case "steroids":
-				drugDesc = "Ster";
-				break;
-			case "female hormone injections":
-				drugDesc = "HormXX++";
-				break;
-			case "male hormone injections":
-				drugDesc = "HormXY++";
-				break;
-			case "hormone enhancers":
-				drugDesc = "Horm+";
-				break;
-			case "hormone blockers":
-				drugDesc = "Horm-";
-				break;
-			case "anti-aging cream":
-				drugDesc = "Age-";
-				break;
-			case "appetite suppressors":
-				drugDesc = "ApSup";
-				break;
-			case "penis atrophiers":
-				drugDesc = "Dick-";
-				break;
-			case "testicle atrophiers":
-				drugDesc = "Balls-";
-				break;
-			case "clitoris atrophiers":
-				drugDesc = "Clit-";
-				break;
-			case "labia atrophiers":
-				drugDesc = "Labia-";
-				break;
-			case "nipple atrophiers":
-				drugDesc = "Nipple-";
-				break;
-			case "lip atrophiers":
-				drugDesc = "Lip-";
-				break;
-			case "breast redistributors":
-				drugDesc = "Breast-";
-				break;
-			case "butt redistributors":
-				drugDesc = "Butt-";
-				break;
-			case "sag-B-gone":
-				drugDesc = "AntiSag";
-				break;
-			case "growth stimulants":
-				drugDesc = "GroStim";
-				break;
-			case "priapism agents":
-				drugDesc = "Erection";
-				break;
-		}
-		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);
+		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);
 
-		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"]);
+		para = helpers.makeParagraph(res);
+		para.classList.add("pink");
+		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.pubertyXY === 0 && slave.balls > 0) {
-			makeSpan(c, "Prepub balls", "strong");
+		if ((slave.relationship !== 0) || (slave.relation !== 0) || (abbrSettings.clothes === 2) || (abbrSettings.rulesets === 2)) {
+			para = helpers.makeParagraph(res);
 		}
-		if (slave.balls > 0 && slave.vasectomy === 1) {
-			makeSpan(c, "Vasect", "strong");
+		delegates.relations(slave, para);
+		if (!slave.fuckdoll) {
+			delegates.clothes(slave, para);
 		}
-		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);
+		const RABlock = helpers.makeBlock(para);
+		delegates.rulesets(slave, RABlock);
+		if (slave.origin !== 0) {
+			delegates.origins(slave, res);
 		}
+		return res;
 	}
 
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_drugs(slave, c) {
-		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
-	 * @returns {string}
-	 */
-	function long_race(slave) {
-		switch (slave.race) {
-			case "white":
-				return `Caucasian.`;
-			case "asian":
-				return `Asian.`;
-			case "indo-aryan":
-				return `Indo-aryan.`;
-			case "latina":
-				return `Latina.`;
-			case "middle eastern":
-				return `Middle Eastern.`;
-			case "black":
-				return `Black.`;
-			case "pacific islander":
-				return `Pacific Islander.`;
-			case "malay":
-				return `Malay.`;
-			case "amerindian":
-				return `Amerindian.`;
-			case "semitic":
-				return `Semitic.`;
-			case "southern european":
-				return `Southern European.`;
-			case "mixed race":
-				return `Mixed race.`;
-			default:
-				return `${slave.race.charAt(0).toUpperCase() + slave.race.slice(1)}.`;
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @returns {string}
-	 */
-	function short_race(slave) {
-		switch (slave.race) {
-			case "white":
-				return `C`;
-			case "asian":
-				return `A`;
-			case "indo-aryan":
-				return `I`;
-			case "latina":
-				return `L`;
-			case "middle eastern":
-				return `ME`;
-			case "black":
-				return `B`;
-			case "pacific islander":
-				return `PI`;
-			case "malay":
-				return `M`;
-			case "amerindian":
-				return `AI`;
-			case "semitic":
-				return `S`;
-			case "southern european":
-				return `SE`;
-			case "mixed race":
-				return `MR`;
-			default:
-				return `${slave.race.charAt(0).toUpperCase() + slave.race.charAt(1) + slave.race.charAt(2)}`;
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @returns {string}
-	 */
-	function short_nationality(slave) {
-		switch (slave.nationality) {
-			case "Afghan":
-				return "Afg";
-			case "Albanian":
-				return "Alb";
-			case "Algerian":
-				return "Alg";
-			case "American":
-				return "USA";
-			case "Andorran":
-				return "And";
-			case "Angolan":
-				return "Ang";
-			case "Antiguan":
-				return "AB";
-			case "Argentinian":
-				return "Arg";
-			case "Armenian":
-				return "Arm";
-			case "Aruban":
-				return "Aru";
-			case "Australian":
-				return "Aus";
-			case "Austrian":
-				return "Aut";
-			case "Azerbaijani":
-				return "Aze";
-			case "Bahamian":
-				return "Bah";
-			case "Bahraini":
-				return "Bah";
-			case "Bangladeshi":
-				return "Bgd";
-			case "Barbadian":
-				return "Bar";
-			case "Belarusian":
-				return "Ber";
-			case "Belgian":
-				return "Bel";
-			case "Belizean":
-				return "Blz";
-			case "Beninese":
-				return "Ben";
-			case "Bermudian":
-				return "Bmd";
-			case "Bhutanese":
-				return "Bhu";
-			case "Bissau-Guinean":
-				return "GB";
-			case "Bolivian":
-				return "Bol";
-			case "Bosnian":
-				return "Bos";
-			case "Brazilian":
-				return "Bra";
-			case "British":
-				return "UK";
-			case "Bruneian":
-				return "Bru";
-			case "Bulgarian":
-				return "Bul";
-			case "Burkinabé":
-				return "BF";
-			case "Burmese":
-				return "Bur";
-			case "Burundian":
-				return "Bnd";
-			case "Cambodian":
-				return "Kam";
-			case "Cameroonian":
-				return "Cam";
-			case "Canadian":
-				return "Can";
-			case "Cape Verdean":
-				return "CV";
-			case "Catalan":
-				return "Cat";
-			case "Central African":
-				return "CAR";
-			case "Chadian":
-				return "Cha";
-			case "Chilean":
-				return "Chl";
-			case "Chinese":
-				return "Chi";
-			case "Colombian":
-				return "Col";
-			case "Comorian":
-				return "Com";
-			case "Congolese":
-				return "RC";
-			case "a Cook Islander":
-				return "CI";
-			case "Costa Rican":
-				return "CR";
-			case "Croatian":
-				return "Cro";
-			case "Cuban":
-				return "Cub";
-			case "Curaçaoan":
-				return "Cur";
-			case "Cypriot":
-				return "Cyp";
-			case "Czech":
-				return "Cze";
-			case "Danish":
-				return "Den";
-			case "Djiboutian":
-				return "Dji";
-			case "Dominican":
-				return "DR";
-			case "Dominiquais":
-				return "Dom";
-			case "Dutch":
-				return "Nld";
-			case "East Timorese":
-				return "ET";
-			case "Ecuadorian":
-				return "Ecu";
-			case "Egyptian":
-				return "Egy";
-			case "Emirati":
-				return "UAE";
-			case "Equatoguinean":
-				return "EG";
-			case "Eritrean":
-				return "Eri";
-			case "Estonian":
-				return "Est";
-			case "Ethiopian":
-				return "Eth";
-			case "Fijian":
-				return "Fij";
-			case "Filipina":
-				return "Phl";
-			case "Finnish":
-				return "Fin";
-			case "French":
-				return "Fra";
-			case "French Guianan":
-				return "FG";
-			case "French Polynesian":
-				return "FP";
-			case "Gabonese":
-				return "Gab";
-			case "Gambian":
-				return "Gam";
-			case "Georgian":
-				return "Geo";
-			case "German":
-				return "Ger";
-			case "Ghanan":
-				return "Gha";
-			case "Greek":
-				return "Gre";
-			case "Greenlandic":
-				return "Grn";
-			case "Grenadian":
-				return "Gda";
-			case "Guamanian":
-				return "Gua";
-			case "Guatemalan":
-				return "Gtm";
-			case "Guinean":
-				return "Gui";
-			case "Guyanese":
-				return "Guy";
-			case "Haitian":
-				return "Hai";
-			case "Honduran":
-				return "Hon";
-			case "Hungarian":
-				return "Hun";
-			case "I-Kiribati":
-				return "Kir";
-			case "Icelandic":
-				return "Ice";
-			case "Indian":
-				return "Ind";
-			case "Indonesian":
-				return "Idn";
-			case "Iranian":
-				return "Irn";
-			case "Iraqi":
-				return "Irq";
-			case "Irish":
-				return "Irl";
-			case "Israeli":
-				return "Isr";
-			case "Italian":
-				return "Ita";
-			case "Ivorian":
-				return "IC";
-			case "Jamaican":
-				return "Jam";
-			case "Japanese":
-				return "Jpn";
-			case "Jordanian":
-				return "Jor";
-			case "Kazakh":
-				return "Kaz";
-			case "Kenyan":
-				return "Ken";
-			case "Kittitian":
-				return "SKN";
-			case "Korean":
-				return "Kor";
-			case "Kosovan":
-				return "Kos";
-			case "Kurdish":
-				return "Kur";
-			case "Kuwaiti":
-				return "Kuw";
-			case "Kyrgyz":
-				return "Kyr";
-			case "Laotian":
-				return "Lao";
-			case "Latvian":
-				return "Lat";
-			case "Lebanese":
-				return "Lbn";
-			case "Liberian":
-				return "Lib";
-			case "Libyan":
-				return "Lby";
-			case "a Liechtensteiner":
-				return "Lie";
-			case "Lithuanian":
-				return "Lit";
-			case "Luxembourgian":
-				return "Lux";
-			case "Macedonian":
-				return "Mac";
-			case "Malagasy":
-				return "Mad";
-			case "Malawian":
-				return "Mwi";
-			case "Malaysian":
-				return "Mys";
-			case "Maldivian":
-				return "Mdv";
-			case "Malian":
-				return "Mal";
-			case "Maltese":
-				return "Mlt";
-			case "Marshallese":
-				return "MI";
-			case "Mauritanian":
-				return "Mta";
-			case "Mauritian":
-				return "Mts";
-			case "Mexican":
-				return "Mex";
-			case "Micronesian":
-				return "FSM";
-			case "Moldovan":
-				return "Mol";
-			case "Monégasque":
-				return "Mnc";
-			case "Mongolian":
-				return "Mon";
-			case "Montenegrin":
-				return "Mng";
-			case "Moroccan":
-				return "Mor";
-			case "Mosotho":
-				return "Les";
-			case "Motswana":
-				return "Bot";
-			case "Mozambican":
-				return "Moz";
-			case "Namibian":
-				return "Nam";
-			case "Nauruan":
-				return "Nau";
-			case "Nepalese":
-				return "Npl";
-			case "New Caledonian":
-				return "NC";
-			case "a New Zealander":
-				return "NZ";
-			case "Ni-Vanuatu":
-				return "Van";
-			case "Nicaraguan":
-				return "Nic";
-			case "Nigerian":
-				return "Nga";
-			case "Nigerien":
-				return "Ngr";
-			case "Niuean":
-				return "Niu";
-			case "Norwegian":
-				return "Nor";
-			case "Omani":
-				return "Omn";
-			case "Pakistani":
-				return "Pak";
-			case "Palauan":
-				return "Plu";
-			case "Palestinian":
-				return "Pal";
-			case "Panamanian":
-				return "Pan";
-			case "Papua New Guinean":
-				return "PNG";
-			case "Paraguayan":
-				return "Par";
-			case "Peruvian":
-				return "Per";
-			case "Polish":
-				return "Pol";
-			case "Portuguese":
-				return "Por";
-			case "Puerto Rican":
-				return "PR";
-			case "Qatari":
-				return "Qat";
-			case "Romanian":
-				return "Rom";
-			case "Russian":
-				return "Rus";
-			case "Rwandan":
-				return "Rwa";
-			case "Sahrawi":
-				return "Sah";
-			case "Saint Lucian":
-				return "SL";
-			case "Salvadoran":
-				return "ES";
-			case "Sammarinese":
-				return "SM";
-			case "Samoan":
-				return "Sam";
-			case "São Toméan":
-				return "STP";
-			case "Saudi":
-				return "Sau";
-			case "Scottish":
-				return "Sco";
-			case "Senegalese":
-				return "Sen";
-			case "Serbian":
-				return "Srb";
-			case "Seychellois":
-				return "Sey";
-			case "Sierra Leonean":
-				return "Sie";
-			case "Singaporean":
-				return "Sng";
-			case "Slovak":
-				return "Svk";
-			case "Slovene":
-				return "Svn";
-			case "a Solomon Islander":
-				return "SI";
-			case "Somali":
-				return "Som";
-			case "South African":
-				return "RSA";
-			case "South Sudanese":
-				return "SS";
-			case "Spanish":
-				return "Spa";
-			case "Sri Lankan":
-				return "Sri";
-			case "Sudanese":
-				return "Sud";
-			case "Surinamese":
-				return "Sur";
-			case "Swazi":
-				return "Swa";
-			case "Swedish":
-				return "Swe";
-			case "Swiss":
-				return "Swi";
-			case "Syrian":
-				return "Syr";
-			case "Taiwanese":
-				return "Tai";
-			case "Tajik":
-				return "Taj";
-			case "Tanzanian":
-				return "Tza";
-			case "Thai":
-				return "Tha";
-			case "Tibetan":
-				return "Tib";
-			case "Togolese":
-				return "Tog";
-			case "Tongan":
-				return "Ton";
-			case "Trinidadian":
-				return "TT";
-			case "Tunisian":
-				return "Tun";
-			case "Turkish":
-				return "Tur";
-			case "Turkmen":
-				return "Tkm";
-			case "Tuvaluan":
-				return "Tuv";
-			case "Ugandan":
-				return "Uga";
-			case "Ukrainian":
-				return "Ukr";
-			case "Uruguayan":
-				return "Uru";
-			case "Uzbek":
-				return "Uzb";
-			case "Vatican":
-				return "VC";
-			case "Venezuelan":
-				return "Ven";
-			case "Vietnamese":
-				return "Vnm";
-			case "Vincentian":
-				return "SVG";
-			case "Yemeni":
-				return "Yem";
-			case "Zairian":
-				return "DRC";
-			case "Zambian":
-				return "Zam";
-			case "Zimbabwean":
-				if (slave.race === "white") {
-					return `Rho`;
-				} else {
-					return `Zwe`;
-				}
-			case "Ancient Chinese Revivalist":
-				return `Chi Rev`;
-			case "Ancient Egyptian Revivalist":
-				return `Egy Rev`;
-			case "Arabian Revivalist":
-				return `Ara Rev`;
-			case "Aztec Revivalist":
-				return `Azt Rev`;
-			case "Edo Revivalist":
-				return `Edo Rev`;
-			case "Roman Revivalist":
-				return `Rom Rev`;
-			case "":
-			case "none":
-			case "slave":
-			case "Stateless":
-				return "None";
-			default:
-				return `${slave.nationality.charAt(0) + slave.nationality.charAt(1) + slave.nationality.charAt(2)}`;
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @returns {string}
-	 */
-	function long_nationality(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}.`;
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @returns {string}
-	 */
-	function short_skin(slave) {
-		switch (slave.skin) {
-			case "pure white":
-				return `P. Whi`;
-			case "extremely fair":
-				return `E. Fai`;
-			case "very fair":
-				return `V. Fai`;
-			case "extremely pale":
-				return `E. Pal`;
-			case "very pale":
-				return `V. Pal`;
-			case "light brown":
-				return `L. Br`;
-			case "dark brown":
-				return `D. Br`;
-			case "light olive":
-				return `L. Oli`;
-			case "dark olive":
-				return `D. Oli`;
-			case "light beige":
-				return `L. Bei`;
-			case "dark beige":
-				return `D. Bei`;
-			case "tan":
-				return `Tan`;
-			case "bronze":
-				return `Bron`;
-			case "ebony":
-				return `Ebon`;
-			case "pure black":
-				return `P. Bla`;
-			case "dark":
-			case "fair":
-			case "pale":
-				return `${slave.skin.charAt(0).toUpperCase() + slave.skin.slice(1)}`;
-			default:
-				return `${slave.skin.charAt(0).toUpperCase() + slave.skin.charAt(1) + slave.skin.charAt(2)}`;
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_genitals(slave, c) {
-		if (slave.dick > 0) {
-			let dickDesc = "";
-			if (slave.balls === 0) {
-				dickDesc += `Geld`;
-			}
-			if ((slave.dick > 8) && (slave.balls > 8)) {
-				dickDesc += `Junk+++`;
-			} else if ((slave.dick > 5) && (slave.balls > 5)) {
-				dickDesc += `Junk++`;
-			} else if ((slave.dick > 4) && (slave.balls > 4)) {
-				dickDesc += `Junk+`;
-			} else if ((slave.dick > 3) && (slave.balls > 3)) {
-				dickDesc += `Junk`;
-			} else if (slave.dick > 8) {
-				dickDesc += `Dick+++`;
-			} else if (slave.dick > 5) {
-				dickDesc += `Dick++`;
-			} else if (slave.dick > 4) {
-				dickDesc += `Dick+`;
-			} else if (slave.dick > 3) {
-				dickDesc += `Dick`;
-			} else if (slave.balls > 10) {
-				dickDesc += `Balls+++`;
-			} else if (slave.balls > 5) {
-				dickDesc += `Balls++`;
-			} else if (slave.balls > 4) {
-				dickDesc += `Balls+`;
-			} else if (slave.balls > 3) {
-				dickDesc += `Balls`;
-			}
-			if (dickDesc.length) {
-				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");
-		}
-		let holesDesc = "";
-		if ((slave.vagina > 3) && (slave.anus > 3)) {
-			holesDesc += `V++A++`;
-		} else if ((slave.vagina > 2) && (slave.anus > 2)) {
-			holesDesc += `V+A+`;
-		} else if (slave.vagina > 3) {
-			holesDesc += `V++`;
-		} else if (slave.vagina > 2) {
-			holesDesc += `V+`;
-		} else if (slave.anus > 3) {
-			holesDesc += `A++`;
-		} else if (slave.anus > 2) {
-			holesDesc += `A+`;
-		}
-		if (holesDesc.length) {
-			makeSpan(c, holesDesc, "pink");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_genitals(slave, c) {
-		if (slave.dick > 0) {
-			let dickDesc = "";
-			if (slave.balls === 0) {
-				dickDesc += 'Gelded.';
-			}
-			if ((slave.dick > 8) && (slave.balls > 8)) {
-				dickDesc += `Hyper dick & balls.`;
-			} else if ((slave.dick > 5) && (slave.balls > 5)) {
-				dickDesc += `Monster dick & balls.`;
-			} else if ((slave.dick > 4) && (slave.balls > 4)) {
-				dickDesc = `Huge dick & balls.`;
-			} else if ((slave.dick > 3) && (slave.balls > 3)) {
-				dickDesc = `Big dick & balls.`;
-			} else if (slave.dick > 8) {
-				dickDesc = `Hyper dong.`;
-			} else if (slave.dick > 5) {
-				dickDesc = `Monster dong.`;
-			} else if (slave.dick > 4) {
-				dickDesc = `Huge dick.`;
-			} else if (slave.dick > 3) {
-				dickDesc = `Big dick.`;
-			} else if (slave.balls > 8) {
-				dickDesc = `Hyper balls.`;
-			} else if (slave.balls > 5) {
-				dickDesc = `Monstrous balls.`;
-			} else if (slave.balls > 4) {
-				dickDesc = `Huge balls.`;
-			} else if (slave.balls > 3) {
-				dickDesc = `Big balls.`;
-			}
-			if (dickDesc.length) {
-				makeSpan(c, dickDesc, "pink");
-			}
-		}
-		if (slave.vagina === 0) {
-			makeSpan(c, "Virgin.", "lime");
-		} else if ((slave.pregKnown === 1) && canWalk(slave) && (slave.clothes === "no clothing" || slave.clothes === "body oil") && (slave.shoes === "none")) {
-			makeSpan(c, "Naked, barefoot, and pregnant.", "pink");
-		}
-		if (slave.anus === 0) {
-			makeSpan(c, "Anal virgin.", "lime");
-		}
-		let holesDesc = "";
-		if ((slave.vagina > 3) && (slave.anus > 3)) {
-			holesDesc += `Blown out holes.`;
-		} else if ((slave.vagina > 2) && (slave.anus > 2)) {
-			holesDesc += `High mileage.`;
-		} else if (slave.vagina > 3) {
-			holesDesc += `Cavernous pussy.`;
-		} else if (slave.vagina > 2) {
-			holesDesc += `Loose pussy.`;
-		} else if (slave.anus > 3) {
-			holesDesc += `Permagaped anus.`;
-		} else if (slave.anus > 2) {
-			holesDesc += `Gaping anus.`;
-		}
-		if (holesDesc.length) {
-			makeSpan(c, holesDesc, "pink");
-		}
-	}
-
-	/**
-	 * @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 >= 40) {
-			r.textContent += "40s";
-		} else if (slave.actualAge >= 35) {
-			r.textContent += "Lt30s";
-		} else if (slave.actualAge >= 30) {
-			r.textContent += "Ea30s";
-		} else if (slave.actualAge >= 25) {
-			r.textContent += "Lt20s";
-		} else if (slave.actualAge >= 20) {
-			r.textContent += "Ea20s";
-		} else if (slave.actualAge >= 18) {
-			r.textContent += slave.actualAge.toString();
-		}
-		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) {
-		if (slave.face < -95) {
-			makeSpan(c, "Face---", "red", true, slave.face);
-		} else if (slave.face < -40) {
-			makeSpan(c, "Face--", "red", true, slave.face);
-		} else if (slave.face < -10) {
-			makeSpan(c, "Face-", "red", true, slave.face);
-		} else if (slave.face <= 10) {
-			makeSpan(c, "Face", null, true, slave.face);
-		} else if (slave.face <= 40) {
-			makeSpan(c, "Face+", "pink", true, slave.face);
-		} else if (slave.face <= 95) {
-			makeSpan(c, "Face++", "pink", true, slave.face);
-		} else {
-			makeSpan(c, "Face+++", "pink", true, slave.face);
-		}
-	}
-
-	/**
-	 * @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) {
-		if (slave.lips > 95) {
-			makeSpan(c, "Facepussy");
-		} else if (slave.lips > 70) {
-			makeSpan(c, "Lips+++", null, true, slave.lips);
-		} else if (slave.lips > 40) {
-			makeSpan(c, "Lips++", null, true, slave.lips);
-		} else if (slave.lips > 20) {
-			makeSpan(c, "Lips+", null, true, slave.lips);
-		} else if (slave.lips > 10) {
-			makeSpan(c, "Lips", null, true, slave.lips);
-		} else {
-			makeSpan(c, "Lips-", "red", true, slave.lips);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_teeth(slave, c) {
-		if (slave.teeth === "crooked") {
-			makeSpan(c, "Cr Teeth", "yellow");
-		} else if (slave.teeth === "gapped") {
-			makeSpan(c, "Gap", "yellow");
-		} else if (slave.teeth === "cosmetic braces") {
-			makeSpan(c, `Cos Braces`);
-		} else if (slave.teeth === "straightening braces") {
-			makeSpan(c, `Braces`);
-		} else if (slave.teeth === "removable") {
-			makeSpan(c, `Rem Teeth`);
-		} else if (slave.teeth === "pointy") {
-			makeSpan(c, `Fangs`);
-		} else if (slave.teeth === "baby") {
-			makeSpan(c, `Baby`);
-		} else if (slave.teeth === "mixed") {
-			makeSpan(c, `Mixed`);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_muscles(slave, c) {
-		if (slave.muscles > 95) {
-			makeSpan(c, "Musc++", undefined, true, slave.muscles);
-		} else if (slave.muscles > 50) {
-			makeSpan(c, "Musc+", undefined, true, slave.muscles);
-		} else if (slave.muscles > 30) {
-			makeSpan(c, "Fit", undefined, true, slave.muscles);
-		} else if (slave.muscles > 5) {
-			makeSpan(c, "Toned", undefined, true, slave.muscles);
-		} else if (slave.muscles > -6) {
-			makeSpan(c, "Soft", undefined, true, slave.muscles);
-		} else if (slave.muscles > -31) {
-			if (V.arcologies[0].FSPhysicalIdealist === "unset") {
-				makeSpan(c, "Weak", "red", true, slave.muscles);
-			} else {
-				makeSpan(c, "Soft", undefined, true, slave.muscles);
-			}
-		} else if (slave.muscles > -96) {
-			if (V.arcologies[0].FSPhysicalIdealist === "unset") {
-				makeSpan(c, "Weak+", "red", true, slave.muscles);
-			} else {
-				makeSpan(c, "Soft+", undefined, true, slave.muscles);
-			}
-		} else {
-			makeSpan(c, "Weak++", "red", true, slave.muscles);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_voice(slave, c) {
-		if (slave.voice === 0) {
-			makeSpan(c, "Mute", "red");
-		} else {
-			if (slave.accent === 3) {
-				makeSpan(c, "Acc--", "red");
-			} else if (slave.accent === 2) {
-				makeSpan(c, "Acc-");
-			} else if (slave.accent === 4) {
-				makeSpan(c, "Acc--");
-			} else if (slave.accent === 1) {
-				makeSpan(c, "Acc", "pink");
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_tits_ass(slave, c) {
-		let styles = "pink";
-		if ((slave.boobs >= 12000) && (slave.butt > 9)) {
-			makeSpan(c, "T&A+++", styles);
-		} else if ((slave.boobs > 4000) && (slave.butt > 8)) {
-			makeSpan(c, "T&A++", styles);
-		} else if ((slave.boobs > 2000) && (slave.butt > 6)) {
-			makeSpan(c, "T&A+", styles);
-		} else if ((slave.boobs > 800) && (slave.butt > 4)) {
-			makeSpan(c, "T&A", styles);
-		} else if ((slave.boobs < 500) && (slave.butt < 3) && (slave.weight <= 10) && (slave.muscles <= 30)) {
-			makeSpan(c, "Girlish", styles);
-		} else if (slave.boobs >= 12000) {
-			makeSpan(c, "Boobs+++", styles);
-		} else if (slave.boobs > 4000) {
-			makeSpan(c, "Boobs++", styles);
-		} else if (slave.boobs > 2000) {
-			makeSpan(c, "Boobs+", styles);
-		} else if (slave.boobs > 800) {
-			makeSpan(c, "Boobs", styles);
-		} else if (slave.butt > 9) {
-			makeSpan(c, "Ass+++", styles);
-		} else if (slave.butt > 8) {
-			makeSpan(c, "Ass++", styles);
-		} else if (slave.butt > 6) {
-			makeSpan(c, "Ass+", styles);
-		} else if (slave.butt > 4) {
-			makeSpan(c, "Ass", styles);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_hips(slave, c) {
-		let desc = "";
-		if (slave.hips < -1) {
-			if (slave.butt > 2 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) {
-				desc = `Disp+`;
-			}
-		} else if (slave.hips < 0) {
-			if (slave.butt > 4 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) {
-				desc = `Disp+`;
-			}
-		} else if (slave.hips > 2) {
-			if (slave.butt <= 8) {
-				desc = `Disp-`;
-			}
-		} else if (slave.hips > 1) {
-			if (slave.butt <= 3 && (V.arcologies[0].FSSlimnessEnthusiast === "unset" || (slave.boobs >= 500))) {
-				desc = `Disp-`;
-			}
-		} else if (slave.hips > 0) {
-			if (slave.butt > 8) {
-				if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) {
-					desc = `Disp+`;
-				}
-			} else if (slave.butt <= 2 && (V.arcologies[0].FSSlimnessEnthusiast === "unset" || (slave.boobs >= 500))) {
-				desc = `Disp-`;
-			}
-		} else {
-			if (slave.butt > 6) {
-				if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) {
-					desc = `Disp+`;
-				}
-			} else if (slave.butt <= 1 && (V.arcologies[0].FSSlimnessEnthusiast === "unset" || (slave.boobs >= 500))) {
-				desc = `Disp-`;
-			}
-		}
-		if (desc) {
-			makeSpan(c, desc, "red");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_waist(slave, c) {
-		if (slave.waist > 95) {
-			makeSpan(c, "Wst---", "red", false, slave.waist);
-		} else if (slave.waist > 40) {
-			makeSpan(c, "Wst--", "red", false, slave.waist);
-		} else if (slave.waist > 10) {
-			makeSpan(c, "Wst-", "red", false, slave.waist);
-		} else if (slave.waist >= -10) {
-			makeSpan(c, "Wst", undefined, false, slave.waist);
-		} else if (slave.waist >= -40) {
-			makeSpan(c, "Wst+", "pink", false, slave.waist);
-		} else if (slave.waist >= -95) {
-			makeSpan(c, "Wst++", "pink", false, slave.waist);
-		} else {
-			makeSpan(c, "Wst+++", "pink", false, slave.waist);
-		}
-	}
-
-	/**
-	 * @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 long_age(slave, c) {
-		let r = makeSpan(c, "", "pink");
-		if (V.showAgeDetail === 1) {
-			r.textContent += `Age ` + `${slave.actualAge}` + `.`;
-		} else if (slave.actualAge >= 40) {
-			r.textContent += `Forties.`;
-		} else if (slave.actualAge >= 35) {
-			r.textContent += `Late thirties.`;
-		} else if (slave.actualAge >= 30) {
-			r.textContent += `Early thirties.`;
-		} else if (slave.actualAge >= 25) {
-			r.textContent += `Late twenties.`;
-		} else if (slave.actualAge >= 20) {
-			r.textContent += `Early twenties.`;
-		} else if (slave.actualAge >= 19) {
-			r.textContent += `Nineteen.`;
-		} else if (slave.actualAge >= 18) {
-			r.textContent += `Eighteen.`;
-		} else {
-			r.textContent += `Underage.`;
-		}
-		/*
-		 ** 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) {
-				r.textContent += ` ${slave.physicalAge}` + ` year old body.`;
-			}
-			if (slave.visualAge !== slave.physicalAge) {
-				r.textContent += ` Looks ` + `${slave.visualAge}` + `.`;
-			}
-		} 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.'
-			 */
-			r.textContent += ` Appears to have a ` + `${slave.visualAge}` + ` year old body.`;
-		}
-		if (slave.geneMods.NCS === 1) {
-			makeSpan(r, "NCS", "orange");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_face(slave, c) {
-		if (slave.face < -95) {
-			makeSpan(c, `Very ugly ${slave.faceShape} face`, "red", true, slave.face);
-		} else if (slave.face < -40) {
-			makeSpan(c, `Ugly ${slave.faceShape} face`, "red", true, slave.face);
-		} else if (slave.face < -10) {
-			makeSpan(c, `Unattractive ${slave.faceShape} face`, "red", true, slave.face);
-		} else if (slave.face <= 10) {
-			makeSpan(c, `Average ${slave.faceShape} face`, null, true, slave.face);
-		} else if (slave.face <= 40) {
-			makeSpan(c, `Attractive ${slave.faceShape} face`, "pink", true, slave.face);
-		} else if (slave.face <= 95) {
-			makeSpan(c, `Beautiful ${slave.faceShape} face`, "pink", true, slave.face);
-		} else {
-			makeSpan(c, `Very beautiful ${slave.faceShape} face`, "pink", 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) {
-		if (slave.lips > 95) {
-			makeSpan(c, "Facepussy", undefined, true, slave.lips);
-		} else if (slave.lips > 70) {
-			makeSpan(c, "Huge lips", undefined, true, slave.lips);
-		} else if (slave.lips > 40) {
-			makeSpan(c, "Big lips", undefined, true, slave.lips);
-		} else if (slave.lips > 20) {
-			makeSpan(c, "Pretty lips", undefined, true, slave.lips);
-		} else if (slave.lips > 10) {
-			makeSpan(c, "Normal lips", undefined, true, slave.lips);
-		} else {
-			makeSpan(c, "Thin lips", "red", true, slave.lips);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_teeth(slave, c) {
-		if (slave.teeth === "crooked") {
-			makeSpan(c, "Crooked teeth.", "yellow");
-		} else if (slave.teeth === "gapped") {
-			makeSpan(c, "Tooth gap.", "yellow");
-		} else if (slave.teeth === "cosmetic braces") {
-			makeSpan(c, "Cosmetic braces.");
-		} else if (slave.teeth === "straightening braces") {
-			makeSpan(c, "Braces.");
-		} else if (slave.teeth === "removable") {
-			makeSpan(c, "Removable teeth.");
-		} else if (slave.teeth === "pointy") {
-			makeSpan(c, "Sharp fangs.");
-		} else if (slave.teeth === "baby") {
-			makeSpan(c, "Baby teeth.");
-		} else if (slave.teeth === "mixed") {
-			makeSpan(c, "Mixed teeth.");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_muscles(slave, c) {
-		if (slave.muscles > 95) {
-			makeSpan(c, "Hugely muscular", undefined, true, slave.muscles);
-		} else if (slave.muscles > 50) {
-			makeSpan(c, "Muscular", undefined, true, slave.muscles);
-		} else if (slave.muscles > 30) {
-			makeSpan(c, "Fit", undefined, true, slave.muscles);
-		} else if (slave.muscles > 5) {
-			makeSpan(c, "Toned", undefined, true, slave.muscles);
-		} else if (slave.muscles > -6) {
-			makeSpan(c, "Soft", undefined, true, slave.muscles);
-		} else if (slave.muscles > -31) {
-			if (V.arcologies[0].FSPhysicalIdealist === "unset") {
-				makeSpan(c, "Weak", "red", true, slave.muscles);
-			} else {
-				makeSpan(c, "Weak", undefined, true, slave.muscles);
-			}
-		} else if (slave.muscles > -96) {
-			if (V.arcologies[0].FSPhysicalIdealist === "unset") {
-				makeSpan(c, "Very weak", "red", true, slave.muscles);
-			} else {
-				makeSpan(c, "Very weak", undefined, true, slave.muscles);
-			}
-		} else {
-			makeSpan(c, "Frail", "red", true, slave.muscles);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_voice(slave, c) {
-		if (slave.voice === 0) {
-			makeSpan(c, "Mute.", "red");
-		} else {
-			if (slave.accent === 3) {
-				makeSpan(c, "Bad accent.", "red");
-			} else if (slave.accent === 4) {
-				makeSpan(c, "No language skills.", "red");
-			} else if (slave.accent === 2) {
-				makeSpan(c, "Accent.");
-			} else if (slave.accent === 1) {
-				makeSpan(c, "Cute accent.", "pink");
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_tits_ass(slave, c) {
-		const styles = "pink";
-		if ((slave.boobs >= 12000) && (slave.butt > 9)) {
-			makeSpan(c, "Hyper T&A.", styles);
-		} else if ((slave.boobs > 4000) && (slave.butt > 8)) {
-			makeSpan(c, "Enormous T&A.", styles);
-		} else if ((slave.boobs > 2000) && (slave.butt > 6)) {
-			makeSpan(c, "Huge T&A.", styles);
-		} else if ((slave.boobs > 800) && (slave.butt > 4)) {
-			makeSpan(c, "Big T&A.", styles);
-		} else if ((slave.boobs < 500) && (slave.butt < 3) && (slave.weight <= 10) && (slave.muscles <= 30)) {
-			makeSpan(c, "Girlish figure.", styles);
-		} else if (slave.boobs >= 12000) {
-			makeSpan(c, "Immobilizing tits.", styles);
-		} else if (slave.boobs > 4000) {
-			makeSpan(c, "Monstrous tits.", styles);
-		} else if (slave.boobs > 2000) {
-			makeSpan(c, "Huge tits.", styles);
-		} else if (slave.boobs > 800) {
-			makeSpan(c, "Big tits.", styles);
-		} else if (slave.butt > 9) {
-			makeSpan(c, "Hyper ass.", styles);
-		} else if (slave.butt > 8) {
-			makeSpan(c, "Titanic ass.", styles);
-		} else if (slave.butt > 6) {
-			makeSpan(c, "Huge ass.", styles);
-		} else if (slave.butt > 4) {
-			makeSpan(c, "Big ass.", styles);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_hips(slave, c) {
-		const styles = "red";
-		if (slave.hips < -1) {
-			if (slave.butt > 2 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) {
-				makeSpan(c, "Disproportionately big butt.", styles);
-			}
-		} else if (slave.hips < 0) {
-			if (slave.butt > 4 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) {
-				makeSpan(c, "Disproportionately big butt.", styles);
-			}
-		} else if (slave.hips > 2) {
-			if (slave.butt <= 8) {
-				makeSpan(c, "Disproportionately small butt.", styles);
-			}
-		} else if (slave.hips > 1) {
-			if (slave.butt <= 3 && ((V.arcologies[0].FSSlimnessEnthusiast === "unset") || (slave.boobs >= 500))) {
-				makeSpan(c, "Disproportionately small butt.", styles);
-			}
-		} else if (slave.hips > 0) {
-			if (slave.butt > 8) {
-				if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) {
-					makeSpan(c, "Disproportionately big butt.", styles);
-				}
-			} else if (slave.butt <= 2 && ((V.arcologies[0].FSSlimnessEnthusiast === "unset") || (slave.boobs >= 500))) {
-				makeSpan(c, "Disproportionately small butt.", styles);
-			}
-		} else {
-			if (slave.butt > 6) {
-				if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) {
-					makeSpan(c, "Disproportionately big butt.", styles);
-				}
-			} else if (slave.butt <= 1 && ((V.arcologies[0].FSSlimnessEnthusiast === "unset") || (slave.boobs >= 500))) {
-				makeSpan(c, "Disproportionately small butt.", styles);
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_waist(slave, c) {
-		if (slave.waist > 95) {
-			makeSpan(c, "Masculine waist", "red", true, slave.waist);
-		} else if (slave.waist > 40) {
-			makeSpan(c, "Ugly waist", "red", true, slave.waist);
-		} else if (slave.waist > 10) {
-			makeSpan(c, "Unattractive waist", "red", true, slave.waist);
-		} else if (slave.waist >= -10) {
-			makeSpan(c, "Average waist", null, true, slave.waist);
-		} else if (slave.waist >= -40) {
-			makeSpan(c, "Feminine waist", "pink", true, slave.waist);
-		} else if (slave.waist >= -95) {
-			makeSpan(c, "Hourglass waist", "pink", true, slave.waist);
-		} else {
-			makeSpan(c, "Absurdly narrow waist", "pink", true, slave.waist);
-		}
-	}
-
-	/**
-	 * @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_intelligence(slave, c) {
-		const intelligence = slave.intelligence + slave.intelligenceImplant;
-		if (slave.fetish === "mindbroken") {
-			return;
-		}
-		let education = "";
-		let naturalIntelligence = "";
-		let styles = undefined;
-		if (slave.intelligenceImplant >= 30) {
-			education = "(e+)";
-		} else if (slave.intelligenceImplant >= 15) {
-			education = "(e)";
-		} else if (slave.intelligenceImplant <= -15) {
-			education = "(e-)";
-		}
-		if (intelligence >= 130) {
-			naturalIntelligence = "I++++";
-			styles = "deepskyblue";
-		} else if (intelligence > 95) {
-			naturalIntelligence = "I+++";
-			styles = "deepskyblue";
-		} else if (intelligence > 50) {
-			naturalIntelligence = "I++";
-			styles = "deepskyblue";
-		} else if (intelligence > 15) {
-			naturalIntelligence = "I+";
-			styles = "deepskyblue";
-		} else if (intelligence >= -15) {
-			naturalIntelligence = "I";
-		} else if (intelligence >= -50) {
-			naturalIntelligence = "I-";
-			styles = "orangered";
-		} else if (intelligence >= -95) {
-			naturalIntelligence = "I--";
-			styles = "orangered";
-		} else {
-			naturalIntelligence = "I---";
-			styles = "orangered";
-		}
-		makeSpan(c, `${naturalIntelligence}${education}`, styles, true, intelligence);
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_skills(slave, c) {
-		let _SSkills = slave.skill.anal + slave.skill.oral;
-		let r = makeSpan(c, "", "aquamarine");
-		if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) {
-			r.textContent += `MSS`;
-		} else {
-			_SSkills += slave.skill.vaginal;
-			_SSkills = Math.trunc(_SSkills);
-			if (_SSkills > 180) {
-				r.textContent += `S++`;
-			} else if ((_SSkills > 120) && (slave.vagina < 0)) {
-				r.textContent += `Sh++`;
-			} else if (_SSkills > 90) {
-				r.textContent += `S+`;
-			} else if (_SSkills > 30) {
-				r.textContent += `S`;
-			} else {
-				r.textContent += `S-`;
-			}
-			if (V.summaryStats) {
-				r.textContent += `[${_SSkills}]`;
-			}
-			r.textContent += " ";
-			if (slave.skill.whoring >= 100) {
-				r.textContent += `W+++`;
-			} else if (slave.skill.whoring > 60) {
-				r.textContent += `W++`;
-			} else if (slave.skill.whoring > 30) {
-				r.textContent += `W+`;
-			} else if (slave.skill.whoring > 10) {
-				r.textContent += `W`;
-			}
-			if (slave.skill.whoring > 10) {
-				if (V.summaryStats) {
-					r.textContent += `[${slave.skill.whoring}]`;
-				}
-			}
-			r.textContent += " ";
-			if (slave.skill.entertainment >= 100) {
-				r.textContent += `E+++`;
-			} else if (slave.skill.entertainment > 60) {
-				r.textContent += `E++`;
-			} else if (slave.skill.entertainment > 30) {
-				r.textContent += `E+`;
-			} else if (slave.skill.entertainment > 10) {
-				r.textContent += `E`;
-			}
-			if (slave.skill.entertainment > 10) {
-				if (V.summaryStats) {
-					r.textContent += `[${slave.skill.entertainment}]`;
-				}
-			}
-		}
-		if (slave.skill.combat > 0) {
-			r.textContent += " C";
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_prestige(slave, c) {
-		if (slave.prestige > 0) {
-			const styles = "green";
-			if (slave.prestige > 2) {
-				makeSpan(c, "Prest++", styles);
-			} else if (slave.prestige === 2) {
-				makeSpan(c, "Prest+", styles);
-			} else if (slave.prestige === 1) {
-				makeSpan(c, "Prest", styles);
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_porn_prestige(slave, c) {
-		if (slave.porn.prestige > 0) {
-			const styles = "green";
-			if (slave.porn.prestige > 2) {
-				makeSpan(c, "PPrest++", styles);
-			} else if (slave.porn.prestige === 2) {
-				makeSpan(c, "PPrest+", styles);
-			} else if (slave.porn.prestige === 1) {
-				makeSpan(c, "PPrest", styles);
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_intelligence(slave, c) {
-		const intelligence = slave.intelligence + slave.intelligenceImplant;
-		if (slave.fetish === "mindbroken") {
-			return;
-		}
-		let education = "";
-		let naturalIntelligence = "";
-		let styles = undefined;
-		if (slave.intelligenceImplant >= 30) {
-			education = ", well educated";
-		} else if (slave.intelligenceImplant >= 15) {
-			education = ", educated";
-		} else if (slave.intelligenceImplant <= -15) {
-			education = ", hindered";
-		}
-		if (intelligence >= 130) {
-			naturalIntelligence = "Genius";
-			styles = "deepskyblue";
-		} else if (intelligence > 95) {
-			naturalIntelligence = "Brilliant";
-			styles = "deepskyblue";
-		} else if (intelligence > 50) {
-			naturalIntelligence = "Very smart";
-			styles = "deepskyblue";
-		} else if (intelligence > 15) {
-			naturalIntelligence = "Smart";
-			styles = "deepskyblue";
-		} else if (intelligence >= -15) {
-			naturalIntelligence = "Average intelligence";
-		} else if (intelligence >= -50) {
-			naturalIntelligence = "Slow";
-			styles = "orangered";
-		} else if (intelligence >= -95) {
-			naturalIntelligence = "Very slow";
-			styles = "orangered";
-		} else {
-			naturalIntelligence = "Moronic";
-			styles = "orangered";
-		}
-		makeSpan(c, `${naturalIntelligence}${education}`, styles, true, intelligence);
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_skills(slave, c) {
-		let _SSkills = (slave.skill.anal + slave.skill.oral);
-		if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) {
-			makeSpan(c, "Masterful Sex Slave.", "aquamarine");
-		} else {
-			let desc;
-			_SSkills += slave.skill.vaginal;
-			if (_SSkills > 180) {
-				desc = "Sex master";
-			} else if ((_SSkills > 120) && (slave.vagina < 0)) {
-				desc = "Masterful shemale";
-			} else if (_SSkills > 90) {
-				desc = "Sexual expert";
-			} else if (_SSkills > 30) {
-				desc = "Sexually skilled";
-			} else {
-				desc = "Sexually unskilled";
-			}
-			if (desc) {
-				makeSpan(c, desc, "aquamarine", true, Math.trunc(_SSkills));
-				desc = "";
-			}
-			if (slave.skill.whoring >= 100) {
-				desc = "Masterful whore";
-			} else if (slave.skill.whoring >= 60) {
-				desc = "Expert whore";
-			} else if (slave.skill.whoring >= 30) {
-				desc = "Skilled whore";
-			} else if (slave.skill.whoring >= 10) {
-				desc = "Basic whore";
-			}
-			if (desc) {
-				makeSpan(c, desc, "aquamarine", true, slave.skill.whoring);
-				desc = "";
-			}
-			if (slave.skill.entertainment >= 100) {
-				desc = "Masterful entertainer";
-			} else if (slave.skill.entertainment >= 60) {
-				desc = "Expert entertainer";
-			} else if (slave.skill.entertainment >= 30) {
-				desc = "Skilled entertainer";
-			} else if (slave.skill.entertainment >= 10) {
-				desc = "Basic entertainer";
-			}
-			if (desc) {
-				makeSpan(c, desc, "aquamarine", true, slave.skill.entertainment);
-				desc = "";
-			}
-		}
-		if (slave.skill.combat > 0) {
-			makeSpan(c, "Trained fighter.", "aquamarine");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_prestige(slave, c) {
-		if (slave.prestige > 0) {
-			const styles = "green";
-			if (slave.prestige > 2) {
-				makeSpan(c, "Extremely prestigious.", styles);
-			} else if (slave.prestige === 2) {
-				makeSpan(c, "Very prestigious.", styles);
-			} else if (slave.prestige === 1) {
-				makeSpan(c, "Prestigious.", styles);
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_porn_prestige(slave, c) {
-		if (slave.porn.prestige > 0) {
-			const styles = "green";
-			if (slave.porn.prestige > 2) {
-				makeSpan(c, "Porn star.", styles);
-			} else if (slave.porn.prestige === 2) {
-				makeSpan(c, "Porn slut.", styles);
-			} else if (slave.porn.prestige === 1) {
-				makeSpan(c, "Porn amateur.", styles);
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_fetish(slave, c) {
-		let descStr = "";
-		switch (slave.fetish) {
-			case "submissive":
-				if (slave.fetishStrength > 95) {
-					descStr = `Sub++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Sub+`;
-				} else {
-					descStr = `Sub`;
-				}
-				break;
-			case "cumslut":
-				if (slave.fetishStrength > 95) {
-					descStr = `Oral++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Oral+`;
-				} else {
-					descStr = `Oral`;
-				}
-				break;
-			case "humiliation":
-				if (slave.fetishStrength > 95) {
-					descStr = `Humil++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Humil+`;
-				} else {
-					descStr = `Humil`;
-				}
-				break;
-			case "buttslut":
-				if (slave.fetishStrength > 95) {
-					descStr = `Anal++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Anal+`;
-				} else {
-					descStr = `Anal`;
-				}
-				break;
-			case "boobs":
-				if (slave.fetishStrength > 95) {
-					descStr = `Boobs++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Boobs+`;
-				} else {
-					descStr = `Boobs`;
-				}
-				break;
-			case "sadist":
-				if (slave.fetishStrength > 95) {
-					descStr = `Sadist++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Sadist+`;
-				} else {
-					descStr = `Sadist`;
-				}
-				break;
-			case "masochist":
-				if (slave.fetishStrength > 95) {
-					descStr = `Pain++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Pain+`;
-				} else {
-					descStr = `Pain`;
-				}
-				break;
-			case "dom":
-				if (slave.fetishStrength > 95) {
-					descStr = `Dom++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Dom+`;
-				} else {
-					descStr = `Dom`;
-				}
-				break;
-			case "pregnancy":
-				if (slave.fetishStrength > 95) {
-					descStr = `Preg++`;
-				} else if (slave.fetishStrength > 60) {
-					descStr = `Preg+`;
-				} else {
-					descStr = `Preg`;
-				}
-				break;
-			default:
-				descStr = `Vanilla`;
-				break;
-		}
-		if (V.summaryStats) {
-			descStr += `[${slave.fetishStrength}]`;
-		}
-		makeSpan(c, descStr, "lightcoral");
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_attraction(slave, c) {
-		if (slave.attrXY <= 5) {
-			makeSpan(c, "XY---", "red", false, slave.attrXY);
-		} else if (slave.attrXY <= 15) {
-			makeSpan(c, "XY--", "red", false, slave.attrXY);
-		} else if (slave.attrXY <= 35) {
-			makeSpan(c, "XY---", "red", false, slave.attrXY);
-		} else if (slave.attrXY <= 65) {
-			makeSpan(c, "XY", undefined, false, slave.attrXY);
-		} else if (slave.attrXY <= 85) {
-			makeSpan(c, "XY+", "green", false, slave.attrXY);
-		} else if (slave.attrXY <= 95) {
-			makeSpan(c, "XY++", "green", false, slave.attrXY);
-		} else if (slave.attrXX > 95) {
-			if (slave.energy <= 95) {
-				makeSpan(c, "Omni!", "green");
-			} else {
-				makeSpan(c, "Omni+Nympho!!", "green");
-			}
-		} else {
-			makeSpan(c, "XY+++", "green", false, slave.attrXY);
-		}
-		if (slave.attrXX <= 5) {
-			makeSpan(c, "XX---", "red", false, slave.attrXX);
-		} else if (slave.attrXX <= 15) {
-			makeSpan(c, "XX--", "red", false, slave.attrXX);
-		} else if (slave.attrXX <= 35) {
-			makeSpan(c, "XX-", "red", false, slave.attrXX);
-		} else if (slave.attrXX <= 65) {
-			makeSpan(c, "XX", undefined, false, slave.attrXX);
-		} else if (slave.attrXX <= 85) {
-			makeSpan(c, "XX+", "green", false, slave.attrXX);
-		} else if (slave.attrXX <= 95) {
-			makeSpan(c, "XX++", "green", false, slave.attrXX);
-		} else if (slave.attrXY <= 95) {
-			makeSpan(c, "XX+++", "green", false, slave.attrXX);
-		}
-		if (slave.energy > 95) {
-			if ((slave.attrXY <= 95) || (slave.attrXX <= 95)) {
-				makeSpan(c, "Nympho!", "green");
-			}
-		} else if (slave.energy > 80) {
-			makeSpan(c, "SD++", "green", false, slave.energy);
-		} else if (slave.energy > 60) {
-			makeSpan(c, "SD+", "green", false, slave.energy);
-		} else if (slave.energy > 40) {
-			makeSpan(c, "SD", "green", false, slave.energy);
-		} else if (slave.energy > 20) {
-			makeSpan(c, "SD-", "red", false, slave.energy);
-		} else {
-			makeSpan(c, "SD--", "red", false, slave.energy);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_smart_fetish(slave, c) {
-		function settingStr() {
-			if (slave.fetishKnown === 1) {
-				if (slave.clitSetting === "off") {
-					return `SP-`;
-				} else if (((slave.fetish !== "submissive") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "submissive")) {
-					return `SP:sub`;
-				} else if (((slave.fetish !== "cumslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "oral")) {
-					return `SP:oral`;
-				} else if (((slave.fetish !== "humiliation") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "humiliation")) {
-					return `SP:humil`;
-				} else if (((slave.fetish !== "buttslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "anal")) {
-					return `SP:anal`;
-				} else if (((slave.fetish !== "boobs") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "boobs")) {
-					return `SP:boobs`;
-				} else if (((slave.fetish !== "sadist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "sadist")) {
-					return `SP:sade`;
-				} else if (((slave.fetish !== "masochist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "masochist")) {
-					return `SP:pain`;
-				} else if (((slave.fetish !== "dom") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "dom")) {
-					return `SP:dom`;
-				} else if (((slave.fetish !== "pregnancy") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "pregnancy")) {
-					return `SP:preg`;
-				} else if (((slave.fetish !== "none") && (slave.clitSetting === "vanilla"))) {
-					return `SP:vanilla`;
-				} else if ((slave.energy <= 95) && (slave.clitSetting === "all")) {
-					return `SP:all`;
-				} else if ((slave.energy > 5) && (slave.clitSetting === "none")) {
-					return `SP:none`;
-				} else if (!["anti-men", "anti-women", "men", "women"].includes(slave.clitSetting)) {
-					return `SP:monitoring`;
-				}
-			} else {
-				switch (slave.clitSetting) {
-					case "off":
-						return `SP-`;
-					case "submissive":
-						return `SP:sub`;
-					case "lesbian":
-						return `SP:les`;
-					case "oral":
-						return `SP:oral`;
-					case "humiliation":
-						return `SP:humil`;
-					case "anal":
-						return `SP:anal`;
-					case "boobs":
-						return `SP:boobs`;
-					case "sadist":
-						return `SP:sade`;
-					case "masochist":
-						return `SP:pain`;
-					case "dom":
-						return `SP:dom`;
-					case "pregnancy":
-						return `SP:pregnancy`;
-					case "vanilla":
-						return `SP:vanilla`;
-					case "all":
-						return `SP:all`;
-					case "none":
-						return `SP:none`;
-				}
-			}
-			return null;
-		}
-		const s = settingStr();
-		if (s) {
-			makeSpan(c, settingStr());
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_smart_attraction(slave, c) {
-		function settingStr() {
-			if (slave.attrKnown === 1) {
-				if (slave.clitSetting === "women") {
-					if (slave.attrXX < 95) {
-						return `SP:women`;
-					} else {
-						return `SP:monitoring`;
-					}
-				} else if (slave.clitSetting === "men") {
-					if (slave.attrXY < 95) {
-						return `SP:men`;
-					} else {
-						return `SP:monitoring`;
-					}
-				} else if (slave.clitSetting === "anti-women") {
-					if (slave.attrXX > 0) {
-						return `SP:anti-women`;
-					} else {
-						return `SP:monitoring`;
-					}
-				} else if (slave.clitSetting === "anti-men") {
-					if (slave.attrXY > 0) {
-						return `SP:anti-men`;
-					} else {
-						return `SP:monitoring`;
-					}
-				}
-			} else {
-				if (slave.clitSetting === "women") {
-					return `SP:women`;
-				} else if (slave.clitSetting === "men") {
-					return `SP:men`;
-				} else if (slave.clitSetting === "anti-women") {
-					return `SP:anti-women`;
-				} else if (slave.clitSetting === "anti-men") {
-					return `SP:anti-men`;
-				}
-			}
-			return null;
-		}
-		const s = settingStr();
-		if (s) {
-			makeSpan(c, settingStr());
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_behavior_flaw(slave, c) {
-		function descStr() {
-			switch (slave.behavioralFlaw) {
-				case "arrogant":
-					return `Arrog`;
-				case "bitchy":
-					return `Bitchy`;
-				case "odd":
-					return `Odd`;
-				case "hates men":
-					return `Men-`;
-				case "hates women":
-					return `Women-`;
-				case "gluttonous":
-					return `Glut`;
-				case "anorexic":
-					return `Ano`;
-				case "devout":
-					return `Dev`;
-				case "liberated":
-					return `Lib`;
-				default:
-					slave.behavioralFlaw = "none";
-					return null;
-			}
-		}
-		const s = descStr();
-		if (s) {
-			makeSpan(c, descStr(), "red");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_sex_flaw(slave, c) {
-		switch (slave.sexualFlaw) {
-			case "hates oral":
-				makeSpan(c, "Oral-", "red");
-				break;
-			case "hates anal":
-				makeSpan(c, "Anal-", "red");
-				break;
-			case "hates penetration":
-				makeSpan(c, "Fuck-", "red");
-				break;
-			case "shamefast":
-				makeSpan(c, "Shame", "red");
-				break;
-			case "idealistic":
-				makeSpan(c, "Ideal", "red");
-				break;
-			case "repressed":
-				makeSpan(c, "Repre", "red");
-				break;
-			case "apathetic":
-				makeSpan(c, "Apath", "red");
-				break;
-			case "crude":
-				makeSpan(c, "Crude", "red");
-				break;
-			case "judgemental":
-				makeSpan(c, "Judge", "red");
-				break;
-			case "cum addict":
-				makeSpan(c, "CumAdd", "yellow");
-				break;
-			case "anal addict":
-				makeSpan(c, "AnalAdd", "yellow");
-				break;
-			case "attention whore":
-				makeSpan(c, "Attention", "yellow");
-				break;
-			case "breast growth":
-				makeSpan(c, "BoobObsess", "yellow");
-				break;
-			case "abusive":
-				makeSpan(c, "Abusive", "yellow");
-				break;
-			case "malicious":
-				makeSpan(c, "Malice", "yellow");
-				break;
-			case "self hating":
-				makeSpan(c, "SelfHatr", "yellow");
-				break;
-			case "neglectful":
-				makeSpan(c, "SelfNeglect", "yellow");
-				break;
-			case "breeder":
-				makeSpan(c, "BreedObsess", "yellow");
-				break;
-			default:
-				slave.sexualFlaw = "none";
-				break;
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_behavior_quirk(slave, c) {
-		function descStr() {
-			switch (slave.behavioralQuirk) {
-				case "confident":
-					return `Confid`;
-				case "cutting":
-					return `Cutting`;
-				case "funny":
-					return `Funny`;
-				case "fitness":
-					return `Fit`;
-				case "adores women":
-					return `Women+`;
-				case "adores men":
-					return `Men+`;
-				case "insecure":
-					return `Insec`;
-				case "sinful":
-					return `Sinf`;
-				case "advocate":
-					return `Advoc`;
-				default:
-					slave.behavioralQuirk = "none";
-					return null;
-			}
-		}
-		const s = descStr();
-		if (s) {
-			makeSpan(c, s, "green");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function short_sex_quirk(slave, c) {
-		function descStr() {
-			switch (slave.sexualQuirk) {
-				case "gagfuck queen":
-					return `Gagfuck`;
-				case "painal queen":
-					return `Painal`;
-				case "strugglefuck queen":
-					return `Struggle`;
-				case "tease":
-					return `Tease`;
-				case "romantic":
-					return `Romantic`;
-				case "perverted":
-					return `Perverted`;
-				case "caring":
-					return `Caring`;
-				case "unflinching":
-					return `Unflinch`;
-				case "size queen":
-					return `SizeQ`;
-				default:
-					slave.sexualQuirk = "none";
-					return null;
-			}
-		}
-		const s = descStr();
-		if (s) {
-			makeSpan(c, s, "green");
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_fetish(slave, c) {
-		function fetishStr() {
-			switch (slave.fetish) {
-				case "submissive":
-					if (slave.fetishStrength > 95) {
-						return "Complete submissive";
-					} else if (slave.fetishStrength > 60) {
-						return "Submissive";
-					} else {
-						return "Submissive tendencies";
-					}
-				case "cumslut":
-					if (slave.fetishStrength > 95) {
-						return "Cumslut";
-					} else if (slave.fetishStrength > 60) {
-						return "Oral fixation";
-					} else {
-						return "Prefers oral";
-					}
-				case "humiliation":
-					if (slave.fetishStrength > 95) {
-						return "Humiliation slut";
-					} else if (slave.fetishStrength > 60) {
-						return "Exhibitionist";
-					} else {
-						return "Interest in humiliation";
-					}
-				case "buttslut":
-					if (slave.fetishStrength > 95) {
-						return "Buttslut";
-					} else if (slave.fetishStrength > 60) {
-						return "Anal fixation";
-					} else {
-						return "Prefers anal";
-					}
-				case "boobs":
-					if (slave.fetishStrength > 95) {
-						return "Boobslut";
-					} else if (slave.fetishStrength > 60) {
-						return "Breast fixation";
-					} else {
-						return "Loves boobs";
-					}
-				case "sadist":
-					if (slave.fetishStrength > 95) {
-						return "Complete sadist";
-					} else if (slave.fetishStrength > 60) {
-						return "Sadist";
-					} else {
-						return "Sadistic tendencies";
-					}
-				case "masochist":
-					if (slave.fetishStrength > 95) {
-						return "Complete masochist";
-					} else if (slave.fetishStrength > 60) {
-						return "Masochist";
-					} else {
-						return "Masochistic tendencies";
-					}
-				case "dom":
-					if (slave.fetishStrength > 95) {
-						return "Complete dom";
-					} else if (slave.fetishStrength > 60) {
-						return "Dominant";
-					} else {
-						return "Dominant tendencies";
-					}
-				case "pregnancy":
-					if (slave.fetishStrength > 95) {
-						return "Pregnancy fetish";
-					} else if (slave.fetishStrength > 60) {
-						return "Pregnancy kink";
-					} else {
-						return "Interest in impregnation";
-					}
-				default:
-					return "Sexually vanilla";
-			}
-		}
-		makeSpan(c, fetishStr(), "lightcoral", true, slave.fetishStrength);
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_attraction(slave, c) {
-		if (slave.attrXY <= 5) {
-			makeSpan(c, "Disgusted by men", "red", true, slave.attrXY);
-		} else if (slave.attrXY <= 15) {
-			makeSpan(c, "Turned off by men", "red", true, slave.attrXY);
-		} else if (slave.attrXY <= 35) {
-			makeSpan(c, "Not attracted to men", "red", true, slave.attrXY);
-		} else if (slave.attrXY <= 65) {
-			makeSpan(c, "Indifferent to men", undefined, true, slave.attrXY);
-		} else if (slave.attrXY <= 85) {
-			makeSpan(c, "Attracted to men", "green", true, slave.attrXY);
-		} else if (slave.attrXY <= 95) {
-			makeSpan(c, "Aroused by men", "green", true, slave.attrXY);
-		} else if (slave.attrXX > 95) {
-			if (slave.energy <= 95) {
-				makeSpan(c, "Omnisexual!", "green");
-			} else {
-				makeSpan(c, "Omnisexual nymphomaniac!", "green");
-			}
-		} else {
-			makeSpan(c, "Passionate about men", "green", true, slave.attrXY);
-		}
-		if (slave.attrXX <= 5) {
-			makeSpan(c, "disgusted by women", "red", true, slave.attrXX);
-		} else if (slave.attrXX <= 15) {
-			makeSpan(c, "turned off by women", "red", true, slave.attrXX);
-		} else if (slave.attrXX <= 35) {
-			makeSpan(c, "not attracted to women", "red", true, slave.attrXX);
-		} else if (slave.attrXX <= 65) {
-			makeSpan(c, "indifferent to women", undefined, true, slave.attrXX);
-		} else if (slave.attrXX <= 85) {
-			makeSpan(c, "attracted to women", "green", true, slave.attrXX);
-		} else if (slave.attrXX <= 95) {
-			makeSpan(c, "aroused by women", "green", true, slave.attrXX);
-		} else if (slave.attrXY <= 95) {
-			makeSpan(c, "passionate about women", "green", true, slave.attrXX);
-		}
-		if (slave.energy > 95) {
-			if ((slave.attrXY <= 95) || (slave.attrXX <= 95)) {
-				makeSpan(c, "Nymphomaniac!", "green");
-			}
-		} else if (slave.energy > 80) {
-			makeSpan(c, "Powerful sex drive", "green", true, slave.energy);
-		} else if (slave.energy > 60) {
-			makeSpan(c, "Good sex drive", "green", true, slave.energy);
-		} else if (slave.energy > 40) {
-			makeSpan(c, "Average sex drive", "yellow", true, slave.energy);
-		} else if (slave.energy > 20) {
-			makeSpan(c, "Poor sex drive", "red", true, slave.energy);
-		} else {
-			makeSpan(c, "No sex drive", "red", true, slave.energy);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_smart_fetish(slave, c) {
-		function descStr() {
-			if (slave.fetishKnown === 1) {
-				if (slave.clitSetting === "off") {
-					return `SP off.`;
-				} else if (((slave.fetish !== "submissive") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "submissive")) {
-					return `SP: submissive.`;
-				} else if (((slave.fetish !== "cumslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "oral")) {
-					return `SP: oral.`;
-				} else if (((slave.fetish !== "humiliation") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "humiliation")) {
-					return `SP: humiliation.`;
-				} else if (((slave.fetish !== "buttslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "anal")) {
-					return `SP: anal.`;
-				} else if (((slave.fetish !== "boobs") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "boobs")) {
-					return `SP: breasts.`;
-				} else if (((slave.fetish !== "sadist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "sadist")) {
-					return `SP: sadism.`;
-				} else if (((slave.fetish !== "masochist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "masochist")) {
-					return `SP: masochism.`;
-				} else if (((slave.fetish !== "dom") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "dom")) {
-					return `SP: dominance.`;
-				} else if (((slave.fetish !== "pregnancy") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "pregnancy")) {
-					return `SP: pregnancy.`;
-				} else if ((slave.fetish !== "none") && (slave.clitSetting === "vanilla")) {
-					return `SP: vanilla.`;
-				} else if ((slave.energy <= 95) && (slave.clitSetting === "all")) {
-					return `SP: all.`;
-				} else if ((slave.energy > 5) && (slave.clitSetting === "none")) {
-					return `SP: none.`;
-				} else if (!["anti-men", "anti-women", "men", "women"].includes(slave.clitSetting)) {
-					return `SP: monitoring.`;
-				}
-			} else {
-				switch (slave.clitSetting) {
-					case "off":
-						return `SP off.`;
-					case "submissive":
-						return `SP: submissive.`;
-					case "oral":
-						return `SP: oral.`;
-					case "humiliation":
-						return `SP: humiliation.`;
-					case "anal":
-						return `SP: anal.`;
-					case "boobs":
-						return `SP: breasts.`;
-					case "sadist":
-						return `SP: sadism.`;
-					case "masochist":
-						return `SP: masochism.`;
-					case "dom":
-						return `SP: dominance.`;
-					case "pregnancy":
-						return `SP: pregnancy.`;
-					case "vanilla":
-						return `SP: vanilla.`;
-					case "all":
-						return `SP: all.`;
-					case "none":
-						return `SP: none.`;
-				}
-			}
-			return null;
-		}
-		const s = descStr();
-		if (s) {
-			makeSpan(c, s);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_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.`);
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_behavior_flaw(slave, c) {
-		function descStr() {
-			switch (slave.behavioralFlaw) {
-				case "arrogant":
-					return `Arrogant.`;
-				case "bitchy":
-					return `Bitchy.`;
-				case "odd":
-					return `Odd.`;
-				case "hates men":
-					return `Hates men.`;
-				case "hates women":
-					return `Hates women.`;
-				case "gluttonous":
-					return `Stress eater.`;
-				case "anorexic":
-					return `Anorexic.`;
-				case "devout":
-					return `Devoutly religious.`;
-				case "liberated":
-					return `Mentally liberated.`;
-				default:
-					slave.behavioralFlaw = "none";
-					return null;
-			}
-		}
-		const s = descStr();
-		if (s) {
-			makeSpan(c, s, "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;
-		}
-	}
+	function displayOptionsFragment() {
+		const res = document.createDocumentFragment();
+
+		// SAH is "Summarized, Abbreviated, Hidden"
+		const SAHOptions = {
+			"Summarized": 2,
+			"Abbreviated": 1,
+			"Hidden": 0
+		};
+
+		function appendOption(name, desc, options) {
+			res.append(App.UI.DOM.Widgets.optionSelector((value) => {
+				V.UI.slaveSummary.abbreviation[name] = value; Engine.play(passage());
+			},
+			options, V.UI.slaveSummary.abbreviation[name], desc));
+		}
+
+		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
+		});
 
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @returns {string}
-	 */
-	function short_extended_family(slave) {
-		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`;
-		}
 		return 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_extended_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])}.`));
-				}
-			}
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_clothes(slave, c) {
-		function descStr() {
-			switch (slave.clothes) {
-				case "attractive lingerie":
-					return `Nice lingerie.`;
-				case "a succubus outfit":
-					return `Succubus outfit.`;
-				case "a string bikini":
-					return `String bikini.`;
-				case "a scalemail bikini":
-					return `Scalemail bikini.`;
-				case "striped panties":
-					return `Striped panties.`;
-				case "a monokini":
-					return `Monokini.`;
-				case "an apron":
-					return `Apron.`;
-				case "a cybersuit":
-					return `Cybersuit.`;
-				case "cutoffs and a t-shirt":
-					return `Cutoffs, t-shirt.`;
-				case "a slutty outfit":
-					return `Slutty outfit.`;
-				case "uncomfortable straps":
-					return `Leather straps.`;
-				case "a fallen nuns habit":
-					return `Slutty habit.`;
-				case "a chattel habit":
-					return `Chattel habit.`;
-				case "a penitent nuns habit":
-					return `Cilice.`;
-				case "slutty jewelry":
-					return `Bangles.`;
-				case "attractive lingerie for a pregnant woman":
-					return `Preggo lingerie.`;
-				case "a maternity dress":
-					return `Maternity dress.`;
-				case "stretch pants and a crop-top":
-					return `Stretch pants, crop-top.`;
-				case "harem gauze":
-					return `Harem outfit.`;
-				case "a slave gown":
-					return `Slave gown.`;
-				case "a halter top dress":
-					return `Halter top dress.`;
-				case "a mini dress":
-					return `Mini dress.`;
-				case "a ball gown":
-					return `Ball gown.`;
-				case "slutty business attire":
-					return `Slutty suit.`;
-				case "nice business attire":
-					return `Nice suit.`;
-				case "a comfortable bodysuit":
-					return `Bodysuit.`;
-				case "a military uniform":
-					return `Military uniform.`;
-				case "a schutzstaffel uniform":
-					return `Schutzstaffel uniform.`;
-				case "a slutty schutzstaffel uniform":
-					return `Slutty Schutzstaffel uniform.`;
-				case "a red army uniform":
-					return `Red Army uniform.`;
-				case "a long qipao":
-					return `Long Qipao.`;
-				case "battlearmor":
-					return `Battlearmor.`;
-				case "a mounty outfit":
-					return `Mounty outfit.`;
-				case "a dirndl":
-					return `Dirndl.`;
-				case "lederhosen":
-					return `Lederhosen.`;
-				case "a biyelgee costume":
-					return `Biyelgee costume.`;
-				case "a leotard":
-					return `Leotard.`;
-				case "a bunny outfit":
-					return `Bunny outfit.`;
-				case "a slutty maid outfit":
-					return `Slutty maid.`;
-				case "a nice maid outfit":
-					return `Nice maid.`;
-				case "a slutty nurse outfit":
-					return `Slutty nurse.`;
-				case "a nice nurse outfit":
-					return `Nice nurse.`;
-				case "a schoolgirl outfit":
-					return `Schoolgirl outfit.`;
-				case "a kimono":
-					return `Kimono.`;
-				case "a hijab and abaya":
-					return `Hijab and abaya.`;
-				case "battledress":
-					return `Battledress.`;
-				case "a latex catsuit":
-					return `Nice latex.`;
-				case "restrictive latex":
-					return `Bondage latex.`;
-				case "conservative clothing":
-					return `Conservative clothing.`;
-				case "chains":
-					return `Chains.`;
-				case "overalls":
-					return `Overalls.`;
-				case "a cheerleader outfit":
-					return `Cheerleader.`;
-				case "clubslut netting":
-					return `Netting.`;
-				case "shibari ropes":
-					return `Shibari.`;
-				case "Western clothing":
-					return `Chaps.`;
-				case "body oil":
-					return `Body oil.`;
-				case "a toga":
-					return `Toga.`;
-				case "a huipil":
-					return `Huipil.`;
-				case "a slutty qipao":
-					return `Slutty qipao.`;
-				case "spats and a tank top":
-					return `Spats, tank top.`;
-				case "a burkini":
-					return `Burkini.`;
-				case "a niqab and abaya":
-					return `Niqab and abaya.`;
-				case "a klan robe":
-					return `Klan robe.`;
-				case "a hijab and blouse":
-					return `Hijab and blouse.`;
-				case "a burqa":
-					return `Burqa.`;
-				case "kitty lingerie":
-					return `Kitty lingerie.`;
-				case "a tube top and thong":
-					return `Tube top, thong.`;
-				case "a button-up shirt and panties":
-					return `Button-up shirt, panties.`;
-				case "a gothic lolita dress":
-					return `Gothic lolita dress.`;
-				case "a hanbok":
-					return `Hanbok.`;
-				case "a bra":
-					return `Nice bra.`;
-				case "a button-up shirt":
-					return `Nice button-up shirt.`;
-				case "a nice pony outfit":
-					return `Nice pony outfit.`;
-				case "a sweater":
-					return `Nice sweater.`;
-				case "a tank-top":
-					return `Nice tank-top.`;
-				case "a thong":
-					return `Nice thong.`;
-				case "a tube top":
-					return `Nice tube top.`;
-				case "a one-piece swimsuit":
-					return `Swimsuit.`;
-				case "a police uniform":
-					return `Police uniform.`;
-				case "a striped bra":
-					return `Striped bra.`;
-				case "a skimpy loincloth":
-					return `Skimpy loincloth.`;
-				case "a slutty klan robe":
-					return `Slutty klan robe.`;
-				case "a slutty pony outfit":
-					return `Slutty pony outfit.`;
-				case "a Santa dress":
-					return `Santa dress.`;
-				case "a sports bra":
-					return `Sports bra.`;
-				case "a sweater and panties":
-					return `Sweater, panties.`;
-				case "a t-shirt":
-					return `T-shirt.`;
-				case "a tank-top and panties":
-					return `Tank-top, panties.`;
-				case "a t-shirt and thong":
-					return `Thong, t-shirt.`;
-				case "an oversized t-shirt and boyshorts":
-					return `Over-sized t-shirt, boy shorts.`;
-				case "an oversized t-shirt":
-					return `Nice over-sized t-shirt.`;
-				case "a t-shirt and jeans":
-					return `Blue jeans, t-shirt.`;
-				case "boyshorts":
-					return `Boy shorts.`;
-				case "cutoffs":
-					return `Jean shorts.`;
-				case "leather pants and pasties":
-					return `Leather pants, pasties.`;
-				case "leather pants":
-					return `Nice leather pants.`;
-				case "panties":
-					return `Nice panties.`;
-				case "sport shorts and a t-shirt":
-					return `Nice sport shorts, shirt.`;
-				case "a t-shirt and panties":
-					return `Panties, t-shirt.`;
-				case "panties and pasties":
-					return `Pasties, panties.`;
-				case "pasties":
-					return `Pasties.`;
-				case "striped underwear":
-					return `Striped underwear`;
-				case "sport shorts and a sports bra":
-					return `Shorts, bra.`;
-				case "jeans":
-					return `Tight blue jeans.`;
-				case "a sweater and cutoffs":
-					return `Jean shorts, sweater.`;
-				case "leather pants and a tube top":
-					return `Leather pants, tube top.`;
-				case "sport shorts":
-					return `Shorts.`;
-				case "a bimbo outfit":
-					return `Bimbo outfit.`;
-				case "a courtesan dress":
-					return `Courtesan dress.`;
-				default:
-					return `Naked.`;
-			}
-		}
-		makeSpan(c, descStr());
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_collar(slave, c) {
-		function descStr() {
-			switch (slave.collar) {
-				case "uncomfortable leather":
-					return `Leather collar.`;
-				case "tight steel":
-					return `Steel collar.`;
-				case "preg biometrics":
-					return `Pregnancy biometrics collar.`;
-				case "cruel retirement counter":
-					return `Cruel counter collar.`;
-				case "shock punishment":
-					return `Shock collar.`;
-				case "dildo gag":
-					return `Dildo gag.`;
-				case "massive dildo gag":
-					return `Throat-bulging dildo gag.`;
-				case "neck corset":
-					return `Neck corset.`;
-				case "stylish leather":
-					return `Stylish leather collar.`;
-				case "satin choker":
-					return `Satin choker.`;
-				case "silk ribbon":
-					return `Silken ribbon.`;
-				case "heavy gold":
-					return `Gold collar.`;
-				case "bowtie":
-					return `Bowtie collar.`;
-				case "pretty jewelry":
-					return `Pretty collar.`;
-				case "nice retirement counter":
-					return `Nice counter collar.`;
-				case "bell collar":
-					return `Bell collar.`;
-				case "leather with cowbell":
-					return `Cowbell collar.`;
-				case "ancient Egyptian":
-					return `Wesekh.`;
-				case "ball gag":
-					return `Ball gag.`;
-				case "bit gag":
-					return `Bit gag.`;
-				case "porcelain mask":
-					return `Porcelain mask.`;
-				default:
-					return null;
-			}
-		}
-		const s = descStr();
-		if (s) {
-			makeSpan(c, s);
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function long_belly(slave, c) {
-		function descStr() {
-			switch (slave.bellyAccessory) {
-				case "shapewear":
-					return `Shapewear.`;
-				case "a small empathy belly":
-					return `Small fake belly.`;
-				case "a medium empathy belly":
-					return `Medium fake belly.`;
-				case "a large empathy belly":
-					return `Large fake belly.`;
-				case "a huge empathy belly":
-					return `Huge fake belly.`;
-				case "a corset":
-					return `Corset.`;
-				case "an extreme corset":
-					return `Extreme corsetage.`;
-				case "a support band":
-					return `Support band.`;
-				default:
-					return null;
-			}
-		}
-		const s = descStr();
-		if (s) {
-			makeSpan(c, s);
-		}
-	}
-
-	/**
-	 * @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") {
-			switch (slave.vaginalAccessory) {
-				case "bullet vibrator":
-					makeSpan(c, "Attached bullet vibrator.");
-					break;
-				case "smart bullet vibrator":
-					makeSpan(c, "Attached smart bullet vibrator.");
-					break;
-				case "dildo":
-					makeSpan(c, "Vaginal dildo.");
-					break;
-				case "large dildo":
-					makeSpan(c, "Large vaginal dildo.");
-					break;
-				case "huge dildo":
-					makeSpan(c, "Huge vaginal dildo.");
-					break;
-				case "long dildo":
-					makeSpan(c, "Long vaginal dildo.");
-					break;
-				case "long, large dildo":
-					makeSpan(c, "Long and large vaginal dildo.");
-					break;
-				case "long, huge dildo":
-					makeSpan(c, "Long and wide vaginal dildo.");
-					break;
-			}
-		}
-		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) {
-		switch (slave.buttplug) {
-			case "plug":
-				makeSpan(c, "Buttplug.");
-				break;
-			case "large plug":
-				makeSpan(c, "Large buttplug.");
-				break;
-			case "huge plug":
-				makeSpan(c, "Huge buttplug.");
-				break;
-			case "long plug":
-				makeSpan(c, "Long buttplug.");
-				break;
-			case "long, large plug":
-				makeSpan(c, "Large, long buttplug.");
-				break;
-			case "long, huge plug":
-				makeSpan(c, "Enormous buttplug.");
-				break;
-		}
-		switch (slave.buttplugAttachment) {
-			case "tail":
-				makeSpan(c, "Attached tail.");
-				break;
-			case "cat tail":
-				makeSpan(c, "Attached cat tail.");
-				break;
-			case "fox tail":
-				makeSpan(c, "Attached fox tail.");
-				break;
-			case "cow tail":
-				makeSpan(c, "Attached cow tail.");
-				break;
-		}
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Node} c
-	 * @returns {void}
-	 */
-	function rules_assistant(slave, c) {
-		if (slave.useRulesAssistant === 0) {
-			makeSpan(c, "RA-Exempt", "lightgreen");
-		} else if (V.abbreviateRulesets === 2 && (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}
-	 */
-	function origins(slave, c) {
-		const para = 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 {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");
-		r.classList.add("ssi");
-		_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;
-	}
-
-	return SlaveSummary;
-})();
+	return {
+		makeNewState: makeNewState,
+		settingsChanged: settingsChanged,
+		societyChanged: societyChanged,
+		render: render,
+		displayOptionsFragment: displayOptionsFragment
+	};
+}();
diff --git a/src/uncategorized/fsDevelopments.tw b/src/uncategorized/fsDevelopments.tw
index b32cd29bc31d84da14275459bc071d8fe0b89d40..32433250947b235aac603a21e374c108a6d6de8a 100644
--- a/src/uncategorized/fsDevelopments.tw
+++ b/src/uncategorized/fsDevelopments.tw
@@ -1297,3 +1297,5 @@
 		<</if>>
 	<</if>>
 <</if>>
+
+<<run App.UI.SlaveSummary.societyChanged()>>
diff --git a/src/uncategorized/policies.tw b/src/uncategorized/policies.tw
index 677cdced99dee85e636853ca48062b3edd5292da..9646d6669cfc93f2c7629039cde9a31dcd5f101f 100644
--- a/src/uncategorized/policies.tw
+++ b/src/uncategorized/policies.tw
@@ -7,6 +7,7 @@
 </style>
 
 <<set $nextButton = "Back">>
+<<set _passageSwitchHandler = App.EventHandlers.societyChanged>>
 <<if $arcologies[0].FSSupremacistLawME && $PC.race != $arcologies[0].FSSupremacistRace>>
 	<<set $nextLink = "Gameover", $gameover = "Idiot Ball 2 The Dumbassening">>
 <<elseif $arcologies[0].FSSubjugationistLawME && $PC.race == $arcologies[0].FSSubjugationistRace>>
diff --git a/src/uncategorized/slaveInteract.tw b/src/uncategorized/slaveInteract.tw
index 52f4425885c3cc02e173d9a52d53b106c88b573b..b2842a8b890b8a72a23c5f7b2bb4af2e9a8d5ca4 100644
--- a/src/uncategorized/slaveInteract.tw
+++ b/src/uncategorized/slaveInteract.tw
@@ -432,37 +432,11 @@
 				<</if>>
 			<</if>>
 
-			<br><br>Non-assignment orgasm rules:
-			<div style="text-indent:2em">
-			Masturbation is <span id="relMasturbation" style="font-weight:bold"><<if getSlave($AS).rules.release.masturbation === 1>>allowed<<else>>forbidden<</if>></span>.
-				<<link "Allow">><<set getSlave($AS).rules.release.masturbation = 1>><<replace "#relMasturbation">>allowed<</replace>><</link>> |
-				<<link "Forbid">><<set getSlave($AS).rules.release.masturbation = 0>><<replace "#relMasturbation">>forbidden<</replace>><</link>>
-			</div>
-			<div style="text-indent:2em">
-			Sex with romantic partner is <span id="relPartner" style="font-weight:bold"><<if getSlave($AS).rules.release.partner === 1>>allowed<<else>>forbidden<</if>></span>.
-				<<link "Allow">><<set getSlave($AS).rules.release.partner = 1>><<replace "#relPartner">>allowed<</replace>><</link>> |
-				<<link "Forbid">><<set getSlave($AS).rules.release.partner = 0>><<replace "#relPartner">>forbidden<</replace>><</link>>
-			</div>
-			<<if $seeIncest == 1>>
-				<div style="text-indent:2em">
-				Sex with close family is <span id="relFamily" style="font-weight:bold"><<if getSlave($AS).rules.release.family === 1>>allowed<<else>>forbidden<</if>></span>.
-					<<link "Allow">><<set getSlave($AS).rules.release.family = 1>><<replace "#relFamily">>allowed<</replace>><</link>> |
-					<<link "Forbid">><<set getSlave($AS).rules.release.family = 0>><<replace "#relFamily">>forbidden<</replace>><</link>>
-				</div>
-			<</if>>
-			<div style="text-indent:2em">
-			Sex with other slaves is <span id="relSlaves" style="font-weight:bold"><<if getSlave($AS).rules.release.slaves === 1>>allowed<<else>>forbidden<</if>></span>.
-				<<link "Allow">><<set getSlave($AS).rules.release.slaves = 1>><<replace "#relSlaves">>allowed<</replace>><</link>> |
-				<<link "Forbid">><<set getSlave($AS).rules.release.slaves = 0>><<replace "#relSlaves">>forbidden<</replace>><</link>>
-			</div>
-			<div style="text-indent:2em">
-			Routine sex with <<= properMaster()>> is <span id="relMaster" style="font-weight:bold"><<if getSlave($AS).rules.release.master === 1>>granted<<else>>denied<</if>></span>.
-				<<link "Grant">><<set getSlave($AS).rules.release.master = 1>><<replace "#relMaster">>granted<</replace>><</link>> |
-				<<link "Deny">><<set getSlave($AS).rules.release.master = 0>><<replace "#relMaster">>denied<</replace>><</link>>
-			</div>
+			<span id="orgasm"></span>
+			<script>App.UI.SlaveInteract.orgasm(getSlave(V.activeSlave.ID))</script>
 
 			<<if getSlave($AS).voice != 0>>
-				<br>Speech rules: <span id="speechRules" style="font-weight:bold"><<= getSlave($AS).rules.speech>></span>.
+				Speech rules: <span id="speechRules" style="font-weight:bold"><<= getSlave($AS).rules.speech>></span>.
 				<<link "Restrictive">><<set getSlave($AS).rules.speech = "restrictive">><<replace "#speechRules">><<= getSlave($AS).rules.speech>><</replace>><</link>> |
 				<<link "Permissive">><<set getSlave($AS).rules.speech = "permissive">><<replace "#speechRules">><<= getSlave($AS).rules.speech>><</replace>><</link>>
 				<<if getSlave($AS).accent > 0 && getSlave($AS).accent < 4>>| <<link "Accent elimination">><<set getSlave($AS).rules.speech = "accent elimination">><<replace "#speechRules">><<= getSlave($AS).rules.speech>><</replace>><</link>>
@@ -476,39 +450,8 @@
 			<<link "Just friends">><<set getSlave($AS).rules.relationship = "just friends">><<replace "#relationshipRules">><<= getSlave($AS).rules.relationship>><</replace>><</link>> |
 			<<link "Permissive">><<set getSlave($AS).rules.relationship = "permissive">><<replace "#relationshipRules">><<= getSlave($AS).rules.relationship>><</replace>><</link>>
 		<</if>>
-
-		<<if getSlave($AS).clitPiercing == 3 || getSlave($AS).vaginalAccessory == "smart bullet vibrator">>
-			<div>
-			<<if getSlave($AS).clitPiercing == 3>>
-				<<if getSlave($AS).dick < 1>>
-					$His smart clit piercing <<if getSlave($AS).vaginalAccessory == "smart bullet vibrator">>and smart bullet vibrator are<<else>>is<</if>> set to
-				<<else>>
-					$His smart frenulum piercing <<if getSlave($AS).vaginalAccessory == "smart bullet vibrator">>and smart bullet vibrator are<<else>>is<</if>> set to
-				<</if>>
-			<<else>>
-				$His smart bullet vibe is set to
-			<</if>>
-			<span id="setting" style="font-weight:bold"><<= getSlave($AS).clitSetting>></span>.
-			<<link "Vanilla">><<set getSlave($AS).clitSetting = "vanilla">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Oral">><<set getSlave($AS).clitSetting = "oral">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Anal">><<set getSlave($AS).clitSetting = "anal">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Boobs">><<set getSlave($AS).clitSetting = "boobs">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Sub">><<set getSlave($AS).clitSetting = "submissive">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Dom">><<set getSlave($AS).clitSetting = "dom">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Humiliation">><<set getSlave($AS).clitSetting = "humiliation">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			<<if $seePreg != 0>>
-				| <<link "Preg">><<set getSlave($AS).clitSetting = "pregnancy">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			<</if>>
-			| <<link "Pain">><<set getSlave($AS).clitSetting = "masochist">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Sadism">><<set getSlave($AS).clitSetting = "sadist">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Men">><<set getSlave($AS).clitSetting = "men">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Anti-men">><<set getSlave($AS).clitSetting = "anti-men">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Women">><<set getSlave($AS).clitSetting = "women">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "Anti-women">><<set getSlave($AS).clitSetting = "anti-women">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "All sex">><<set getSlave($AS).clitSetting = "all">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			| <<link "No sex">><<set getSlave($AS).clitSetting = "none">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>>
-			</div>
-		<</if>>
+		<span id="smartSettings"></span>
+		<script>App.UI.SlaveInteract.smartSettings(getSlave(V.activeSlave.ID))</script>
 	</div>
 </div>
 
diff --git a/src/uncategorized/summaryOptions.tw b/src/uncategorized/summaryOptions.tw
index 507d35f4e91a147484f70da5603a9405cc706033..c839f964e8536e79801fe1106d8c59dcae6a1875 100644
--- a/src/uncategorized/summaryOptions.tw
+++ b/src/uncategorized/summaryOptions.tw
@@ -70,95 +70,7 @@ Sample summary:
 	<<option 2 "Card">>
 <</options>>
 
-<<options $abbreviateDevotion "Summary Options">>
-	Mental stats are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateMental "Summary Options">>
-	Mental attributes are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateRules "Summary Options">>
-	Rules are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateHealth "Summary Options">>
-	Health is
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateDiet "Summary Options">>
-	Diet and weight are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateDrugs "Summary Options">>
-	Drugs and addiction are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateHormoneBalance "Summary Options">>
-	Hormone balance is
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateGenitalia "Summary Options">>
-	Genitalia are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviatePhysicals "Summary Options">>
-	Physical traits are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateSkills "Summary Options">>
-	Skills are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateNationality "Summary Options">>
-	Nationality is
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateRace "Summary Options">>
-	Race is
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-	<<option 0 "Hidden">>
-<</options>>
-
-<<options $abbreviateRulesets "Summary Options">>
-	Rules Assistant rulesets are
-	<<option 2 "Summarized">>
-	<<option 1 "Abbreviated">>
-<</options>>
+<<= App.UI.DOM.includeDOM(App.UI.SlaveSummary.displayOptionsFragment(), "dof") >>
 
 <<options $summaryStats "Summary Options">>
 	Granular slave stat numbers are
@@ -172,12 +84,6 @@ Main menu assignment shortcuts are
 	<<option 0 "Hidden">>
 <</options>>
 
-<<options $abbreviateOrigins "Summary Options">>
-	Origins are
-	<<option 2 "Summarized">>
-	<<option 0 "Hidden">>
-<</options>>
-
 <<if $showMissingSlaves>>
 	<<options $showMissingSlavesSD "Summary Options">>
 		Missing slave parents are
@@ -187,5 +93,5 @@ Main menu assignment shortcuts are
 <</if>>
 
 <p style="font-style:italic">
-	[[FC Dev's preferred options|Summary Options ][$seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion",$sortSlavesOrder = "descending",$sortSlavesMain = 0,$rulesAssistantMain = 1,$abbreviateDevotion = 1,$abbreviateRules = 1,$abbreviateClothes = 2,$abbreviateHealth = 1,$abbreviateDiet = 1,$abbreviateDrugs = 1,$abbreviateRace = 1,$abbreviateGenitalia = 1,$abbreviatePhysicals = 1,$abbreviateSkills = 1,$abbreviateMental = 1,$abbreviateSidebar = 1]]
+	[[FC Dev's preferred options|Summary Options ][$seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion",$sortSlavesOrder = "descending",$sortSlavesMain = 0,$rulesAssistantMain = 1, $UI.slaveSummary.abbreviation = {devotion: 1, mental: 1, rules: 1, clothes: 2, health: 1, diet: 1, drugs: 1, hormoneBalance: 1, race: 1, genitalia: 1, physicals: 1, skills: 1, nationality: 1, rulesets: 1, clothes: 0, origins: 0} ,$abbreviateSidebar = 1]]
 </p>
diff --git a/src/zz1-last/setupEventHandlers.js b/src/zz1-last/setupEventHandlers.js
index 9a41b2959c9a850010c0079626bf5252ab8ae5ee..190756ccced5b1ba246a99d25876336893d5d9dc 100644
--- a/src/zz1-last/setupEventHandlers.js
+++ b/src/zz1-last/setupEventHandlers.js
@@ -5,3 +5,7 @@ Config.saves.onSave = App.EventHandlers.onSave;
 $(document).on(':storyready', function(ev) {
 	App.EventHandlers.storyReady();
 });
+
+$(document).one(':passagestart', function() {
+	App.EventHandlers.optionsChanged();
+});