code:
// 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: {
						3: {desc: "Overweight", style: "red"},
						99: {desc: "Nicely chubby"}
					},
					1: {desc: "Nicely chubby"}
				},
				230: {
					0: {
						4: {desc: "Fat", style: "red"},
						99: {desc: "Pleasantly soft and shapely"}
					},
					1: {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"},
				200: {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.",
			"Imperial Plate": "Imperial Plate.",
			"a tight Imperial bodysuit": "Imperial Bodysuit.",
			"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.",
				"bell collar": "Bell collar.",
				"bowtie": "Bowtie collar.",
				"neck tie": "Neck tie.",
				"cruel retirement counter": "Cruel counter collar.",
				"heavy gold": "Gold collar.",
				"leather with cowbell": "Cowbell collar.",
				"neck corset": "Neck corset.",
				"nice retirement counter": "Nice counter collar.",
				"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.",
			},
			faceAccessory: {
				"porcelain mask": "Porcelain mask.",
				"cat ears": "Cat ears.",
			},
			mouthAccessory: {
				"bit gag": "Bit gag.",
				"dildo gag": "Dildo gag.",
				"ring gag": "Ring gag.",
				"massive dildo gag": "Throat-bulging dildo gag.",
				"ball gag": "Ball gag.",
			},
			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 Added.",
			4: "Milk & Cum Added.",
			5: "Cum Based with Milk.",
			6: "Milk Based.",
			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: {
				0: {desc: "Energetic", style: "green"},
				30: {desc: "Rested", 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"}, //intended limit of 100 health
				999: {desc: "Unnaturally healthy", style: "green"} //catch to display unintended cases of health > 100 instead of vanishing
			},
		},
		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 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"}
			},
			XY: {
				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"}
			},
			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: {
						3: {desc: "W++", style: ["red", "strong"]},
						99: {desc: "W++", style: "strong"}
					},
					1: {desc: "W++", style: "strong"}
				},
				230: {
					0: {
						4: {desc: "W+++", style: ["red", "strong"]},
						99: {desc: "W+++", style: "strong"}
					},
					1: {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+"},
				200: {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: "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"}
			},
			XY: {
				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"}
			},
			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"}
			}
		}
	}
};


code:
App.Data.NationalityPresets = {};

App.Data.NationalityPresets.Vanilla = new Map ([
	["Vanilla Global", {
		Afghan: 2,
		Albanian: 1,
		Algerian: 3,
		American: 6,
		Andorran: 1,
		Angolan: 1,
		Antiguan: 1,
		Argentinian: 4,
		Armenian: 2,
		Aruban: 1,
		Australian: 3,
		Austrian: 2,
		Azerbaijani: 1,
		Bahamian: 1,
		Bahraini: 1,
		Bangladeshi: 4,
		Barbadian: 1,
		Belarusian: 1,
		Belgian: 2,
		Belizean: 1,
		Beninese: 1,
		Bermudian: 1,
		Bhutanese: 1,
		"Bissau-Guinean": 1,
		Bolivian: 2,
		Bosnian: 1,
		Brazilian: 4,
		British: 3,
		Bruneian: 1,
		Bulgarian: 2,
		Burkinabé: 1,
		Burmese: 2,
		Burundian: 1,
		Cambodian: 1,
		Cameroonian: 1,
		Canadian: 2,
		"Cape Verdean": 1,
		Catalan: 1,
		"Central African": 1,
		Chadian: 1,
		Chilean: 2,
		Chinese: 15,
		Colombian: 3,
		Comorian: 1,
		Congolese: 1,
		"a Cook Islander": 1,
		"Costa Rican": 1,
		Croatian: 1,
		Cuban: 2,
		Curaçaoan: 1,
		Cypriot: 1,
		Czech: 2,
		Danish: 2,
		Djiboutian: 1,
		Dominican: 2,
		Dominiquais: 1,
		Dutch: 2,
		"East Timorese": 1,
		Ecuadorian: 1,
		Egyptian: 3,
		Emirati: 2,
		Equatoguinean: 1,
		Eritrean: 1,
		Estonian: 2,
		Ethiopian: 3,
		Fijian: 1,
		Filipina: 3,
		Finnish: 2,
		French: 3,
		"French Guianan": 1,
		"French Polynesian": 1,
		Gabonese: 1,
		Gambian: 1,
		Georgian: 1,
		German: 3,
		Ghanan: 2,
		Greek: 2,
		Greenlandic: 1,
		Grenadian: 1,
		Guamanian: 1,
		Guatemalan: 1,
		Guinean: 1,
		Guyanese: 1,
		Haitian: 2,
		Honduran: 1,
		Hungarian: 2,
		"I-Kiribati": 1,
		Icelandic: 2,
		Indian: 11,
		Indonesian: 5,
		Iranian: 3,
		Iraqi: 2,
		Irish: 2,
		Israeli: 2,
		Italian: 3,
		Ivorian: 1,
		Jamaican: 2,
		Japanese: 3,
		Jordanian: 2,
		Kazakh: 2,
		Kenyan: 3,
		Kittitian: 1,
		Korean: 2,
		Kosovan: 1,
		Kurdish: 1,
		Kuwaiti: 1,
		Kyrgyz: 1,
		Laotian: 1,
		Latvian: 1,
		Lebanese: 2,
		Liberian: 1,
		Libyan: 2,
		"a Liechtensteiner": 1,
		Lithuanian: 2,
		Luxembourgian: 1,
		Macedonian: 1,
		Malagasy: 1,
		Malawian: 1,
		Malaysian: 3,
		Maldivian: 1,
		Malian: 2,
		Maltese: 1,
		Marshallese: 1,
		Mauritanian: 1,
		Mauritian: 1,
		Mexican: 4,
		Micronesian: 1,
		Moldovan: 1,
		Monégasque: 1,
		Mongolian: 1,
		Montenegrin: 1,
		Moroccan: 2,
		Mosotho: 1,
		Motswana: 1,
		Mozambican: 1,
		Namibian: 1,
		Nauruan: 1,
		Nepalese: 2,
		"New Caledonian": 1,
		"a New Zealander": 2,
		"Ni-Vanuatu": 1,
		Nicaraguan: 2,
		Nigerian: 4,
		Nigerien: 2,
		Niuean: 1,
		Norwegian: 2,
		Omani: 2,
		Pakistani: 3,
		Palauan: 1,
		Palestinian: 1,
		Panamanian: 1,
		"Papua New Guinean": 1,
		Paraguayan: 1,
		Peruvian: 2,
		Polish: 3,
		Portuguese: 2,
		"Puerto Rican": 1,
		Qatari: 1,
		Romanian: 2,
		Russian: 4,
		Rwandan: 1,
		Sahrawi: 1,
		"Saint Lucian": 1,
		Salvadoran: 2,
		Sammarinese: 1,
		Samoan: 1,
		"São Toméan": 1,
		Saudi: 2,
		Scottish: 1,
		Senegalese: 1,
		Serbian: 2,
		Seychellois: 1,
		"Sierra Leonean": 1,
		Singaporean: 1,
		Slovak: 2,
		Slovene: 1,
		"a Solomon Islander": 1,
		Somali: 1,
		"South African": 4,
		"South Sudanese": 1,
		Spanish: 3,
		"Sri Lankan": 1,
		Sudanese: 3,
		Surinamese: 1,
		Swazi: 1,
		Swedish: 2,
		Swiss: 2,
		Syrian: 1,
		Taiwanese: 1,
		Tajik: 1,
		Tanzanian: 2,
		Thai: 3,
		Tibetan: 1,
		Togolese: 1,
		Tongan: 1,
		Trinidadian: 1,
		Tunisian: 2,
		Turkish: 3,
		Turkmen: 1,
		Tuvaluan: 1,
		Ugandan: 2,
		Ukrainian: 3,
		Uruguayan: 2,
		Uzbek: 2,
		Vatican: 1,
		Venezuelan: 3,
		Vietnamese: 3,
		Vincentian: 1,
		Yemeni: 2,
		Zairian: 3,
		Zambian: 1,
		Zimbabwean: 2
	}],
	["Vanilla North America", {
		American: 6,
		Antiguan: 1,
		Aruban: 1,
		Bahamian: 1,
		Barbadian: 1,
		Belizean: 1,
		Bermudian: 1,
		Canadian: 2,
		"Costa Rican": 1,
		Cuban: 2,
		Curaçaoan: 1,
		Dominican: 2,
		Dominiquais: 1,
		Greenlandic: 1,
		Grenadian: 1,
		Guatemalan: 1,
		Haitian: 2,
		Honduran: 1,
		Jamaican: 2,
		Kittitian: 1,
		Mexican: 4,
		Nicaraguan: 2,
		Panamanian: 1,
		"Puerto Rican": 1,
		"Saint Lucian": 1,
		Salvadoran: 2,
		Trinidadian: 1,
		Vincentian: 1
	}],
	["Vanilla South America", {
		Argentinian: 4,
		Bolivian: 2,
		Brazilian: 4,
		Chilean: 2,
		Colombian: 3,
		Ecuadorian: 1,
		"French Guianan": 1,
		Guyanese: 1,
		Paraguayan: 1,
		Peruvian: 2,
		Surinamese: 1,
		Uruguayan: 2,
		Venezuelan: 3
	}],
	["Vanilla Middle East", {
		Afghan: 2,
		Armenian: 2,
		Azerbaijani: 1,
		Bahraini: 1,
		Cypriot: 1,
		Egyptian: 3,
		Emirati: 2,
		Georgian: 1,
		Iranian: 3,
		Iraqi: 2,
		Israeli: 2,
		Jordanian: 2,
		Kurdish: 1,
		Kuwaiti: 1,
		Lebanese: 2,
		Omani: 2,
		Palestinian: 1,
		Qatari: 1,
		Saudi: 2,
		Syrian: 1,
		Turkish: 3,
		Yemeni: 2
	}],
	["Vanilla Africa", {
		Algerian: 3,
		Angolan: 1,
		Beninese: 1,
		"Bissau-Guinean": 1,
		Burkinabé: 1,
		Burundian: 1,
		Cameroonian: 1,
		"Cape Verdean": 1,
		"Central African": 1,
		Chadian: 1,
		Comorian: 1,
		Congolese: 1,
		Djiboutian: 1,
		Equatoguinean: 1,
		Eritrean: 1,
		Ethiopian: 3,
		Gabonese: 1,
		Gambian: 1,
		Ghanan: 2,
		Guinean: 1,
		Ivorian: 1,
		Kenyan: 3,
		Liberian: 1,
		Libyan: 2,
		Malagasy: 1,
		Malawian: 1,
		Malian: 2,
		Mauritanian: 1,
		Mauritian: 1,
		Moroccan: 2,
		Mosotho: 1,
		Motswana: 1,
		Mozambican: 1,
		Namibian: 1,
		Nigerian: 4,
		Nigerien: 2,
		Rwandan: 1,
		Sahrawi: 1,
		"São Toméan": 1,
		Senegalese: 1,
		Seychellois: 1,
		"Sierra Leonean": 1,
		Somali: 1,
		"South African": 4,
		"South Sudanese": 1,
		Sudanese: 3,
		Swazi: 1,
		Tanzanian: 2,
		Togolese: 1,
		Tunisian: 2,
		Ugandan: 2,
		Zairian: 3,
		Zambian: 1,
		Zimbabwean: 2
	}],
	["Vanilla Asia", {
		Bangladeshi: 4,
		Bhutanese: 1,
		Bruneian: 1,
		Burmese: 2,
		Cambodian: 1,
		Chinese: 15,
		"East Timorese": 1,
		Filipina: 3,
		Indian: 11,
		Indonesian: 5,
		Japanese: 3,
		Kazakh: 2,
		Korean: 2,
		Kyrgyz: 1,
		Laotian: 1,
		Malaysian: 3,
		Maldivian: 1,
		Mongolian: 1,
		Nepalese: 2,
		Pakistani: 3,
		Singaporean: 1,
		"Sri Lankan": 1,
		Taiwanese: 1,
		Tajik: 1,
		Thai: 3,
		Tibetan: 1,
		Turkmen: 1,
		Uzbek: 2,
		Vietnamese: 3
	}],
	["Vanilla Europe", {
		Albanian: 1,
		Andorran: 1,
		Austrian: 2,
		Belarusian: 1,
		Belgian: 2,
		Bosnian: 1,
		British: 3,
		Bulgarian: 2,
		Catalan: 1,
		Croatian: 1,
		Czech: 2,
		Danish: 2,
		Dutch: 2,
		Estonian: 2,
		Finnish: 2,
		French: 3,
		German: 3,
		Greek: 2,
		Hungarian: 2,
		Icelandic: 2,
		Irish: 2,
		Italian: 3,
		Kosovan: 1,
		Latvian: 1,
		"a Liechtensteiner": 1,
		Lithuanian: 2,
		Luxembourgian: 1,
		Macedonian: 1,
		Maltese: 1,
		Moldovan: 1,
		Monégasque: 1,
		Montenegrin: 1,
		Norwegian: 2,
		Polish: 3,
		Portuguese: 2,
		Romanian: 2,
		Russian: 4,
		Sammarinese: 1,
		Scottish: 1,
		Serbian: 2,
		Slovak: 2,
		Slovene: 1,
		Spanish: 3,
		Swedish: 2,
		Swiss: 2,
		Ukrainian: 3,
		Vatican: 1
	}],
	["Vanilla Australia", {
		Australian: 3,
		"a Cook Islander": 1,
		Fijian: 1,
		"French Polynesian": 1,
		Guamanian: 1,
		"I-Kiribati": 1,
		Marshallese: 1,
		Micronesian: 1,
		Nauruan: 1,
		"New Caledonian": 1,
		"a New Zealander": 2,
		"Ni-Vanuatu": 1,
		Niuean: 1,
		Palauan: 1,
		"Papua New Guinean": 1,
		Samoan: 1,
		"a Solomon Islander": 1,
		Tongan: 1,
		Tuvaluan: 1
	}],
]);

App.Data.NationalityPresets.Mod = new Map([
	["Realism North America", {
		American: 36503,
		Antiguan: 11,
		Aruban: 10,
		Bahamian: 45,
		Barbadian: 28,
		Belizean: 50,
		Bermudian: 5,
		Canadian: 4188,
		"Costa Rican": 555,
		Cuban: 1141,
		Curaçaoan: 17,
		Dominican: 1249,
		Dominiquais: 7,
		Greenlandic: 5,
		Grenadian: 11,
		Guatemalan: 2277,
		Haitian: 1303,
		Honduran: 1177,
		Jamaican: 290,
		Kittitian: 6,
		Mexican: 15306,
		Nicaraguan: 731,
		Panamanian: 515,
		"Puerto Rican": 354,
		"Saint Lucian": 18,
		Salvadoran: 688,
		Trinidadian: 136,
		Vincentian: 11
	}],
	["Realism South America", {
		Argentinian: 5102,
		Bolivian: 1392,
		Brazilian: 22920,
		Chilean: 2005,
		Colombian: 5105,
		Ecuadorian: 2055,
		"French Guianan": 41,
		Guyanese: 83,
		Paraguayan: 817,
		Peruvian: 3829,
		Surinamese: 63,
		Uruguayan: 362,
		Venezuelan: 3826
	}],
	["Realism Middle East", {
		Afghan: 5090,
		Armenian: 286,
		Azerbaijani: 1086,
		Bahraini: 212,
		Cypriot: 131,
		Egyptian: 12826,
		Emirati: 1164,
		Georgian: 366,
		Iranian: 9047,
		Iraqi: 5982,
		Israeli: 1062,
		Jordanian: 1184,
		Kurdish: 7258,
		Kuwaiti: 511,
		Lebanese: 533,
		Omani: 612,
		Palestinian: 746,
		Qatari: 340,
		Saudi: 4131,
		Syrian: 2888,
		Turkish: 9091,
		Yemeni: 3996
	}],
	["Realism Africa", {
		Algerian: 5107,
		Angolan: 5166,
		Beninese: 1756,
		"Bissau-Guinean": 275,
		Burkinabé: 3105,
		Burundian: 1797,
		Cameroonian: 3688,
		"Cape Verdean": 66,
		"Central African": 679,
		Chadian: 2431,
		Comorian: 116,
		Congolese: 827,
		Djiboutian: 118,
		Equatoguinean: 210,
		Eritrean: 742,
		Ethiopian: 15303,
		Gabonese: 282,
		Gambian: 338,
		Ghanan: 4071,
		Guinean: 1978,
		Ivorian: 3741,
		Kenyan: 7408,
		Liberian: 727,
		Libyan: 760,
		Malagasy: 3989,
		Malawian: 3010,
		Malian: 3098,
		Mauritanian: 676,
		Mauritian: 128,
		Moroccan: 4240,
		Mosotho: 275,
		Motswana: 297,
		Mozambican: 4824,
		Namibian: 352,
		Nigerian: 29732,
		Nigerien: 4187,
		Rwandan: 1754,
		Sahrawi: 80,
		"São Toméan": 29,
		Senegalese: 2486,
		Seychellois: 9,
		"Sierra Leonean": 1056,
		Somali: 2469,
		"South African": 6688,
		"South Sudanese": 1918,
		Sudanese: 6099,
		Swazi: 177,
		Tanzanian: 9586,
		Togolese: 1166,
		Tunisian: 1316,
		Ugandan: 7338,
		Zairian: 13815,
		Zambian: 2844,
		Zimbabwean: 2355
	}],
	["Realism Asia", {
		Bangladeshi: 19160,
		Bhutanese: 94,
		Bruneian: 50,
		Burmese: 6043,
		Cambodian: 1972,
		Chinese: 143350,
		"East Timorese": 187,
		Filipina: 13266,
		Indian: 156457,
		Indonesian: 30475,
		Japanese: 11849,
		Kazakh: 2095,
		Korean: 7977,
		Kyrgyz: 730,
		Laotian: 841,
		Malaysian: 3838,
		Maldivian: 52,
		Mongolian: 369,
		Nepalese: 3456,
		Pakistani: 26109,
		Singaporean: 647,
		"Sri Lankan": 2149,
		Taiwanese: 2410,
		Tajik: 1202,
		Thai: 6919,
		Tibetan: 435,
		Turkmen: 707,
		Uzbek: 3805,
		Vietnamese: 3255
	}],
	/* I need reweighting and possibly country additions */
	["Realism Europe", {
		Albanian: 289,
		Andorran: 7,
		Austrian: 897,
		Belarusian: 899,
		Belgian: 1215,
		Bosnian: 333,
		British: 7189,
		Bulgarian: 615,
		Catalan: 767,
		Croatian: 379,
		Czech: 1039,
		Danish: 611,
		Dutch: 1769,
		Estonian: 122,
		Finnish: 578,
		French: 6886,
		German: 8173,
		Greek: 1062,
		Hungarian: 900,
		Icelandic: 37,
		Irish: 537,
		Italian: 5753,
		Kosovan: 193,
		Latvian: 168,
		"a Liechtensteiner": 4,
		Lithuanian: 263,
		Luxembourgian: 70,
		Macedonian: 205,
		Maltese: 43,
		Moldovan: 372,
		Monégasque: 4,
		Montenegrin: 61,
		Norwegian: 619,
		Polish: 3569,
		Portuguese: 969,
		Romanian: 1797,
		Russian: 13807,
		Sammarinese: 3,
		Scottish: 575,
		Serbian: 814,
		Slovak: 530,
		Slovene: 203,
		Spanish: 4586,
		Swedish: 1094,
		Swiss: 941,
		Ukrainian: 3989,
		Vatican: 1
	}],
	["Realism Australia", {
		Australian: 2952,
		"a Cook Islander": 1,
		Fijian: 98,
		"French Polynesian": 31,
		Guamanian: 18,
		"I-Kiribati": 15,
		Marshallese: 5,
		Micronesian: 12,
		Nauruan: 1,
		"New Caledonian": 33,
		"a New Zealander": 536,
		"Ni-Vanuatu": 38,
		Niuean: 1,
		Palauan: 2,
		"Papua New Guinean": 1136,
		Samoan: 22,
		"a Solomon Islander": 83,
		Tongan: 12,
		Tuvaluan: 1
	}],
	["East Asia", {Chinese: 21, Japanese: 2, Korean: 1}],
	["USA", {American: 8, Canadian: 1, Mexican: 3}],
	["Brazil", {
		Argentinian: 4,
		Bolivian: 2,
		Brazilian: 30,
		Chilean: 2,
		Colombian: 3,
		Ecuadorian: 1,
		"French Guianan": 1,
		Guyanese: 1,
		Paraguayan: 1,
		Peruvian: 2,
		Surinamese: 1,
		Uruguayan: 2,
		Venezuelan: 2
	}],
	["Mediterranean", {
		Albanian: 29,
		Algerian: 404,
		Andorran: 1,
		Bosnian: 35,
		British: 1,
		Bulgarian: 71,
		Catalan: 75,
		Croatian: 42,
		Cypriot: 12,
		Egyptian: 948,
		French: 671,
		Georgian: 49,
		Greek: 108,
		Israeli: 89,
		Italian: 605,
		Lebanese: 60,
		Libyan: 63,
		Maltese: 5,
		Monégasque: 1,
		Montenegrin: 7,
		Moroccan: 358,
		Palestinian: 18,
		Portuguese: 102,
		Romanian: 132,
		Russian: 40,
		Sammarinese: 1,
		Slovene: 21,
		Spanish: 466,
		Syrian: 171,
		Tunisian: 113,
		Turkish: 808,
		Ukrainian: 105,
		Vatican: 1,
	}],

	/* I need reweighting */
	["Realism Global", {
		Afghan: 5090,
		Albanian: 289,
		Algerian: 5107,
		American: 36503,
		Andorran: 7,
		Angolan: 5166,
		Antiguan: 11,
		Argentinian: 5102,
		Armenian: 286,
		Aruban: 10,
		Australian: 2952,
		Austrian: 897,
		Azerbaijani: 1086,
		Bahamian: 45,
		Bahraini: 212,
		Bangladeshi: 19160,
		Barbadian: 28,
		Belarusian: 899,
		Belgian: 1215,
		Belizean: 50,
		Beninese: 1756,
		Bermudian: 5,
		Bhutanese: 94,
		"Bissau-Guinean": 275,
		Bolivian: 1392,
		Bosnian: 333,
		Brazilian: 22920,
		British: 7189,
		Bruneian: 50,
		Bulgarian: 615,
		Burkinabé: 3105,
		Burmese: 6043,
		Burundian: 1797,
		Cambodian: 1972,
		Cameroonian: 3688,
		Canadian: 4188,
		"Cape Verdean": 66,
		Catalan: 767,
		"Central African": 679,
		Chadian: 2431,
		Chilean: 2005,
		Chinese: 143350,
		Colombian: 5405,
		Comorian: 116,
		Congolese: 827,
		"a Cook Islander": 1,
		"Costa Rican": 555,
		Croatian: 379,
		Cuban: 1141,
		Curaçaoan: 17,
		Cypriot: 131,
		Czech: 1039,
		Danish: 611,
		Djiboutian: 118,
		Dominican: 1249,
		Dominiquais: 7,
		Dutch: 1769,
		"East Timorese": 187,
		Ecuadorian: 2055,
		Egyptian: 12826,
		Emirati: 1164,
		Equatoguinean: 210,
		Eritrean: 742,
		Estonian: 122,
		Ethiopian: 15303,
		Fijian: 98,
		Filipina: 13266,
		Finnish: 578,
		French: 6886,
		"French Guianan": 41,
		"French Polynesian": 31,
		Gabonese: 282,
		Gambian: 338,
		Georgian: 366,
		German: 8173,
		Ghanan: 4071,
		Greek: 1062,
		Greenlandic: 5,
		Grenadian: 11,
		Guamanian: 18,
		Guatemalan: 2277,
		Guinean: 1978,
		Guyanese: 83,
		Haitian: 1303,
		Honduran: 1177,
		Hungarian: 900,
		"I-Kiribati": 15,
		Icelandic: 37,
		Indian: 156457,
		Indonesian: 30475,
		Iranian: 9047,
		Iraqi: 5982,
		Irish: 537,
		Israeli: 1062,
		Italian: 5753,
		Ivorian: 3741,
		Jamaican: 290,
		Japanese: 11849,
		Jordanian: 1184,
		Kazakh: 2095,
		Kenyan: 7408,
		Kittitian: 6,
		Korean: 7977,
		Kosovan: 193,
		Kurdish: 7258,
		Kuwaiti: 511,
		Kyrgyz: 730,
		Laotian: 841,
		Latvian: 168,
		Lebanese: 533,
		Liberian: 727,
		Libyan: 760,
		"a Liechtensteiner": 4,
		Lithuanian: 263,
		Luxembourgian: 70,
		Macedonian: 205,
		Malagasy: 3989,
		Malawian: 3010,
		Malaysian: 3838,
		Maldivian: 52,
		Malian: 3098,
		Maltese: 43,
		Marshallese: 5,
		Mauritanian: 676,
		Mauritian: 128,
		Mexican: 15306,
		Micronesian: 12,
		Moldovan: 372,
		Monégasque: 4,
		Mongolian: 369,
		Montenegrin: 61,
		Moroccan: 4240,
		Mosotho: 275,
		Motswana: 297,
		Mozambican: 4824,
		Namibian: 352,
		Nauruan: 1,
		Nepalese: 3456,
		"New Caledonian": 33,
		"a New Zealander": 536,
		"Ni-Vanuatu": 38,
		Nicaraguan: 731,
		Nigerian: 29732,
		Nigerien: 4187,
		Niuean: 1,
		Norwegian: 619,
		Omani: 612,
		Pakistani: 26109,
		Palauan: 2,
		Palestinian: 746,
		Panamanian: 515,
		"Papua New Guinean": 1136,
		Paraguayan: 817,
		Peruvian: 3829,
		Polish: 3569,
		Portuguese: 969,
		"Puerto Rican": 354,
		Qatari: 340,
		Romanian: 1797,
		Russian: 13807,
		Rwandan: 1754,
		Sahrawi: 80,
		"Saint Lucian": 18,
		Salvadoran: 688,
		Sammarinese: 3,
		Samoan: 22,
		"São Toméan": 29,
		Saudi: 4131,
		Scottish: 575,
		Senegalese: 2486,
		Serbian: 814,
		Seychellois: 9,
		"Sierra Leonean": 1056,
		Singaporean: 647,
		Slovak: 530,
		Slovene: 203,
		"a Solomon Islander": 83,
		Somali: 2469,
		"South African": 6688,
		"South Sudanese": 1918,
		Spanish: 4586,
		"Sri Lankan": 2149,
		Sudanese: 6099,
		Surinamese: 63,
		Swazi: 177,
		Swedish: 1094,
		Swiss: 941,
		Syrian: 2888,
		Taiwanese: 2410,
		Tajik: 1202,
		Tanzanian: 9586,
		Thai: 6919,
		Tibetan: 435,
		Togolese: 1166,
		Tongan: 12,
		Trinidadian: 136,
		Tunisian: 1316,
		Turkish: 9091,
		Turkmen: 707,
		Tuvaluan: 1,
		Ugandan: 7338,
		Ukrainian: 3989,
		Uruguayan: 362,
		Uzbek: 3805,
		Vatican: 1,
		Venezuelan: 3826,
		Vietnamese: 10898,
		Vincentian: 11,
		Yemeni: 3996,
		Zairian: 13815,
		Zambian: 2844,
		Zimbabwean: 2355
	}],
]);

repo_name: Welptard/fc

license: mit


code:
/**
 * @typedef {object} slaveWear
 * @property {string} name
 * @property {string} value
 * @property {string} [fs]
 * @property {boolean} [requirements]
 */

/**
 * @typedef {Array<slaveWear>} slaveWearCategory
 */

/** @type {Object.<string, slaveWearCategory>} */
App.Data.slaveWear = {

	niceClothes: [
		{
			name: "Maternity lingerie",
			value: "attractive lingerie for a pregnant woman",
			fs: "FSRepopulationFocus",
			get requirements() { return V.boughtItem.clothing.maternityLingerie === 1; }
		},
		{
			name: "Bunny outfit",
			value: "a bunny outfit",
			fs: "FSGenderFundamentalist",
			get requirements() { return V.boughtItem.clothing.bunny === 1; }
		},
		{
			name: "Body oil",
			value: "body oil",
			fs: "FSPhysicalIdealist",
			get requirements() { return V.boughtItem.clothing.oil === 1; }
		},
		{
			name: "Chattel habit",
			value: "a chattel habit",
			fs: "FSChattelReligionist",
			get requirements() { return V.boughtItem.clothing.habit === 1; }
		},
		{
			name: "Conservative clothing",
			value: "conservative clothing",
			fs: "FSPaternalist",
			get requirements() { return V.boughtItem.clothing.conservative === 1; }
		},
		{
			name: "Harem gauze",
			value: "harem gauze",
			fs: "FSArabianRevivalist",
			get requirements() { return V.boughtItem.clothing.harem === 1; }
		},
		{
			name: "Huipil",
			value: "a huipil",
			fs: "FSAztecRevivalist",
			get requirements() { return V.boughtItem.clothing.huipil === 1; }
		},
		{
			name: "Kimono",
			value: "a kimono",
			fs: "FSEdoRevivalist",
			get requirements() { return (V.boughtItem.clothing.kimono === 1 || V.continent === "Japan"); }
		},
		{
			name: "Maternity dress",
			value: "a maternity dress",
			fs: "FSRepopulationFocus",
			get requirements() { return V.boughtItem.clothing.maternityDress === 1; }
		},
		{
			name: "Qipao (slutty)",
			value: "a slutty qipao",
			fs: "FSChineseRevivalist",
			get requirements() { return V.boughtItem.clothing.qipao === 1; }
		},
		{
			name: "Qipao (long)",
			value: "a long qipao",
			fs: "FSChineseRevivalist",
			get requirements() { return V.boughtItem.clothing.cultural === 1; }
		},
		{
			name: "Imperial Plate",
			value: "Imperial Plate",
			fs: "FSNeoImperialist",
			get requirements() { return V.boughtItem.clothing.imperialarmor === 1; }
		},
		{
			name: "Imperial Bodysuit",
			value: "a tight Imperial bodysuit",
			fs: "FSNeoImperialist",
			get requirements() { return V.boughtItem.clothing.imperialsuit === 1; }
		},
		{
			name: "Stretch pants and a crop-top",
			value: "stretch pants and a crop-top",
			fs: "FSHedonisticDecadence",
			get requirements() { return V.boughtItem.clothing.lazyClothes === 1; }
		},
		{
			name: "Toga",
			value: "a toga",
			fs: "FSRomanRevivalist",
			get requirements() { return V.boughtItem.clothing.toga === 1; }
		},
		{
			name: "Western clothing",
			value: "Western clothing",
			fs: "FSPastoralist",
			get requirements() { return V.boughtItem.clothing.western === 1; }
		},
		{
			name: "Courtesan dress",
			value: "a courtesan dress",
			fs: "FSSlaveProfessionalism",
			get requirements() { return V.boughtItem.clothing.courtesan === 1; }
		},
		{
			name: "Bimbo outfit",
			value: "a bimbo outfit",
			fs: "FSIntellectualDependency",
			get requirements() { return V.boughtItem.clothing.bimbo === 1; }
		},
		{
			name: "Petite admi outfit",
			value: "petite admi outfit",
			fs: "FSPetiteAdmiration",
			get requirements() { return V.boughtItem.clothing.petite === 1; }
		},
		{name: "Battlearmor", value: "battlearmor", get requirements() { return V.boughtItem.clothing.military === 1; }},
		{name: "Military uniform", value: "a military uniform", get requirements() { return V.boughtItem.clothing.military === 1; }},
		{name: "Red Army uniform", value: "a red army uniform", get requirements() { return V.boughtItem.clothing.military === 1; }},
		{name: "Battledress", value: "battledress", get requirements() { return V.boughtItem.clothing.military === 1; }},
		{name: "Biyelgee costume", value: "a biyelgee costume", get requirements() { return V.boughtItem.clothing.cultural === 1; }},
		{name: "Dirndl", value: "a dirndl", get requirements() { return V.boughtItem.clothing.cultural === 1; }},
		{name: "Lederhosen", value: "lederhosen", get requirements() { return V.boughtItem.clothing.cultural === 1; }},
		{name: "Mounty outfit", value: "a mounty outfit", get requirements() { return V.boughtItem.clothing.cultural === 1; }},
		{name: "Hanbok", value: "a hanbok", get requirements() { return V.boughtItem.clothing.cultural === 1; }},
		{
			name: "Burqa",
			value: "a burqa",
			get requirements() { return V.boughtItem.clothing.middleEastern === 1 || V.continent === "the Middle East"; }
		},
		{
			name: "Niqab and abaya",
			value: "a niqab and abaya",
			get requirements() { return V.boughtItem.clothing.middleEastern === 1 || V.continent === "the Middle East"; }
		},
		{
			name: "Hijab and blouse",
			value: "a hijab and blouse",
			get requirements() { return (V.boughtItem.clothing.conservative === 1 || V.continent === "the Middle East"); }
		},
		{
			name: "Burkini",
			value: "a burkini",
			get requirements() { return V.boughtItem.clothing.swimwear === 1 && (V.boughtItem.clothing.swimwear === 1 || V.continent === "the Middle East"); }
		},
		{name: "Santa dress", value: "a Santa dress", get requirements() { return V.boughtItem.clothing.costume === 1; }},

		{name: "Klan robe", value: "a klan robe", get requirements() { return V.boughtItem.clothing.pol === 1; }},
		{name: "Slutty klan robe", value: "a slutty klan robe", get requirements() { return V.boughtItem.clothing.pol === 1; }},
		{name: "Schutzstaffel uniform", value: "a schutzstaffel uniform", get requirements() { return V.boughtItem.clothing.pol === 1; }},
		{name: "Slutty schutzstaffel uniform", value: "a slutty schutzstaffel uniform", get requirements() { return V.boughtItem.clothing.pol === 1; }},

		{name: "Nice business attire", value: "nice business attire", get requirements() { return V.boughtItem.clothing.career === 1; }},
		{name: "Nurse (nice)", value: "a nice nurse outfit", get requirements() { return V.boughtItem.clothing.career === 1; }},
		{name: "Police uniform", value: "a police uniform", get requirements() { return V.boughtItem.clothing.career === 1; }},
		{
			name: "Maid (nice)",
			value: "a nice maid outfit",
			get requirements() { return V.boughtItem.clothing.career === 1 || V.PC.career === "servant"; }
		},

		{name: "Ballgown", value: "a ball gown", get requirements() { return V.boughtItem.clothing.dresses === 1; }},
		{name: "Gothic lolita dress", value: "a gothic lolita dress", get requirements() { return V.boughtItem.clothing.dresses === 1; }},

		{name: "Cybersuit", value: "a cybersuit", get requirements() { return V.boughtItem.clothing.bodysuits === 1; }},
		{name: "Latex catsuit", value: "a latex catsuit", get requirements() { return V.boughtItem.clothing.bodysuits === 1; }},

		{name: "Button-up shirt and panties", value: "a button-up shirt and panties", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Button-up shirt", value: "a button-up shirt", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Cutoffs", value: "cutoffs", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Jeans", value: "jeans", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Leather pants and a tube top", value: "leather pants and a tube top", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Leather pants", value: "leather pants", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Oversized t-shirt", value: "an oversized t-shirt", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Sweater and cutoffs", value: "a sweater and cutoffs", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Sweater and panties", value: "a sweater and panties", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Sweater", value: "a sweater", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "T-shirt and jeans", value: "a t-shirt and jeans", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "T-shirt and panties", value: "a t-shirt and panties", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "T-shirt", value: "a t-shirt", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Tank-top and panties", value: "a tank-top and panties", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Tank-top", value: "a tank-top", get requirements() { return V.boughtItem.clothing.casual === 1; }},
		{name: "Tube top", value: "a tube top", get requirements() { return V.boughtItem.clothing.casual === 1; }},

		{name: "Boyshorts", value: "boyshorts", get requirements() { return V.boughtItem.clothing.underwear === 1; }},
		{name: "Bra", value: "a bra", get requirements() { return V.boughtItem.clothing.underwear === 1; }},
		{name: "Kitty lingerie", value: "kitty lingerie", get requirements() { return V.boughtItem.clothing.underwear === 1; }},
		{name: "Panties and pasties", value: "panties and pasties", get requirements() { return V.boughtItem.clothing.underwear === 1; }},
		{name: "Skimpy loincloth", value: "a skimpy loincloth", get requirements() { return V.boughtItem.clothing.underwear === 1; }},
		{name: "Thong", value: "a thong", get requirements() { return V.boughtItem.clothing.underwear === 1; }},
		{name: "Pasties", value: "pasties", get requirements() { return V.boughtItem.clothing.underwear === 1; }},

		{
			name: "Leather pants and pasties",
			value: "leather pants and pasties",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; }
		},
		{
			name: "T-shirt and thong",
			value: "a t-shirt and thong",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; }
		},
		{
			name: "Tube top and thong",
			value: "a tube top and thong",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; }
		},
		{
			name: "Oversized t-shirt and boyshorts",
			value: "an oversized t-shirt and boyshorts",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; }
		},
		{name: "Sport shorts and a sports bra", value: "sport shorts and a sports bra", get requirements() { return V.boughtItem.clothing.sports === 1; }},
		{name: "Sport shorts", value: "sport shorts", get requirements() { return V.boughtItem.clothing.sports === 1; }},
		{name: "Sports bra", value: "a sports bra", get requirements() { return V.boughtItem.clothing.sports === 1; }},
		{
			name: "Sport shorts and a t-shirt",
			value: "sport shorts and a t-shirt",
			get requirements() { return V.boughtItem.clothing.sports === 1 && V.boughtItem.clothing.casual === 1; }
		},
		{name: "Pony outfit (nice)", value: "a nice pony outfit", get requirements() { return V.boughtItem.clothing.pony === 1; }},
		{name: "Pony outfit (slutty)", value: "a slutty pony outfit", get requirements() { return V.boughtItem.clothing.pony === 1; }},

		{name: "Monokini", value: "a monokini", get requirements() { return V.boughtItem.clothing.swimwear === 1; }},
		{name: "One-piece swimsuit", value: "a one-piece swimsuit", get requirements() { return V.boughtItem.clothing.swimwear === 1; }},

		{
			name: "Striped bra",
			value: "a striped bra",
			get requirements() { return V.boughtItem.clothing.pantsu === 1 || V.continent === "Japan"; }
		},
		{
			name: "Striped panties",
			value: "striped panties",
			get requirements() { return V.boughtItem.clothing.pantsu === 1 || V.continent === "Japan"; }
		},
		{
			name: "Striped underwear",
			value: "striped underwear",
			get requirements() { return V.boughtItem.clothing.pantsu === 1 || V.continent === "Japan"; }
		},

		// "Normal" things:
		{name: "Apron", value: "an apron"},
		{name: "Bangles", value: "slutty jewelry"},
		{name: "Clubslut netting", value: "clubslut netting"},
		{name: "Cutoffs and a t-shirt", value: "cutoffs and a t-shirt"},
		{name: "Bodysuit", value: "a comfortable bodysuit"},
		{name: "Cheerleader", value: "a cheerleader outfit"},
		{name: "Fallen nun", value: "a fallen nuns habit"},
		{name: "Hijab and abaya", value: "a hijab and abaya"},
		{name: "Leotard", value: "a leotard"},
		{name: "Maid (slutty)", value: "a slutty maid outfit"},
		{name: "Mini dress", value: "a mini dress"},
		{name: "Nice lingerie", value: "attractive lingerie"},
		{name: "Nurse (slutty)", value: "a slutty nurse outfit"},
		{name: "Overalls", value: "overalls"},
		{name: "Panties", value: "panties"},
		{name: "Scalemail bikini", value: "a scalemail bikini"},
		{name: "Schoolgirl", value: "a schoolgirl outfit"},
		{name: "Slutty outfit", value: "a slutty outfit"},
		{name: "Spats and a tank top", value: "spats and a tank top"},
		{name: "String bikini", value: "a string bikini"},
		{name: "Succubus costume", value: "a succubus outfit"},
		{name: "Suit (slutty)", value: "slutty business attire"},

		// {name: "Let them choose", value: "choosing her own clothes"},
		{name: "Haltertop dress", value: "a halter top dress"},
		{name: "Slave gown", value: "a slave gown"}
	],

	harshClothes: [
		{
			name: "Chains",
			value: "chains",
			fs: "FSDegradationist",
			get requirements() { return V.boughtItem.clothing.chains === 1; }
		},
		{name: "Go naked", value: "no clothing"},
		{name: "Penitent nun", value: "a penitent nuns habit"},
		{name: "Restrictive latex", value: "restrictive latex"},
		{name: "Shibari ropes", value: "shibari ropes"},
		{name: "Uncomfortable straps", value: "uncomfortable straps"}
	],

	niceCollars: [
		{name: "Stylish leather", value: "stylish leather"},
		{name: "Satin choker", value: "satin choker"},
		{name: "Silken ribbon", value: "silk ribbon"},
		{name: "Heavy gold", value: "heavy gold"},
		{name: "Pretty jewelry", value: "pretty jewelry"},
		{name: "Nice retirement counter", value: "nice retirement counter", get requirements() { return V.seeAge === 1; }},
		{name: "Pregnancy biometrics", value: "preg biometrics", get requirements() { return V.seePreg === 1; }},
		{name: "Bell", value: "bell collar"},
		{name: "Cowbell", value: "leather with cowbell"},
		{
			name: "Bowtie collar",
			value: "bowtie",
			fs: "FSGenderFundamentalist",
			get requirements() { return V.boughtItem.clothing.bunny === 1; }
		},
		{
			name: "Neck tie",
			value: "neck tie",
			fs: "FSPaternalist",
			get requirements() { return V.boughtItem.clothing.conservative === 1; }
		},
		{
			name: "Ancient Egyptian",
			value: "ancient Egyptian",
			fs: "FSEgyptianRevivalist",
			get requirements() { return V.boughtItem.clothing.egypt === 1; }
		},
	],

	harshCollars: [
		{name: "Tight steel", value: "tight steel"},
		{name: "Cruel retirement counter", value: "cruel retirement counter", get requirements() { return V.seeAge === 1; }},
		{name: "Uncomfortable leather", value: "uncomfortable leather"},
		{name: "Shock punishment", value: "shock punishment"},
		{name: "Neck corset", value: "neck corset"},
	],

	faceAccessory: [
		{name: "Porcelain mask", value: "porcelain mask"},
		{name: "Cat ears", value: "cat ears"},
	],

	mouthAccessory: [
		{name: "Ball gag", value: "ball gag"},
		{name: "Bit gag", value: "bit gag"},
		{name: "Ring gag", value: "ring gag"},
		{
			name: "Massive dildo gag",
			value: "massive dildo gag",
			get requirements() { return V.boughtItem.toys.gags === 1; }
		},
		{name: "Dildo gag", value: "dildo gag"},
	],

	shoes: [
		{name: "Barefoot", value: "none"},
		{name: "Flats", value: "flats"},
		{name: "Heels", value: "heels"},
		{name: "Pumps", value: "pumps"},
		{name: "Thigh boots", value: "boots"},
		{name: "Painfully extreme heels", value: "extreme heels"},
		{
			name: "Platforms",
			value: "platform shoes",
			fs: "FSStatuesqueGlorification",
			get requirements() { return (V.boughtItem.shoes.heels === 1); }
		},
		{
			name: "Platform heels",
			value: "platform heels",
			fs: "FSStatuesqueGlorification",
			get requirements() { return (V.boughtItem.shoes.heels === 1); }
		},
		{
			name: "Painfully extreme platform heels",
			value: "extreme platform heels",
			fs: "FSStatuesqueGlorification",
			get requirements() { return (V.boughtItem.shoes.heels === 1); }
		}
	],

	bellyAccessories: [
		{name: "None", value: "none"},
		{name: "Tight corset", value: "a corset"},
		{name: "Extreme corset", value: "an extreme corset"},
		{name: "Supportive band", value: "a support band"},
		{
			name: "1st Trimester belly",
			value: "a small empathy belly",
			fs: "FSRepopulationFocus",
			get requirements() { return V.boughtItem.clothing.belly === 1; }
		},
		{
			name: "2nd Trimester belly",
			value: "a medium empathy belly",
			fs: "FSRepopulationFocus",
			get requirements() { return V.boughtItem.clothing.belly === 1; }
		},
		{
			name: "3rd Trimester belly",
			value: "a large empathy belly",
			fs: "FSRepopulationFocus",
			get requirements() { return V.boughtItem.clothing.belly === 1; }
		},
		{
			name: "3rd Trimester twins belly",
			value: "a huge empathy belly",
			fs: "FSRepopulationFocus",
			get requirements() { return V.boughtItem.clothing.belly === 1; }
		}
	],

	vaginalAccessories: [
		{
			name: "None",
			value: "none"
		},
		{
			name: "Bullet vibrator",
			value: "bullet vibrator"
		},
		{
			name: "Smart bullet vibrator",
			value: "smart bullet vibrator",
			get requirements() { return V.boughtItem.toys.smartVibes === 1; }
		},
		{
			name: "Dildo",
			value: "dildo"
		},
		{
			name: "Long dildo",
			value: "long dildo",
			get requirements() { return V.boughtItem.toys.dildos === 1; }
		},
		{
			name: "Large dildo",
			value: "large dildo"
		},
		{
			name: "Long, large dildo",
			value: "long, large dildo",
			get requirements() { return V.boughtItem.toys.dildos === 1; }
		},
		{
			name: "Huge dildo",
			value: "huge dildo",
			get requirements() { return V.boughtItem.toys.dildos === 1; }
		},
		{
			name: "Long, huge dildo",
			value: "long, huge dildo",
			get requirements() { return V.boughtItem.toys.dildos === 1; }
		}
	],

	vaginalAttachments: [
		{name: "None", value: "none"},
		{
			name: "Vibrating attachment",
			value: "vibrator",
			get requirements() { return V.boughtItem.toys.vaginalAttachments === 1; }
		},
		{
			name: "Smart vibrating attachment",
			value: "smart vibrator",
			get requirements() { return V.boughtItem.toys.smartVaginalAttachments === 1; }
		}
	],

	dickAccessories: [
		{name: "None", value: "none"},
		{
			name: "Bullet vibrator",
			value: "bullet vibrator"
		},
		{
			name: "Smart bullet vibrator",
			value: "smart bullet vibrator",
			get requirements() { return V.boughtItem.toys.smartVibes === 1; }
		}
	],

	buttplugs: [
		{
			name: "None",
			value: "none"
		},
		{
			name: "Standard plug",
			value: "plug"
		},
		{
			name: "Long plug",
			value: "long plug",
			get requirements() { return V.boughtItem.toys.buttPlugs === 1; }
		},
		{
			name: "Large plug",
			value: "large plug"
		},
		{
			name: "Long, large plug",
			value: "long, large plug",
			get requirements() { return V.boughtItem.toys.buttPlugs === 1; }
		},
		{
			name: "Huge plug",
			value: "huge plug",
		},
		{
			name: "Long, huge plug",
			value: "long, huge plug",
			get requirements() { return V.boughtItem.toys.buttPlugs === 1; }
		}
	],

	buttplugAttachments: [
		{name: "None", value: "none"},
		{name: "Tail", value: "tail", get requirements() { return V.boughtItem.toys.buttPlugTails === 1; }},
		{name: "Fox tail", value: "fox tail", get requirements() { return V.boughtItem.toys.buttPlugTails === 1; }},
		{name: "Cat tail", value: "cat tail", get requirements() { return V.boughtItem.toys.buttPlugTails === 1; }},
		{name: "Cow tail", value: "cow tail", get requirements() { return V.boughtItem.toys.buttPlugTails === 1; }}
	],
	/**
	 * @typedef {object} slaveWearChastity
	 * @property {string} name
	 * @property {string} value
	 * @property {object} updateSlave
	 * @property {string} [fs]
	 */

	/** @type {Array<slaveWearChastity>} */
	chastityDevices: [
		// '.value' must be a string, so using update slave so I can update multiple values.
		{
			name: "None",
			value: "none",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 0,
				chastityVagina: 0
			},
		},
		{
			name: "Anal chastity",
			value: "anal chastity",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 0,
				chastityVagina: 0
			}
		},
		{
			name: "Chastity belt",
			value: "chastity belt",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 0,
				chastityVagina: 1
			},
		},
		{
			name: "Combined chastity belt",
			value: "combined chastity belt",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 0,
				chastityVagina: 1
			},
		},
		{
			name: "Chastity cage",
			value: "chastity cage",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 1,
				chastityVagina: 0
			},
		},
		{
			name: "Combined chastity cage",
			value: "combined chastity cage",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 1,
				chastityVagina: 0
			},
		},
		{
			name: "Genital chastity",
			value: "genital chastity",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 1,
				chastityVagina: 1
			},
		},
		{
			name: "Full chastity",
			value: "full chastity",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 1,
				chastityVagina: 1
			},
		},
		{
			name: "Choose own chastity",
			value: "choose own chastity",
			fs: "FSRestart",
			updateSlave: {
				choosesOwnChastity: 1
			},
		},
		{
			name: "Revoke choosing own chastity",
			value: "revoke choosing own chastity",
			fs: "FSRestart",
			updateSlave: {
				choosesOwnChastity: 0
			},
		},
	],
};


code:
App.Art.cacheArtData = function() {
	/** @param {Element} node */
	function removeBadNamespaces(node) {
		const attrs = node.attributes;
		for (let index = 0; index < attrs.length; ++index) {
			const attr = attrs[index];
			if (attr.name.startsWith("sodipodi") || attr.name.startsWith("inkscape")) {
				node.attributes.removeNamedItem(attr.name);
			}
			for (const child of node.children) {
				removeBadNamespaces(child);
			}
		}
	}

	/**
	 * @param {NodeListOf<Element>} imagePassages
	 * @returns {Map<string, Element>}
	 */
	function makeCache(imagePassages) {
		const dict = new Map();

		for (const ip of imagePassages) {
			const name = ip.attributes.getNamedItem("name").value;
			let div = document.createElement("div");
			const svgData = atob(ip.innerHTML.replace(/data:image\/svg\+xml;base64,/, ''));
			div.innerHTML = svgData.trim();
			removeBadNamespaces(div.children.item(0));
			dict.set(name, div.children.item(0));
		}

		return dict;
	}

	App.Data.Art = {};
	App.Data.Art.Vector = makeCache(document.querySelectorAll('[tags="Twine.image"][name^="Art_Vector"]:not([name^="Art_Vector_Revamp"])'));
	App.Data.Art.VectorRevamp = makeCache(document.querySelectorAll('[tags="Twine.image"][name^="Art_Vector_Revamp"]'));
};

App.Art.SvgQueue = class {
	/**
	 * @param {{trigger:string, action:string, value:string}[]} transformRules - when a 'data-transform' attribute with value "trigger" is seen on an element, perform 'action' with 'value'
	 * @param {Map<string, Element>} cache
	 * @param {string} displayClass
	 */
	constructor(transformRules, cache, displayClass) {
		this._transformRules = transformRules;
		/** @type {{attrs: NamedNodeMap, nodes: Element[]}[]} */
		this._container = [];
		this._cache = cache;
		this._displayClass = displayClass;
	}

	/** transform a node via the transform rules
	 * @param {Element} node
	 */
	_transform(node) {
		const trigger = node.getAttribute("data-transform");
		if (trigger) {
			const rule = this._transformRules.find((r) => r.trigger === trigger);
			if (rule && rule.value) {
				if (rule.action === "text-content") {
					node.textContent = rule.value;
				} else {
					// by default, set attribute (usually 'transform')
					node.setAttribute(rule.action, rule.value);
				}
			}
		}
	}

	/** add an SVG from the cache to the render queue
	 * @param {string} id
	 */
	add(id) {
		const res = this._cache.get(id);
		let clones = [];
		if (!res) {
			console.error(`Missing art resource: ${id}`);
			return;
		}
		for (const srcNode of res.children) {
			const node = /** @type {Element} */ (srcNode.cloneNode(true));
			this._transform(node);
			let transformNodes = node.querySelectorAll('g[data-transform]');
			for (const child of transformNodes) {
				this._transform(child);
			}
			clones.push(node);
		}
		this._container.push({attrs: res.attributes, nodes: clones});
	}

	/** concatenate the contents of a second queue into this one.
	 * displayClass must match.  cache and transformFunc may differ (they are used only by add).
	 * @param {App.Art.SvgQueue} queue
	 */
	concat(queue) {
		if (this._displayClass !== queue._displayClass) {
			throw "Incompatible SVG queues. displayClass must match.";
		}
		this._container.push(...queue._container);
	}

	/** merge consecutive svg child nodes in the queue with the same svg attributes into bigger svg nodes, and write them out
	 * this prevents re-evaluating viewboxes and classes unnecessarily and improves layout performance with lots of art
	 * @returns {DocumentFragment}
	 */
	output() {
		/** evaluate whether an attribute list is equivalent or not
		 * @param {NamedNodeMap} left
		 * @param {NamedNodeMap} right
		 */
		function equalAttributes(left, right) {
			/** get all the attribute names from an attribute list
			 * @param {NamedNodeMap} attrs
			 * @returns {string[]}
			 */
			function attrNames(attrs) {
				let names = [];
				for (let index = 0; index < attrs.length; ++index) {
					names.push(attrs[index].nodeName);
				}
				return names;
			}

			if (!left && !right) {
				return true; // both are nullish, treat as equal
			} else if (!left || !right) {
				return false; // only one is nullish, not equal
			}

			const leftNames = attrNames(left), rightNames = attrNames(right);
			const intersectionLength = _.intersection(leftNames, rightNames).length;
			if (leftNames.length !== intersectionLength || rightNames.length !== intersectionLength) {
				return false; // contain different attributes, not equal
			}
			// are all values equal?
			return leftNames.every((attr) => left.getNamedItem(attr).nodeValue === right.getNamedItem(attr).nodeValue);
		}

		let frag = document.createDocumentFragment();
		let currentAttributes, outSvg;
		for (const svg of this._container) {
			if (!equalAttributes(currentAttributes, svg.attrs)) {
				outSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
				outSvg.setAttribute("class", this._displayClass);
				frag.appendChild(outSvg);
				for (let index = 0; index < svg.attrs.length; ++index) {
					outSvg.setAttribute(svg.attrs[index].nodeName, svg.attrs[index].nodeValue);
				}
				currentAttributes = svg.attrs;
			}
			outSvg.append(...svg.nodes);
		}
		return frag;
	}
};


code:
/**
 * generate two independent Gaussian numbers using Box-Muller transform.
 * mean and deviation specify the desired mean and standard deviation.
 * @param {number} [mean]
 * @param {number} [deviation]
 * @returns {number[]}
 */
function gaussianPair(mean = 0, deviation = 1) {
	const r = Math.sqrt(-2.0 * Math.log(1 - Math.random()));
	const sigma = 2.0 * Math.PI * (1 - Math.random());
	return [r * Math.cos(sigma), r * Math.sin(sigma)].map(val => val * deviation + mean);
}

/**
 * Generate a random integer with a normal distribution between min and max (both inclusive).
 * Default parameters result in truncating the standard normal distribution between -3 and +3.
 * Not specifying min/max results in rerolling val approximately 0.3% of the time.
 * @param {number} [mean]
 * @param {number} [deviation]
 * @param {number} [min]
 * @param {number} [max]
 * @returns {number}
 */
function normalRandInt(mean = 0, deviation = 1, min = mean - 3 * deviation, max = mean + 3 * deviation) {
	let val = gaussianPair(mean, deviation)[0];
	while (val < min || val > max) {
		val = gaussianPair(mean, deviation)[0];
	}
	return Math.round(val);
}

/**
 * Returns a random integer between min and max (both inclusive).
 * If count is defined, chooses that many random numbers between min and max and returns the average. This is an approximation of a normal distribution.
 * @param {number} min
 * @param {number} max
 * @param {number} [count]
 * @returns {number}
 */
function jsRandom(min, max, count = 1) {
	function rand() {
		return Math.random() * (max - min + 1) + min;
	}

	if (count === 1) {
		return Math.floor(rand());
	}

	let total = 0;
	for (let i = 0; i < count; i++) {
		total += rand();
	}
	return Math.floor(total / count);
}

/**
 * Chooses multiple random elements of an array.
 * @param {number[]} arr
 * @param {number} count
 * @returns {number[]}
 */
function jsRandomMany(arr, count) {
	let result = [];
	let _tmp = arr.slice();
	for (let i = 0; i < count; i++) {
		let index = Math.floor(Math.random() * _tmp.length);
		result.push(_tmp.splice(index, 1)[0]);
	}
	return result;
}

/**
 * Accepts both an array and a list, returns undefined if nothing is passed.
 * @param {any[]} choices
 * @param {any} [otherChoices]
 * @returns {any}
 */
function jsEither(choices, ...otherChoices) {
	if (otherChoices.length === 0 && Array.isArray(choices)) {
		return choices[Math.floor(Math.random() * choices.length)];
	}
	const allChoices = otherChoices;
	allChoices.push(choices);
	return allChoices[Math.floor(Math.random() * allChoices.length)];
}


code:
/* eslint no-unused-vars: [2, { "vars": "local" }] */
/* This file contains only JS functions without dependencies on FC specific variables/conventions and do not rely on
 * custom functions outside this file
 */

/**
 * Returns whether x is defined. Port of SugarCube's def.
 * @param {any} x
 * @returns {boolean}
 */
function jsDef(x) {
	if (typeof x === "undefined" || x === null || x === undefined) {
		return false;
	}
	return true;
}

/**
 * @param {number} n
 * @returns {boolean}
 */
function isFloat(n) {
	return Number.isFinite(n) && Math.floor(n) !== n;
}

/**
 * Determines if a is between low and high
 * @param {number} a
 * @param {number} low
 * @param {number} high
 * @param {"exclusive"|"inclusive"} [mode='exclusive'] defaults to 'exclusive' but also supports 'inclusive'.
 * @returns {boolean}
 */
function between(a, low, high, mode = 'exclusive') {
	if (low === null) { low = -Infinity; }
	if (high === null) { high = Infinity; }
	if (mode === 'exclusive') {
		return a > low && a < high;
	} else if (mode === 'inclusive') {
		return a >= low && a <= high;
	}
}

/**
 * @template {PropertyKey} T
 * @param {Object.<T, number>} obj
 * @returns {T}
 */
function hashChoice(obj) {
	let randint = Math.floor(Math.random() * hashSum(obj));
	let ret;
	Object.keys(obj).some((key) => {
		if (randint < obj[key]) {
			ret = key;
			return true;
		} else {
			randint -= obj[key];
			return false;
		}
	});
	return ret;
}

/**
 * @param {Object.<PropertyKey, number>} obj
 * @returns {number}
 */
function hashSum(obj) {
	let sum = 0;
	Object.keys(obj).forEach((key) => {
		sum += obj[key];
	});
	return sum;
}

/**
 * @template {PropertyKey} T
 * @param {T[]} arr
 * @returns {Object.<T, number>}
 */
function arr2obj(arr) {
	/** @type {Object.<T, number>} */
	const obj = {};
	arr.forEach((item) => {
		obj[item] = 1;
	});
	return obj;
}

/**
 * @param {object} object
 * @param {PropertyKey[]} rest
 */
function hashPush(object, ...rest) {
	rest.forEach((item) => {
		if (object[item] === undefined) {
			object[item] = 1;
		} else {
			object[item] += 1;
		}
	});
}

/**
 * @param {Object.<PropertyKey, number>} obj
 * @param {Object.<PropertyKey, number>} other
 */
function hashMerge(obj, other) {
	Object.keys(other).forEach((key) => {
		if (obj[key] === undefined) {
			obj[key] = other[key];
		} else {
			obj[key] += other[key];
		}
	});
}

/**
 * @template {PropertyKey} T
 * @param {T[]} array
 * @returns {Object.<T, number>}
 */
function weightedArray2HashMap(array) {
	/** @type {Object.<T, number>} */
	const obj = {};
	array.forEach((item) => {
		if (obj[item] === undefined) {
			obj[item] = 1;
		} else {
			obj[item] += 1;
		}
	});
	return obj;
}

/**
 * generate a random, almost unique ID that is compliant (possibly) with RFC 4122
 *
 * @returns {string}
 */
function generateNewID() {
	let date = Date.now(); // high-precision timer
	let uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
		let r = (date + Math.random() * 16) % 16 | 0;
		date = Math.floor(date / 16);
		return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
	});
	return uuid;
}

/**
 * @param {Array} array
 * @param {number} indexA
 * @param {number} indexB
 */
function arraySwap(array, indexA, indexB) {
	const tmp = array[indexA];
	array[indexA] = array[indexB];
	array[indexB] = tmp;
}

/**
 * @param {string} string
 * @returns {string}
 */
function capFirstChar(string) {
	return string.charAt(0).toUpperCase() + string.substr(1);
}

/**
 * @param {string} string
 * @returns {string}
 */
function uncapFirstChar(string) {
	return string.charAt(0).toLowerCase() + string.substr(1);
}

/**
 * @param {string} word
 * @returns {string}
 */
function addA(word) {
	let vocal = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'];
	if (vocal.includes(word.charAt(0))) {
		return `an ${word}`;
	}
	return `a ${word}`;
}

Object.defineProperty(Array.prototype, 'toStringExt', {
	/**
	 * @param {string} delimiter
	 * @param {string} lastDelimiter
	 * @returns {string}
	 */
	value(delimiter = ', ', lastDelimiter = ' and ') {
		if (this == null) {
			throw new TypeError('Array.prototype.toStringExt called on null or undefined');
		}
		if (this.length === 0) {
			return "none";
		}
		if (this.length === 1) {
			return this[0].toString();
		}
		const last = this.pop();
		let r = this.join(delimiter);
		this.push(last); // don't leave the array modified
		return `${r}${lastDelimiter}${last}`;
	}
});

/**
 * @param {number} i
 * @returns {string}
 */
function ordinalSuffix(i) {
	let j = i % 10;
	let k = i % 100;
	if (j === 1 && k !== 11) {
		return `${i}st`;
	}
	if (j === 2 && k !== 12) {
		return `${i}nd`;
	}
	if (j === 3 && k !== 13) {
		return `${i}rd`;
	}
	return `${i}th`;
}

/**
 * @param {number} i
 * @returns {string}
 */
function ordinalSuffixWords(i) {
	const text = ["zeroth", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth"];
	if (i < text.length) {
		return text[i];
	}
	return ordinalSuffix(i);
}

/**
 * @template T
 * @param {Iterable<T>} array
 * @returns {T[]}
 */
function removeDuplicates(array) {
	return [...new Set(array)];
}

/**
 * Maps an index from one list onto a matching index on the other.
 * The first and last indexes will be matched to the first and last indexes of the other list,
 * while indexes in between will go to the nearest index.
 * @param {number} index The index in original list to map to new list.
 * @param {*} originalList The original list the index refers into.
 * @param {*} newList The new list which we want an index for
 * @returns {number} The new index into newList
 */
App.Utils.mapIndexBetweenLists = function(index, originalList, newList) {
	if (index === 0) { return 0; }
	if (index === originalList.length - 1) { return newList.length - 1; }
	index--;
	const originalLimitedLength = originalList.length - 2;
	const newLimitedLength = newList.length - 2;
	return Math.round((index / originalLimitedLength) * newLimitedLength) + 1;
};

/**
 * replaces special HTML characters with their '&xxx' forms
 * @param {string} text
 * @returns {string}
 */
App.Utils.escapeHtml = function(text) {
	const map = {
		'&': '&',
		'<': '<',
		'>': '>',
		'"': '"',
		"'": '''
	};
	return text.replace(/[&<>"']/g, m => map[m]);
};

/**
 * Creates an object where the items are accessible via their ids.
 *
 * @param {Iterable} list
 * @returns {{}}
 */
function mapIdList(list) {
	let mappedList = {};
	for (const item of list) {
		mappedList[item.id] = item;
	}
	return mappedList;
}

/**
 * Topological sorting algorithm
 * https://gist.github.com/shinout/1232505
 * Added keys parameter since it better suits our needs and updated to project code style.
 *
 * @template {PropertyKey} T
 * @param {T[]} keys
 * @param {T[][]} edges list of edges. each edge forms Array<ID,ID> e.g. [12, 3]
 * @returns {T[]} Array: topological sorted list of IDs
 * @throws Error: in case there is a closed chain.
 **/
App.Utils.topologicalSort = function(keys, edges) {
	class Node {
		/** @param {T} id */
		constructor(id) {
			this.id = id;
			/** @type {T[]} */
			this.afters = [];
		}
	}

	/** @type {Object.<T, Node>} */
	let nodes = {}; // hash: stringified id of the node => { id: id, afters: list of ids }
	let sorted = []; // sorted list of IDs ( returned value )
	/** @type {Object.<T, boolean>} */
	let visited = {}; // hash: id of already visited node => true

	// 1. build data structures
	keys.forEach(key => {
		nodes[key] = new Node(key);
	});

	edges.forEach(edge => {
		const from = edge[0], to = edge[1];
		if (!nodes[from]) { nodes[from] = new Node(from); }
		if (!nodes[to]) { nodes[to] = new Node(to); }
		nodes[from].afters.push(to);
	});

	// 2. topological sort
	Object.keys(nodes).forEach(function visit(idstr, ancestors) {
		let node = nodes[idstr];
		let id = node.id;

		// if already exists, do nothing
		if (visited[idstr]) { return; }

		if (!Array.isArray(ancestors)) { ancestors = []; }

		ancestors.push(id);

		visited[idstr] = true;

		node.afters.forEach((afterID) => {
			if (ancestors.indexOf(afterID) >= 0) { // if already in ancestors, a closed chain exists.
				throw new Error('closed chain : ' + afterID + ' is in ' + id);
			}

			visit(afterID.toString(), ancestors.map(v => v)); // recursive call
		});

		sorted.unshift(id);
	});

	return sorted;
};

/**
 * Sorts an array of objects based on the order of the first array.
 * Sorts by values accessible by unsorted[key]
 * Values of the second array must be a subset of sorted
 *
 * @param {Array<any>} sorted
 * @param {Array<object>} unsorted
 * @param {string} key
 * @returns {Array<object>}
 */
function sortArrayByArray(sorted, unsorted, key) {
	const map = {};
	sorted.forEach((value, index) => {
		map[value] = index;
	});

	return unsorted.sort((a, b) => {
		return map[a[key]] - map[b[key]];
	});
}

/**
 * @param {object} target
 * @param {object} source
 */
function deepAssign(target, source) {
	function isObject(o) {
		return (o !== undefined && o !== null && typeof o === 'object' && !Array.isArray(o));
	}

	if (isObject(target) && isObject(source)) {
		for (const key in source) {
			if (!source.hasOwnProperty(key)) { continue; }
			if (isObject(source[key])) {
				if (!target.hasOwnProperty(key) || !isObject(target[key])) {
					target[key] = {};
				}
				deepAssign(target[key], source[key]);
			} else {
				Object.assign(target, {
					[key]: source[key]
				});
			}
		}
	}
}

/**
 * Returns the median value for an array
 *
 * For more information about mean vs. median see
 * https://www.clinfo.eu/mean-median/
 * @param {number[]} arr Does not need to be sorted
 * @returns {number}
 */
function median(arr = []) {
	const mid = Math.floor(arr.length / 2);
	const nums = [...arr].sort((a, b) => a - b);

	return arr.length % 2 === 0 ?
		(nums[mid] + nums[mid - 1]) / 2 :
		 nums[mid];
}


free-cities

description: The Free Cities game

type: git

lint: eslint src/**/*.js