diff --git a/.gitignore b/.gitignore
index 36b4d00deeb5a33fc7831b5c2ee11b30581692de..0c1f7be62c2a8cb7e78adcb57afd699e9d56e9e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,76 +16,6 @@ Sessionx.vim
 tags
 [._]*.un~
 
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-bin/*
-
-# PyInstaller
-#  Usually these files are written by a python script from a template
-#  before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-.hypothesis/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# IPython Notebook
-.ipynb_checkpoints
-
-# pyenv
-.python-version
-
 # celery beat schedule file
 celerybeat-schedule
 
@@ -112,6 +42,7 @@ yarn.lock
 
 # Visual Studio Code
 .vscode/settings.json
+.vscode/tasks.json
 *.code-workspace
 
 # WebStorm
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index 0a3859ac54632db02246f16bd4b628faeea26e1c..0000000000000000000000000000000000000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
-	// See https://go.microsoft.com/fwlink/?LinkId=733558
-	// for the documentation about the tasks.json format
-	"version": "2.0.0",
-	"command": "Test build",
-	"tasks": [
-		{
-			"label": "Compile",
-			"type": "shell",
-			"command": "./compile.sh",
-			"windows": {
-				"command": ".\\compile.bat"
-			},
-			"group": "build",
-			"presentation": {
-				"reveal": "always",
-				"panel": "new"
-			},
-			"problemMatcher": []
-		},
-		{
-			"label": "Run in Chrome",
-			"type": "process",
-			"command": "chrome.exe",
-			"windows": {
-				"command": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"
-			},
-			"group": "test",
-			"args": [
-				"${workspaceRoot}/bin/FC_pregmod.html", "--incognito"
-			],
-			"problemMatcher": [],
-		}
-	]
-}
diff --git a/devTools/types/FC/UI.d.ts b/devTools/types/FC/UI.d.ts
index 75680e0ff630fb7da286266fc9e18b3d76623be2..fd6d22e55120a4153d7ea03f7f003aa9f8dc1279 100644
--- a/devTools/types/FC/UI.d.ts
+++ b/devTools/types/FC/UI.d.ts
@@ -32,3 +32,5 @@ declare namespace FC {
 		}
 	}
 }
+
+declare type PassageLinkMap = Pick<HTMLElementTagNameMap, 'a' | 'audio' | 'img' | 'source' | 'video'>
diff --git a/devTools/types/FC/desc.d.ts b/devTools/types/FC/desc.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c98a1e3ec8e7d62fd9faf64d2ba3350f6908fb3a
--- /dev/null
+++ b/devTools/types/FC/desc.d.ts
@@ -0,0 +1,14 @@
+declare namespace FC {
+	namespace Desc {
+		interface LongSlaveOptions {
+			/**
+			 * 0 if the slave is not for sale.  Otherwise a string with the name of the market,
+			 * partially to determine if laws apply to the market or not.
+			 */
+			market?: Zeroable<string>;
+			eventDescription?: boolean;
+			prisonCrime?: boolean;
+			noArt?: boolean;
+		}
+	}
+}
diff --git a/devTools/types/FC/gameState.d.ts b/devTools/types/FC/gameState.d.ts
index 2ea8aa8634098ae9e3ef82ef3b2bccc6d302df22..d8623d645d13a8e42e22c7e867d8dfe3ac7a3664 100644
--- a/devTools/types/FC/gameState.d.ts
+++ b/devTools/types/FC/gameState.d.ts
@@ -172,6 +172,7 @@ declare namespace FC {
 		milkmaidTrustBonus?: number;
 		milkmaidHealthBonus?: number;
 
+		AS: number;
 		HGTrainSlavesIDs?: HeadGirlTrainee[];
 		heroSlaveID?: number;
 		seed?: number;
diff --git a/src/003-assets/CSS/links.css b/src/003-assets/CSS/links.css
index 85517ce2a136a4c5851d050b3db8a59c3a3b429a..654265f051a54e3cb206dd9788a6addd3fe4df5b 100644
--- a/src/003-assets/CSS/links.css
+++ b/src/003-assets/CSS/links.css
@@ -11,8 +11,7 @@
 	font-size: smaller;
 	width: 20em;
 	margin-left: -10em;
-	background-color: slategray;
-	color: black;
+	background-color: rgb(29, 30, 32);
 	text-align: center;
 	border-radius: 3px;
 	padding: 3px;
diff --git a/src/js/slaveListing.js b/src/js/slaveListing.js
index 8b387b2e2c058ba9a35273a8dcafd29c9a2b7a4e..653ccc8655c80dc48d781e041e4b323bcdea4b26 100644
--- a/src/js/slaveListing.js
+++ b/src/js/slaveListing.js
@@ -522,10 +522,11 @@ App.UI.SlaveList.SlaveInteract = {};
 
 /**
  * @param {App.Entity.SlaveState} slave
+ * @param {string} [text] print this text instead of slave name
  * @returns {DocumentFragment|HTMLElement}
  */
-App.UI.SlaveList.SlaveInteract.stdInteract = function(slave) {
-	const link = App.UI.DOM.passageLink(SlaveFullName(slave), "Slave Interact", () => {
+App.UI.SlaveList.SlaveInteract.stdInteract = function(slave, text) {
+	const link = App.UI.DOM.passageLink(text ? text : SlaveFullName(slave), "Slave Interact", () => {
 		App.UI.SlaveList.ScrollPosition.record();
 		V.AS = slave.ID;
 	});
diff --git a/src/js/slaveSummaryHelpers.js b/src/js/slaveSummaryHelpers.js
index 5f1786187abec513f89be88e8f77dab382db702f..e65fc07f1df1afeb87d8e79804ea2daa1861f682 100644
--- a/src/js/slaveSummaryHelpers.js
+++ b/src/js/slaveSummaryHelpers.js
@@ -219,21 +219,195 @@ App.UI.SlaveSummaryImpl = function() {
 			bigButts: 0,
 		};
 
+		/**
+		 *
+		 * @param {ParentNode} container
+		 * @param {App.Entity.SlaveState} slave
+		 * @param {string} text
+		 * @returns {HTMLSpanElement}
+		 */
+		function referenceSlaveWithPreview(container, slave, text) {
+			const res = App.UI.DOM.appendNewElement("span", container, text, "textWithTooltip");
+			const tooltip = App.UI.DOM.appendNewElement("span", res, undefined, "tooltip");
+			tooltip.append(App.UI.DOM.generateLinksStrip([
+				App.UI.DOM.slaveDescriptionDialog(slave, "Pop-up", {eventDescription: false, noArt: true}),
+				App.UI.SlaveList.SlaveInteract.stdInteract(slave, "Go to")
+			]));
+			return res;
+		}
+
+		const longFamilyBits = {
+			and: " and ",
+			makeBit: s => s + '.',
+			daughters10: "Has tons of daughters.",
+			daughters5: "Has many daughters.",
+			daughters1: "Has several daughters.",
+			sisters10: "One of many sisters.",
+			sisters5:  "Has many sisters.",
+			sisters1: "Has several sisters.",
+			emotionBind: "Emotionally bonded to you.",
+			emotionSlut: "Emotional slut."
+		};
+
+		const shortFamilyBits = {
+			and: " & ",
+			makeBit: s => s,
+			daughters10: "tons of daughters",
+			daughters5: "many daughters",
+			daughters1: "has daughters",
+			sisters10: "One of many sisters.",
+			sisters5:  "Has many sisters.",
+			sisters1: "Has several sisters.",
+			emotionBind: "E Bonded",
+			emotionSlut: "E Slut"
+		};
+
+		/**
+		 * @param {Node} container
+		 * @param {App.Entity.SlaveState} slave
+		 * @param {boolean} short
+		 */
+		function renderFamily(container, slave, short) {
+			let handled = 0;
+			const bits = short ? shortFamilyBits : longFamilyBits;
+			const block = makeBlock();
+			const cssClassName = "lightgreen";
+			if (slave.mother > 0) {
+				const _ssj = V.slaves.find(s => s.ID === slave.mother);
+				if (_ssj) {
+					helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+					addText(block, "'s ");
+					let spanText = getPronouns(slave).daughter;
+					if (slave.relationshipTarget === _ssj.ID) {
+						spanText += `${bits.and}${relationshipTerm(slave)}`;
+						handled = 1;
+					}
+					makeSpan(block, bits.makeBit(spanText), cssClassName);
+				}
+			} else if (slave.mother === -1) {
+				addText(block, `Your `);
+				if (slave.relationship < -1) {
+					makeSpan(block, bits.makeBit(`${getPronouns(slave).daughter}${bits.and}${PCrelationshipTerm(slave)}`), cssClassName);
+					handled = 1;
+				} else {
+					makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName);
+				}
+			} else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) {
+				addText(block, `${V.missingTable[slave.mother].fullName}'s `);
+				makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName);
+			}
+			if (slave.father > 0 && slave.father !== slave.mother) {
+				const _ssj = V.slaves.find(s => s.ID === slave.father);
+				if (_ssj) {
+					helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+					addText(block, "'s ");
+					let spanText = getPronouns(slave).daughter;
+					if (slave.relationshipTarget === _ssj.ID) {
+						spanText += `${bits.and}${relationshipTerm(slave)}`;
+						handled = 1;
+					}
+					makeSpan(block, bits.makeBit(spanText), cssClassName);
+				}
+			} else if (slave.father === -1 && slave.father !== slave.mother) {
+				addText(block, `Your `);
+				if (slave.relationship < -1) {
+					makeSpan(block, bits.makeBit(`${getPronouns(slave).daughter}${bits.and}${PCrelationshipTerm(slave)}`), cssClassName);
+					handled = 1;
+				} else {
+					makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName);
+				}
+			} else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) {
+				addText(block, `${V.missingTable[slave.father].fullName}'s `);
+				makeSpan(block, bits.makeBit(getPronouns(slave).daughter), cssClassName);
+			}
+			if (areSisters(V.PC, slave) > 0) {
+				addText(block, `Your `);
+				if (slave.relationship < -1) {
+					makeSpan(block, bits.makeBit(`${relativeTerm(V.PC, slave)}${bits.and}${PCrelationshipTerm(slave)}`), cssClassName);
+					handled = 1;
+				} else {
+					makeSpan(block, bits.makeBit(relativeTerm(V.PC, slave)), cssClassName);
+				}
+			}
+			if (slave.daughters === 1) {
+				const _ssj = V.slaves.find(s => s.mother === slave.ID || s.father === slave.ID);
+				if (_ssj) {
+					helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+					addText(block, "'s ");
+					let spanText = relativeTerm(_ssj, slave);
+					if (slave.relationshipTarget === _ssj.ID) {
+						spanText += `${bits.and}${relationshipTerm(slave)}`;
+						handled = 1;
+					}
+					makeSpan(block, bits.makeBit(spanText), cssClassName);
+				}
+			} else if (slave.daughters > 1) {
+				if (slave.daughters > 10) {
+					makeSpan(block, bits.daughtersGt10, cssClassName);
+				} else if (slave.daughters > 5) {
+					makeSpan(block, bits.daughtersGt5, cssClassName);
+				} else {
+					makeSpan(block, bits.daughtersGt1, cssClassName);
+				}
+			}
+			if (slave.sisters === 1) {
+				const _ssj = V.slaves.find(s => areSisters(s, slave) > 0);
+				if (_ssj) {
+					helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+					addText(block, "'s ");
+					let spanText = getPronouns(slave).sister;
+					if (slave.relationshipTarget === _ssj.ID) {
+						spanText += `${bits.and}${relationshipTerm(slave)}`;
+						handled = 1;
+					}
+					makeSpan(block, bits.makeBit(spanText), cssClassName);
+				}
+			} else if (slave.sisters > 1) {
+				if (slave.sisters > 10) {
+					makeSpan(block, bits.sisters10, cssClassName);
+				} else if (slave.sisters > 5) {
+					makeSpan(block, bits.sisters5, cssClassName);
+				} else {
+					makeSpan(block, bits.sisters1, cssClassName);
+				}
+			}
+			if (slave.relationship > 0 && handled !== 1) {
+				const _ssj = V.slaves.find(s => s.ID === slave.relationshipTarget);
+				if (_ssj) {
+					helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+					addText(block, "'s ");
+					makeSpan(block, bits.makeBit(relationshipTerm(slave)), cssClassName);
+				}
+			} else if (slave.relationship === -3 && !areRelated(V.PC, slave)) {
+				makeSpan(block, bits.makeBit(`Your ${getPronouns(slave).wife}`), cssClassName);
+			} else if (slave.relationship === -2) {
+				makeSpan(block, bits.emotionBind, cssClassName);
+			} else if (slave.relationship === -1) {
+				makeSpan(block, bits.emotionSlut, cssClassName);
+			}
+
+			if (block.textContent.length > 0) {
+				container.appendChild(block);
+			}
+		}
+
 		return {
-			addText: addText,
-			makeSpan: makeSpan,
-			makeBlock: makeBlock,
-			makeParagraph: makeParagraph,
-			getExactRating: getExactRating,
-			getNumericRating: getNumericRating,
-			getMultiNumericRating: getMultiNumericRating,
-			makeStyledSpan: makeStyledSpan,
-			makeRatedStyledSpan: makeRatedStyledSpan,
-			makeMappedStyledSpan: makeMappedStyledSpan,
-			makeMappedSpan: makeMappedSpan,
-			firstThreeUc: firstThreeUc,
-			syncFSData: syncFSData,
-			FSData: FSData,
+			addText,
+			makeSpan,
+			makeBlock,
+			makeParagraph,
+			getExactRating,
+			getNumericRating,
+			getMultiNumericRating,
+			makeStyledSpan,
+			makeRatedStyledSpan,
+			makeMappedStyledSpan,
+			makeMappedSpan,
+			firstThreeUc,
+			syncFSData,
+			FSData,
+			referenceSlaveWithPreview,
+			renderFamily
 		};
 	}();
 
@@ -1325,87 +1499,7 @@ App.UI.SlaveSummaryImpl = function() {
 		 * @returns {void}
 		 */
 		function short_family(slave, c) {
-			let res = "";
-			let handled = 0;
-			if (slave.mother > 0) {
-				const _ssj = V.slaves.find(s => s.ID === slave.mother);
-				if (_ssj) {
-					res += `${SlaveFullName(_ssj)}'s ${getPronouns(slave).daughter}`;
-					if (slave.relationshipTarget === _ssj.ID) {
-						res += ` & ${relationshipTermShort(slave)}`;
-						handled = 1;
-					}
-				}
-				res += " ";
-			} else if (slave.mother === -1) {
-				res += `Your ${getPronouns(slave).daughter}`;
-				if (slave.relationship < -1) {
-					res += ` & ${PCrelationshipTerm(slave)}`;
-					handled = 1;
-				}
-				res += " ";
-			} else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) {
-				res += `${V.missingTable[slave.mother].fullName}'s ${getPronouns(slave).daughter} `;
-			}
-			if (slave.father > 0 && slave.father !== slave.mother) {
-				const _ssj = V.slaves.find(s => s.ID === slave.father);
-				if (_ssj) {
-					res += `${SlaveFullName(_ssj)}'s ${getPronouns(slave).daughter}`;
-					if (slave.relationshipTarget === _ssj.ID && handled !== 1) {
-						res += ` & ${relationshipTermShort(slave)}`;
-						handled = 1;
-					}
-				}
-				res += " ";
-			} else if (slave.father === -1 && slave.mother !== -1) {
-				res += `Your ${getPronouns(slave).daughter}`;
-				if (slave.relationship < -1) {
-					res += ` & ${PCrelationshipTerm(slave)}`;
-					handled = 1;
-				}
-				res += " ";
-			} else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) {
-				res += `${V.missingTable[slave.father].fullName}'s ${getPronouns(slave).daughter}`;
-			}
-			if (slave.daughters === 1) {
-				const _ssj = V.slaves.find(s => s.mother === slave.ID || s.father === slave.ID);
-				if (_ssj) {
-					res += `${SlaveFullName(_ssj)}'s ${relativeTerm(_ssj, slave)}`;
-					if (slave.relationshipTarget === _ssj.ID) {
-						res += ` & ${relationshipTermShort(slave)}`;
-						handled = 1;
-					}
-				}
-				res += " ";
-			} else if (slave.daughters > 1) {
-				res += `multiple daughters `;
-			}
-			if (slave.sisters === 1) {
-				const _ssj = V.slaves.find(s => areSisters(s, slave) > 0);
-				if (_ssj) {
-					res += `${SlaveFullName(_ssj)}'s ${getPronouns(slave).sister}`;
-					if (slave.relationshipTarget === _ssj.ID) {
-						res += `& ${relationshipTermShort(slave)}`;
-						handled = 1;
-					}
-				}
-				res += " ";
-			} else if (slave.sisters > 1) {
-				res += `multiple sisters `;
-			}
-			if (slave.relationship > 0 && handled !== 1) {
-				const _ssj = V.slaves.find(s => s.ID === slave.relationshipTarget);
-				if (_ssj) {
-					res += `${SlaveFullName(_ssj)}'s ${relationshipTermShort(slave)}`;
-				}
-			} else if (slave.relationship === -3 && slave.mother !== -1 && slave.father !== -1) {
-				res += `Your ${getPronouns(slave).wife}`;
-			} else if (slave.relationship === -2) {
-				res += `E Bonded`;
-			} else if (slave.relationship === -1) {
-				res += `E Slut`;
-			}
-			helpers.makeSpan(c, res);
+			helpers.renderFamily(c, slave, true);
 		}
 
 		/**
@@ -1430,11 +1524,14 @@ App.UI.SlaveSummaryImpl = function() {
 				const _ssj = V.slaves.find(s => s.ID === slave.rivalryTarget);
 				if (_ssj) {
 					if (slave.rivalry <= 1) {
-						block.textContent = `Disl ${SlaveFullName(_ssj)}`;
+						block.textContent = 'Disl ';
+						helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
 					} else if (slave.rivalry <= 2) {
-						block.textContent = `${SlaveFullName(_ssj)}'s rival`;
+						helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+						addText(block, "'s rival");
 					} else {
-						block.textContent = `Hates ${SlaveFullName(_ssj)}`;
+						block.textContent = 'Hates ';
+						helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
 					}
 				}
 			}
@@ -1446,120 +1543,7 @@ App.UI.SlaveSummaryImpl = function() {
 		 * @returns {void}
 		 */
 		function long_family(slave, c) {
-			let handled = 0;
-			const block = makeBlock();
-			if (slave.mother > 0) {
-				const _ssj = V.slaves.find(s => s.ID === slave.mother);
-				if (_ssj) {
-					addText(block, `${SlaveFullName(_ssj)}'s `);
-					let spanText = getPronouns(slave).daughter;
-					if (slave.relationshipTarget === _ssj.ID) {
-						spanText += ` and ${relationshipTerm(slave)}`;
-						handled = 1;
-					}
-					makeSpan(block, spanText + '.', "lightgreen");
-				}
-			} else if (slave.mother === -1) {
-				addText(block, `Your `);
-				if (slave.relationship < -1) {
-					makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen");
-					handled = 1;
-				} else {
-					makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
-				}
-			} else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) {
-				addText(block, `${V.missingTable[slave.mother].fullName}'s `);
-				makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
-			}
-			if (slave.father > 0 && slave.father !== slave.mother) {
-				const _ssj = V.slaves.find(s => s.ID === slave.father);
-				if (_ssj) {
-					addText(block, `${SlaveFullName(_ssj)}'s `);
-					let spanText = getPronouns(slave).daughter;
-					if (slave.relationshipTarget === _ssj.ID) {
-						spanText += ` and ${relationshipTerm(slave)}`;
-						handled = 1;
-					}
-					makeSpan(block, spanText + '.', "lightgreen");
-				}
-			} else if (slave.father === -1 && slave.father !== slave.mother) {
-				addText(block, `Your `);
-				if (slave.relationship < -1) {
-					makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen");
-					handled = 1;
-				} else {
-					makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
-				}
-			} else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) {
-				addText(block, `${V.missingTable[slave.father].fullName}'s `);
-				makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen");
-			}
-			if (areSisters(V.PC, slave) > 0) {
-				addText(block, `Your `);
-				if (slave.relationship < -1) {
-					makeSpan(block, `${relativeTerm(V.PC, slave)} and ${PCrelationshipTerm(slave)}.`, "lightgreen");
-					handled = 1;
-				} else {
-					makeSpan(block, `${relativeTerm(V.PC, slave)}.`, "lightgreen");
-				}
-			}
-			if (slave.daughters === 1) {
-				const _ssj = V.slaves.find(s => s.mother === slave.ID || s.father === slave.ID);
-				if (_ssj) {
-					addText(block, `${SlaveFullName(_ssj)}'s `);
-					let spanText = relativeTerm(_ssj, slave);
-					if (slave.relationshipTarget === _ssj.ID) {
-						spanText += ` and ${relationshipTerm(slave)}`;
-						handled = 1;
-					}
-					makeSpan(block, spanText + '.', "lightgreen");
-				}
-			} else if (slave.daughters > 1) {
-				if (slave.daughters > 10) {
-					makeSpan(block, "Has tons of daughters.", "lightgreen");
-				} else if (slave.daughters > 5) {
-					makeSpan(block, "Has many daughters.", "lightgreen");
-				} else {
-					makeSpan(block, "Has several daughters.", "lightgreen");
-				}
-			}
-			if (slave.sisters === 1) {
-				const _ssj = V.slaves.find(s => areSisters(s, slave) > 0);
-				if (_ssj) {
-					addText(block, `${SlaveFullName(_ssj)}'s `);
-					let spanText = getPronouns(slave).sister;
-					if (slave.relationshipTarget === _ssj.ID) {
-						spanText += ` and ${relationshipTerm(slave)}`;
-						handled = 1;
-					}
-					makeSpan(block, spanText + '.', "lightgreen");
-				}
-			} else if (slave.sisters > 1) {
-				if (slave.sisters > 10) {
-					makeSpan(block, "One of many sisters.", "lightgreen");
-				} else if (slave.sisters > 5) {
-					makeSpan(block, "Has many sisters.", "lightgreen");
-				} else {
-					makeSpan(block, "Has several sisters.", "lightgreen");
-				}
-			}
-			if (slave.relationship > 0 && handled !== 1) {
-				const _ssj = V.slaves.find(s => s.ID === slave.relationshipTarget);
-				if (_ssj) {
-					addText(block, `${SlaveFullName(_ssj)}'s `);
-					makeSpan(block, `${relationshipTerm(slave)}.`, "lightgreen");
-				}
-			} else if (slave.relationship === -3 && !areRelated(V.PC, slave)) {
-				makeSpan(block, `Your ${getPronouns(slave).wife}.`, "lightgreen");
-			} else if (slave.relationship === -2) {
-				makeSpan(block, "Emotionally bonded to you.", "lightgreen");
-			} else if (slave.relationship === -1) {
-				makeSpan(block, "Emotional slut.", "lightgreen");
-			}
-
-			if (block.textContent.length > 0) {
-				c.appendChild(block);
-			}
+			helpers.renderFamily(c, slave, false);
 		}
 
 		/**
@@ -1585,13 +1569,18 @@ App.UI.SlaveSummaryImpl = function() {
 				if (_ssj) {
 					if (slave.rivalry <= 1) {
 						makeSpan(block, "Dislikes", "lightsalmon");
-						block.appendChild(document.createTextNode(` ${SlaveFullName(_ssj)}.`));
+						addText(block, ' ');
+						helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+						addText(block, '.');
 					} else if (slave.rivalry <= 2) {
-						block.appendChild(document.createTextNode(`${SlaveFullName(_ssj)}'s `));
+						helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+						addText(block, "'s ");
 						makeSpan(block, "rival.", "lightsalmon");
 					} else {
 						makeSpan(block, "Hates", "lightsalmon");
-						block.appendChild(document.createTextNode(` ${SlaveFullName(_ssj)}.`));
+						addText(block, ' ');
+						helpers.referenceSlaveWithPreview(block, _ssj, SlaveFullName(_ssj));
+						addText(block, '.');
 					}
 				}
 			}
diff --git a/src/js/utilsDOM.js b/src/js/utilsDOM.js
index 43f0377a278ae7f84f27211b5bbfb4466d30cd12..05071b4e5e5f62643629860846dfc192e68ecb7b 100644
--- a/src/js/utilsDOM.js
+++ b/src/js/utilsDOM.js
@@ -7,13 +7,14 @@
  *
  * The result works in the same way as the wiki markup in the SugarCube
  * @see https://www.motoslave.net/sugarcube/2/docs/#markup-html-attribute
+ * @template {keyof PassageLinkMap} K
  * @param {string} linkText link text
  * @param {string} passage the passage name to link to
  * @param {passageLinkHandler} [handler] setter text (optional)
  * @param {string} [tooltip=''] tooltip text (optional)
- * @param {string} [elementType='a'] element type (optional) default is 'a'.
+ * @param {K} [elementType='a'] element type (optional) default is 'a'.
  * Could be any of 'a', 'audio', img', 'source', 'video'
- * @returns {HTMLElement} element text
+ * @returns {PassageLinkMap[K]} element text
  *
  * @example
  * // equal to [[Go to town|Town]]
diff --git a/src/js/utilsSC.js b/src/js/utilsSC.js
index b796c63e8d24b639e726aa4d28dee707f6908a72..6f8753fba5dd6d8c8a1a962ff854e2c1ca462bb9 100644
--- a/src/js/utilsSC.js
+++ b/src/js/utilsSC.js
@@ -243,10 +243,10 @@ App.UI.disabledLink = function(link, reasons) {
 };
 
 /** handler function for slaveDescriptionDialog. do not call directly. */
-App.UI._showDescriptionDialog = function(slave) {
+App.UI._showDescriptionDialog = function(slave, options) {
 	Dialog.setup(SlaveFullName(slave));
 	const image = V.seeImages ? App.UI.DOM.makeElement("div", App.Art.SlaveArtElement(slave, 2, 0), ["imageRef", "medImg"]) : '';
-	Dialog.append(image).append(App.Desc.longSlave(slave, {eventDescription: 1}));
+	Dialog.append(image).append(App.Desc.longSlave(slave, options));
 	Dialog.open();
 };
 
@@ -254,20 +254,23 @@ App.UI._showDescriptionDialog = function(slave) {
  * Generates a link which shows a slave description dialog for a specified slave.
  * Do not call from within another dialog.
  * @param {App.Entity.SlaveState} slave
+ * @param {FC.Desc.LongSlaveOptions} options
  * @returns {string} link (in SC markup)
  */
-App.UI.slaveDescriptionDialog = function(slave) {
-	return App.UI.link(SlaveFullName(slave), App.UI._showDescriptionDialog, [slave]);
+App.UI.slaveDescriptionDialog = function(slave, options = {eventDescription: true}) {
+	return App.UI.link(SlaveFullName(slave), App.UI._showDescriptionDialog, [slave, options]);
 };
 
 /**
  * Generates a link which shows a slave description dialog for a specified slave.
  * Do not call from within another dialog.
  * @param {App.Entity.SlaveState} slave
+ * @param {string} [text] link text to use instead of slave name
+ * @param {FC.Desc.LongSlaveOptions} options
  * @returns {HTMLElement} link
  */
-App.UI.DOM.slaveDescriptionDialog = function(slave) {
-	return App.UI.DOM.link(SlaveFullName(slave), App.UI._showDescriptionDialog, [slave]);
+App.UI.DOM.slaveDescriptionDialog = function(slave, text, options = {eventDescription: true}) {
+	return App.UI.DOM.link(text ? text : SlaveFullName(slave), App.UI._showDescriptionDialog, [slave, options]);
 };
 
 /**
diff --git a/src/npc/descriptions/longSlave.js b/src/npc/descriptions/longSlave.js
index e346ec3936a8e68fec553d6eeb09f76ea0007496..b8e1e60c18406eaa3de09df2b5cb3cbef8e2ac25 100644
--- a/src/npc/descriptions/longSlave.js
+++ b/src/npc/descriptions/longSlave.js
@@ -1,13 +1,9 @@
 /**
  * @param {App.Entity.SlaveState} slave
- * @param {object} params
- * @param {string|number} [params.market] 0 if the slave is not for sale.  Otherwise a string with the name of the market,
- * partially to determine if laws apply to the market or not.
- * @param {number} [params.eventDescription]
- * @param {string} [params.prisonCrime]
+ * @param {FC.Desc.LongSlaveOptions} params
  * @returns {DocumentFragment}
  */
-App.Desc.longSlave = function(slave = V.activeSlave, {market = 0, eventDescription = 0, prisonCrime} = {}) {
+App.Desc.longSlave = function(slave = V.activeSlave, {market = 0, eventDescription = false, prisonCrime, noArt} = {}) {
 	const {
 		He, His, him, he, his
 	} = getPronouns(slave);
@@ -19,7 +15,7 @@ App.Desc.longSlave = function(slave = V.activeSlave, {market = 0, eventDescripti
 	const applyLaw = applyLawCheck(market);
 	SlaveStatClamp(slave);
 
-	if (V.seeImages === 1 && !eventDescription && passage() !== "Slave Interact") {
+	if (V.seeImages === 1 && !eventDescription && !noArt) {
 		// Art
 		span = document.createElement('span');
 		span.id = "art-frame";
diff --git a/src/npc/descriptions/sceneIntro.js b/src/npc/descriptions/sceneIntro.js
index 136a415110203e6630ce96a8d0a5da321dac3100..869674d018a2313ef4d0e7af9bfa07d44f336a57 100644
--- a/src/npc/descriptions/sceneIntro.js
+++ b/src/npc/descriptions/sceneIntro.js
@@ -11,7 +11,7 @@ App.Desc.sceneIntro = function(slave, {market, eventDescription} = {}) {
 		he, him, his, He, His, himself, wife
 	} = getPronouns(slave);
 
-	if (eventDescription === 1) {
+	if (eventDescription) {
 		r.push(`${He} is currently involved in an event, but is assigned to ${slave.assignment}.`);
 		if (slave.assignment === "be a subordinate slave") {
 			let lsd = getSlave(slave.subTarget);
diff --git a/src/uncategorized/slaveInteract.tw b/src/uncategorized/slaveInteract.tw
index 7dad7290954ec125da9e206c081162f41e92c1aa..c1d1aa8e48f9632de2edbece14e758016977d2b5 100644
--- a/src/uncategorized/slaveInteract.tw
+++ b/src/uncategorized/slaveInteract.tw
@@ -54,7 +54,7 @@
 <<includeDOM App.UI.SlaveInteract.navigation(getSlave($AS))>>
 
 <div class="tab-bar">
-	<button class="tab-links" onclick="App.UI.tabBar.openTab(event, 'Description'), jQuery('#LSD').empty().append(App.Desc.longSlave(getSlave(V.AS)))" id="tab Description">Description</button>
+	<button class="tab-links" onclick="App.UI.tabBar.openTab(event, 'Description'), jQuery('#LSD').empty().append(App.Desc.longSlave(getSlave(V.AS), {noArt: true}))" id="tab Description">Description</button>
 	<button class="tab-links" onclick="App.UI.tabBar.openTab(event, 'Modify')" id="tab Modify">Modify</button>
 	<button class="tab-links" onclick="App.UI.tabBar.openTab(event, 'Work')" id="tab Work">Work</button>
 	<button class="tab-links" onclick="App.UI.tabBar.openTab(event, 'Appearance'), App.UI.Wardrobe.refreshAll(getSlave(V.AS))" id="tab Appearance">Appearance</button>